diff --git a/.gitignore b/.gitignore index 84957a801..7688849af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ documents/ vendor/ +public/vendor /nbproject/ .vscode diff --git a/application/config/lvverwaltung.php b/application/config/lvverwaltung.php index 4fe09bff3..d315c9b38 100644 --- a/application/config/lvverwaltung.php +++ b/application/config/lvverwaltung.php @@ -8,4 +8,15 @@ $config['lvverwaltung_tags'] = [ ]; */ -$config['lvverwaltung_tags'] = []; \ No newline at end of file +$config['lvverwaltung_tags'] = []; + +# Additional columns: +/* +$config["list_columns"] = [ + 'fieldname' => [ + 'js' => 'path/to/snippet.js', // tabulator config snippet (eg: return { name: 'Name', field: 'fieldname' }; ) + // NOTE: 'default' & 'joins' like in stv.php config are currently not available here + ] +]; +*/ + diff --git a/application/config/navigation.php b/application/config/navigation.php index 4d4dcc22a..cd71b02ca 100644 --- a/application/config/navigation.php +++ b/application/config/navigation.php @@ -170,6 +170,13 @@ $config['navigation_header'] = array( 'expand' => true, 'sort' => 51, 'requiredPermissions' => 'vertrag/mitarbeiter:r' + ), + 'studierendenverwaltung' => array( + 'link' => site_url('studentenverwaltung'), + 'description' => 'Studierendenverwaltung', + 'expand' => true, + 'sort' => 52, + 'requiredPermissions' => ['admin:r', 'assistenz:r'] ) ) ), @@ -378,7 +385,7 @@ $config['navigation_menu']['apps'] = [ ], 'lav' => [ 'link' => site_url('lehre/lehrauftrag/Lehrauftrag/Dashboard'), - 'description' => 'Lehraufträge', + 'description' => 'Lehrauftragsverwaltung', #'icon' => 'person-chalkboard', 'requiredPermissions' => array('lehre/lehrauftrag_bestellen:r', 'lehre/lehrauftrag_erteilen:r') ] diff --git a/application/config/routes.php b/application/config/routes.php index 0eb2839cd..975537dc4 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -76,52 +76,67 @@ $route['Abgabetool/Student'] = 'Cis/Abgabetool/Student'; $route['Abgabetool/Student/(:any)'] = 'Cis/Abgabetool/Student/$1'; $route['Abgabetool/Deadlines'] = 'Cis/Abgabetool/Deadlines'; +// Studierendenverwaltung App Routes +$route['studvw'] = 'studentenverwaltung/index'; +$route['studvw/.*'] = 'studentenverwaltung/index'; +// Old routes +$route['studentenverwaltung'] = 'redirect/301/studvw'; +$route['studentenverwaltung/(:any)'] = 'redirect/301/studvw/stdsem/$1'; +$route['studentenverwaltung/(:any)/(prestudent|student|person|search)/(:any)'] = 'redirect/301/studvw/stdsem/$1/$2/$3'; +$route['studentenverwaltung/(:any)/prestudent/(:any)/tab/:any'] = 'redirect/301/studvw/stdsem/$1/prestudent/$2'; +$route['studentenverwaltung/(:any)/search/(:any)/(:any)'] = 'redirect/301/studvw/stdsem/$1/search/$2/$3'; +$route['studentenverwaltung/(:any)/(:any)'] = 'redirect/301/studvw/stdsem/$1/stg/$2'; + // Studierendenverwaltung List Routes -$route['api/frontend/v1/stv/[sS]tudents/inout'] = 'api/frontend/v1/stv/Students/index'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})'] = 'api/frontend/v1/stv/Students/index'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/inout/1/incoming/1'] = 'api/frontend/v1/stv/students/getIncoming/$1'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/inout/1/outgoing/1'] = 'api/frontend/v1/stv/students/getOutgoing/$1'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/inout/1/shared_studies/1'] = 'api/frontend/v1/stv/students/getGemeinsamestudien/$1'; -// (studiensemester_kurzbz)/inout[/(incoming|outgoing|gemeinsamestudien)] -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/inout'] = 'api/frontend/v1/stv/Students/index'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/inout/incoming'] = 'api/frontend/v1/stv/Students/getIncoming/$1'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/inout/outgoing'] = 'api/frontend/v1/stv/Students/getOutgoing/$1'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/inout/gemeinsamestudien'] = 'api/frontend/v1/stv/Students/getGemeinsamestudien/$1'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1/stdsem/(:any)/:any/1/:any/1/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudents/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1/stdsem/(:any)/:any/1/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudents/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1/stdsem/(:any)/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudents/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1/stdsem/(:any)'] = 'api/frontend/v1/stv/students/getPrestudents/$1/$2'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/prestudent/1'] = 'api/frontend/v1/stv/students/getPrestudents/$1'; -// (studiengang_kz)/prestudent[/(studiensemester_kurzbz)[/(filter)[/(otherfilter)]]] -$route['api/frontend/v1/stv/[sS]tudents/(-?[0-9]+)/prestudent'] = 'api/frontend/v1/stv/Students/getPrestudents/$1'; -$route['api/frontend/v1/stv/[sS]tudents/(-?[0-9]+)/prestudent/([WS]S[0-9]{4})'] = 'api/frontend/v1/stv/Students/getPrestudents/$1/$2'; -$route['api/frontend/v1/stv/[sS]tudents/(-?[0-9]+)/prestudent/([WS]S[0-9]{4})/(:any)'] = 'api/frontend/v1/stv/Students/getPrestudents/$1/$2/$3'; -$route['api/frontend/v1/stv/[sS]tudents/(-?[0-9]+)/prestudent/([WS]S[0-9]{4})/(:any)/(:any)'] = 'api/frontend/v1/stv/Students/getPrestudents/$1/$2/$4'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1/stdsem/(:any)/:any/1/:any/1/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2/$3/$4'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1/stdsem/(:any)/:any/1/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2/$3/$4'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1/stdsem/(:any)/(:any)/1'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2/$3/$4'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1/stdsem/(:any)'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/stv/stdsem/:any/stg/(:any)/orgform/(:any)/prestudent/1'] = 'api/frontend/v1/stv/students/getPrestudentsOrgform/$1/$2'; -// (studiengang_kz)/(orgform)/prestudent[/(studiensemester_kurzbz)[/(filter)[/(otherfilter)]]] -$route['api/frontend/v1/stv/[sS]tudents/(-?[0-9]+)/([A-Z]{2,3})/prestudent'] = 'api/frontend/v1/stv/Students/getPrestudentsOrgform/$1/$2'; -$route['api/frontend/v1/stv/[sS]tudents/(-?[0-9]+)/([A-Z]{2,3})/prestudent/([WS]S[0-9]{4})'] = 'api/frontend/v1/stv/Students/getPrestudentsOrgform/$1/$2/$3'; -$route['api/frontend/v1/stv/[sS]tudents/(-?[0-9]+)/([A-Z]{2,3})/prestudent/([WS]S[0-9]{4})/(:any)'] = 'api/frontend/v1/stv/Students/getPrestudentsOrgform/$1/$2/$3/$4'; -$route['api/frontend/v1/stv/[sS]tudents/(-?[0-9]+)/([A-Z]{2,3})/prestudent/([WS]S[0-9]{4})/(:any)/(:any)'] = 'api/frontend/v1/stv/Students/getPrestudentsOrgform/$1/$2/$3/$5'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/semester/(:any)/verband/(:any)/group/(:any)'] = 'api/frontend/v1/stv/students/getStudents/$1/$2/$3/$4/$5'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/semester/(:any)/verband/(:any)'] = 'api/frontend/v1/stv/students/getStudents/$1/$2/$3/$4'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/semester/(:any)'] = 'api/frontend/v1/stv/students/getStudents/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)'] = 'api/frontend/v1/stv/students/getStudents/$1/$2'; -// (studiensemester_kurzbz)/(studiengang_kz)/(semester)/grp/(gruppe) -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/(:num)/grp/(:any)'] = 'api/frontend/v1/stv/Students/getStudentsSpezialgruppe/$1/$2/$3/$4'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/orgform/(:any)/semester/(:any)/verband/(:any)/group/(:any)'] = 'api/frontend/v1/stv/students/getStudentsOrgform/$1/$2/$3/$4/$5/$6'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/orgform/(:any)/semester/(:any)/verband/(:any)'] = 'api/frontend/v1/stv/students/getStudentsOrgform/$1/$2/$3/$4/$5'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/orgform/(:any)/semester/(:any)'] = 'api/frontend/v1/stv/students/getStudentsOrgform/$1/$2/$3/$4'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/orgform/(:any)'] = 'api/frontend/v1/stv/students/getStudentsOrgform/$1/$2/$3'; -// (studiensemester_kurzbz)/(studiengang_kz)[/(semester)[/(verband)[/(gruppe)]]] -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)'] = 'api/frontend/v1/stv/Students/getStudents/$1/$2'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/(:num)'] = 'api/frontend/v1/stv/Students/getStudents/$1/$2/$3'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/(:num)/(:any)'] = 'api/frontend/v1/stv/Students/getStudents/$1/$2/$3/$4'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/(:num)/(:any)/(:any)'] = 'api/frontend/v1/stv/Students/getStudents/$1/$2/$3/$4/$5'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/semester/(:any)/group/(:any)'] = 'api/frontend/v1/stv/students/getStudentsSpezialgruppe/$1/$2/$3/$4'; -// (studiensemester_kurzbz)/(studiengang_kz)/(orgform)/(semester)/grp/(gruppe) -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/([A-Z]{2,3})/(:num)/grp/(:any)'] = 'api/frontend/v1/stv/Students/getStudentsOrgformSpezialgruppe/$1/$2/$3/$4/$5'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/stg/(:any)/orgform/(:any)/semester/(:any)/group/(:any)'] = 'api/frontend/v1/stv/students/getStudentsOrgformSpezialgruppe/$1/$3/$2/$4/$5'; -// (studiensemester_kurzbz)/(studiengang_kz)/(orgform)[/(semester)[/(verband)[/(gruppe)]]] -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/([A-Z]{2,3})'] = 'api/frontend/v1/stv/Students/getStudentsOrgform/$1/$2/$3'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/([A-Z]{2,3})/(:num)'] = 'api/frontend/v1/stv/Students/getStudentsOrgform/$1/$2/$3/$4'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/([A-Z]{2,3})/(:num)/(:any)'] = 'api/frontend/v1/stv/Students/getStudentsOrgform/$1/$2/$3/$4/$5'; -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/(-?[0-9]+)/([A-Z]{2,3})/(:num)/(:any)/(:any)'] = 'api/frontend/v1/stv/Students/getStudentsOrgform/$1/$2/$3/$4/$5/$6'; +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/prestudent/(:any)'] = 'api/frontend/v1/stv/students/getPrestudent/$1/$2'; + +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/student/(:any)'] = 'api/frontend/v1/stv/students/getStudent/$1/$2'; + +$route['api/frontend/v1/treemenudata/stv/stdsem/(:any)/person/(:any)'] = 'api/frontend/v1/stv/students/getPerson/$1/$2'; + +$route['api/frontend/v1/treemenudata/stv/.*'] = 'api/frontend/v1/stv/students/index'; + +// Lvverwaltung List Routes +$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2'; +$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/semester/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/semester/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/stg/(:any)/orgform/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByStg/$1/$2/$3'; + +$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2'; +$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)/stg/(:any)'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2/$3'; +$route['api/frontend/v1/treemenudata/lvverwaltung/stdsem/(:any)/emp/(:any)/stg/(:any)/.*'] = 'api/frontend/v1/Lehrveranstaltung/getByEmp/$1/$2/$3'; -// // (studiensemester_kurzbz)/uid/(uid) -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/uid/(:any)'] = 'api/frontend/v1/stv/Students/getStudent/$1/$2'; -// // (studiensemester_kurzbz)/prestudent/(prestudent_id) -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/prestudent/(:num)'] = 'api/frontend/v1/stv/Students/getPrestudent/$1/$2'; -// // (studiensemester_kurzbz)/person/(person_id) -$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/person/(:num)'] = 'api/frontend/v1/stv/Students/getPerson/$1/$2'; // load routes from extensions, also look for environment-specific configs $subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions']; diff --git a/application/config/studierendenantrag.php b/application/config/studierendenantrag.php index ef2098aca..9d1e7a1c8 100644 --- a/application/config/studierendenantrag.php +++ b/application/config/studierendenantrag.php @@ -197,3 +197,11 @@ $config['unterbrechung_enabled'] = true; */ $config['wiederholung_enabled'] = true; +/** + * Modal with Infotext of TLC + * if true, there will be shown an info modal in the studentsView after the first button ABMELDUNG + * + * @var boolean + */ +$config['showInfoModalAbmeldung'] = true; + diff --git a/application/config/stv.php b/application/config/stv.php index 34a30a96e..b12153eda 100644 --- a/application/config/stv.php +++ b/application/config/stv.php @@ -1,6 +1,27 @@ [ + 'js' => 'path/to/snippet.js', // tabulator config snippet (eg: return { name: 'Name', field: 'fieldname' }; ) + 'default' => "SQL SELECT statement for value", + 'joins' => [ + // additional joins needed for the SELECT statement above + [ + 'tablename', + 'join condition', // single fieldname for USING or full condition for ON + 'LEFT|RIGHT', // optional - join type + 'after_xxx|before_xxx|end' // optional - position in query + ] + // ... + ] + ] +]; +*/ + $config['tabs'] = [ 'details' => [ @@ -60,7 +81,13 @@ $config['tabs'] = ], 'notes' => [ //if true, the count of Messages will be shown in the header of the Tab Messages - 'showCountNotes' => true + 'showCountNotes' => true, + + //if true: section with following fields will be displayed: 'verfasser', 'bearbeiter', 'von', 'bis' and 'erledigt' + 'showErweitert' => false, + + //notizlayout: choose of "classicFas", "twoColumnsFormLeft", twoColumnsFormRight, popupModal" + 'notizLayout' => 'popupModal' ], 'combinePeople' => [ //multitab should only be shown with this length of selection @@ -133,13 +160,25 @@ $config['students_tab_order'] = [ $config['stv_prestudent_tags'] = [ 'prioone' => ['readonly' => false], - 'priotwo' => ['readonly' => true], + 'priotwo' => ['readonly' => false], 'hinweis' => ['readonly' => false], - 'hinweis_assistenz' => ['readonly' => true], - 'hinweis_kf' => ['readonly' => true], + 'hinweis_assistenz' => ['readonly' => false], + 'hinweis_kf' => ['readonly' => false], 'hinweis_lehrende' => ['readonly' => false], - 'hinweis_stg_kf' => ['readonly' => true], - 'finished_stg' => ['readonly' => true], - 'finished_kf' => ['readonly' => true], - 'inwork_kf' => ['readonly' => true], + 'hinweis_stg_kf' => ['readonly' => false], + 'finished_stg' => ['readonly' => false], + 'finished_kf' => ['readonly' => false], + 'inwork_kf' => ['readonly' => false], + 'dd_auto' => ['readonly' => true], + 'wh_auto' => ['readonly' => true], + 'prewh_auto' => ['readonly' => true], + 'out_auto' => ['readonly' => true], + 'zgv_auto' => ['readonly' => true], + 'unterbrecher_auto' => ['readonly' => true], + 'stbtr_erh_auto' => ['readonly' => true], + 'jgv_auto' => ['readonly' => true], + 'in_auto' => ['readonly' => true], + 'preabbr_auto' => ['readonly' => true], ]; + +$config['userAutomatedTags'] = 'sftest'; diff --git a/public/js/api/factory/stv/verband.js b/application/config/treemenu.php similarity index 60% rename from public/js/api/factory/stv/verband.js rename to application/config/treemenu.php index 114931e4c..72a02947b 100644 --- a/public/js/api/factory/stv/verband.js +++ b/application/config/treemenu.php @@ -1,5 +1,7 @@ +. */ -export default { - get(path) { - let url = 'api/frontend/v1/stv/verband'; - if (path) - url += '/' + path; - return { - method: 'get', - url - }; - }, - favorites: { - get() { - return { - method: 'get', - url: 'api/frontend/v1/stv/favorites' - }; - }, - set(favorites) { - return { - method: 'post', - url: 'api/frontend/v1/stv/favorites/set', - params: { favorites } - }; - } - } -}; \ No newline at end of file +if (!defined('BASEPATH')) exit('No direct script access allowed'); + +$config['stv'] = [ + "library" => "treemenu/StudVwLib", + "permissions" => ["admin:r", "assistenz:r"] +]; +$config['lvv'] = [ + "library" => "treemenu/LvVwLib", + "permissions" => ["admin:r", "assistenz:r"] +]; diff --git a/application/config/treemenu/custom/lvvw.php b/application/config/treemenu/custom/lvvw.php new file mode 100644 index 000000000..977c0459e --- /dev/null +++ b/application/config/treemenu/custom/lvvw.php @@ -0,0 +1,32 @@ +. + */ + +if (!defined('BASEPATH')) exit('No direct script access allowed'); + +$config['root'] = [ + 'stg' => [ + 'semester' => [ + 'group', + 'verband' => [ + 'group' + ] + ], + 'orgform' + ] +]; diff --git a/application/config/treemenu/stg_base.php b/application/config/treemenu/stg_base.php new file mode 100644 index 000000000..fcb9748b7 --- /dev/null +++ b/application/config/treemenu/stg_base.php @@ -0,0 +1,65 @@ +. + */ + +if (!defined('BASEPATH')) exit('No direct script access allowed'); + +$config['root'] = [ + 'stg' => [ + 'prestudent' => [ + 'stdsem' => [ + 'interessenten' => [ + 'bewerbungnichtabgeschickt', + 'bewerbungabgeschickt', + 'zgv', + 'statusbestaetigt' => [ + 'statusbestaetigtrtnichtangemeldet', + 'statusbestaetigtrtangemeldet' + ], + 'reihungstestnichtangemeldet', + 'reihungstestangemeldet' + ], + 'bewerber' => [ + 'bewerberrtnichtangemeldet', + 'bewerberrtangemeldet' => [ + 'bewerberrtangemeldetteilgenommen', + 'bewerberrtangemeldetnichtteilgenommen' + ] + ], + 'aufgenommen', + 'warteliste', + 'absage', + 'incoming' + ] + ], + 'semester' => [ + 'group', + 'verband' => [ + 'group' + ] + ] + ], + 'inout' => [ + 'incoming', + 'outgoing', + 'shared_studies' + ] +]; + +// copy stg tree into stg/orgforms +$config['root']['stg']['orgform'] = $config['root']['stg']; diff --git a/application/controllers/Redirect.php b/application/controllers/Redirect.php new file mode 100644 index 000000000..f4a9f4cd7 --- /dev/null +++ b/application/controllers/Redirect.php @@ -0,0 +1,44 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller redirects urls to another location. This is mainly used + * with the $routes config + */ +class Redirect extends CI_Controller +{ + public function __construct() + { + parent::__construct(); + } + + /** + * Main function to handle all redirects. + * + * @param string $method - the redirect code (30x) + * @param array $params - the url to redirect to + * + * @return void + */ + public function _remap($method, $params) + { + redirect($params, 'location', $method); + } +} diff --git a/application/controllers/api/frontend/v1/stv/Favorites.php b/application/controllers/api/frontend/v1/Favorites.php similarity index 78% rename from application/controllers/api/frontend/v1/stv/Favorites.php rename to application/controllers/api/frontend/v1/Favorites.php index 951eb01a4..787163abb 100644 --- a/application/controllers/api/frontend/v1/stv/Favorites.php +++ b/application/controllers/api/frontend/v1/Favorites.php @@ -20,8 +20,8 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); /** * This controller operates between (interface) the JS (GUI) and the back-end - * Provides data to the ajax get calls about favorite verbände - * Listens to ajax post calls to change the favorite verbände data + * Provides data to the ajax get calls about favorite treemenu entries + * Listens to ajax post calls to change the favorite treemenu entries * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON */ class Favorites extends FHCAPI_Controller @@ -29,7 +29,7 @@ class Favorites extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'index' => self::PERM_LOGGED, + 'get' => self::PERM_LOGGED, 'set' => self::PERM_LOGGED ]); @@ -37,19 +37,19 @@ class Favorites extends FHCAPI_Controller $this->load->model('system/Variable_model', 'VariableModel'); } - public function index() + public function get($app) { - $result = $this->VariableModel->getVariables(getAuthUID(), ['stv_favorites']); + $result = $this->VariableModel->getVariables(getAuthUID(), [$app . '_favorites']); $data = $this->getDataOrTerminateWithError($result); if (!$data) $this->terminateWithSuccess(null); else - $this->terminateWithSuccess($data['stv_favorites'] ?? null); + $this->terminateWithSuccess($data[$app . '_favorites'] ?? null); } - public function set() + public function set($app) { $this->load->library('form_validation'); @@ -60,7 +60,7 @@ class Favorites extends FHCAPI_Controller $favorites = $this->input->post('favorites'); - $result = $this->VariableModel->setVariable(getAuthUID(), 'stv_favorites', $favorites); + $result = $this->VariableModel->setVariable(getAuthUID(), $app . '_favorites', $favorites); $this->getDataOrTerminateWithError($result); diff --git a/application/controllers/api/frontend/v1/Lehrveranstaltung.php b/application/controllers/api/frontend/v1/Lehrveranstaltung.php index 935602391..66bda9ebf 100644 --- a/application/controllers/api/frontend/v1/Lehrveranstaltung.php +++ b/application/controllers/api/frontend/v1/Lehrveranstaltung.php @@ -56,7 +56,9 @@ class Lehrveranstaltung extends FHCAPI_Controller $studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz); - $lehrveranstaltungen = $this->_ci->LehreinheitModel->getLvsByEmployee($mitarbeiter_uid, $studiensemester_kurzbz, $stg_kz); + $studiengang_kz = $this->getStudiengangKz($stg_kz); + + $lehrveranstaltungen = $this->_ci->LehreinheitModel->getLvsByEmployee($mitarbeiter_uid, $studiensemester_kurzbz, $studiengang_kz); $lehrveranstaltungen_data = $this->getDataOrTerminateWithError($lehrveranstaltungen); $tree = []; @@ -75,15 +77,17 @@ class Lehrveranstaltung extends FHCAPI_Controller $this->terminateWithSuccess($tree); } - public function getByStg($studiensemester_kurzbz = null, $studiengang_kz = null, $semester = null) + public function getByStg($studiensemester_kurzbz = null, $stg_kz = null, $semester = null) { + $studiengang_kz = $this->getStudiengangKz($stg_kz); + if (is_null($studiengang_kz) || !preg_match("/^-?[1-9][0-9]*$/", (string)$studiengang_kz)) $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); $verband = null; if (!is_null($semester) && !is_numeric($semester)) { - $verband = $semester; + $verband = $this->getOrgformKurzbz($semester); $semester = null; } @@ -260,18 +264,53 @@ class Lehrveranstaltung extends FHCAPI_Controller } } + protected function getStudiengangKz($studiengang_typ_kurzbz) + { + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $result = $this->StudiengangModel->loadWhere([ + 'LOWER(CONCAT(typ, kurzbz)) =' => strtolower($studiengang_typ_kurzbz) + ]); + + $data = $this->getDataOrTerminateWithError($result); + + if (!$data) + return null; + + return current($data)->studiengang_kz; + } + private function getStudiensemesterKurzbz($studiensemester_kurzbz = null) { if (!is_null($studiensemester_kurzbz)) { - $studiensemester_result = $this->_ci->StudiensemesterModel->load($studiensemester_kurzbz); + $studiensemester_result = $this->_ci->StudiensemesterModel->loadWhere([ + 'LOWER(studiensemester_kurzbz)' => strtolower($studiensemester_kurzbz) + ]); if (isError($studiensemester_result) || !hasData($studiensemester_result)) - $this->terminateWithError( $this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); return getData($studiensemester_result)[0]->studiensemester_kurzbz; } $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); } + + protected function getOrgformKurzbz($orgform_kurzbz) + { + if ($orgform_kurzbz === null) + return null; + + $this->load->model('codex/Orgform_model', 'OrgformModel'); + $result = $this->OrgformModel->loadWhere([ + 'LOWER(orgform_kurzbz) =' => strtolower($orgform_kurzbz) + ]); + + $data = $this->getDataOrTerminateWithError($result); + + if (!$data) + return $orgform_kurzbz; + + return current($data)->orgform_kurzbz; + } } diff --git a/application/controllers/api/frontend/v1/LvPlan.php b/application/controllers/api/frontend/v1/LvPlan.php index 2a900e92d..36054003c 100644 --- a/application/controllers/api/frontend/v1/LvPlan.php +++ b/application/controllers/api/frontend/v1/LvPlan.php @@ -49,6 +49,8 @@ class LvPlan extends FHCAPI_Controller 'getLehrverband' => self::PERM_LOGGED, 'permissionOtherLvPlan' => self::PERM_LOGGED, 'compactibleEventTypes' => self::PERM_LOGGED, + 'getLeEvents' => self::PERM_LOGGED, + 'getLvEvents' => self::PERM_LOGGED, ]); $this->load->library('LogLib'); @@ -60,7 +62,13 @@ class LvPlan extends FHCAPI_Controller 'dbExecuteUser' => 'RESTful API' )); - $this->load->library('form_validation'); + $this->load->library('form_validation'); + $this->load->library('PhrasesLib'); + $this->loadPhrases( + array( + 'ui' + ) + ); } //------------------------------------------------------------------------------------------------------------------ @@ -196,6 +204,38 @@ class LvPlan extends FHCAPI_Controller )); } + public function getLeEvents($le_id = null, $start_date = null, $end_date = null, $stundenplan = 'stundenplandev') + { + + if (is_null($le_id) || is_null($start_date) || is_null($end_date)) + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + if ($stundenplan !== 'stundenplandev' && $stundenplan !== 'stundenplan') + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + $this->load->library('StundenplanLib'); + + $result = $this->stundenplanlib->getEventsByLE($le_id, $start_date, $end_date, $stundenplan); + $lvplanEvents = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($lvplanEvents); + } + + public function getLvEvents($lv_id = null, $start_date = null, $end_date = null, $stundenplan = 'stundenplandev') + { + if (is_null($lv_id) || is_null($start_date) || is_null($end_date)) + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + if ($stundenplan !== 'stundenplandev' && $stundenplan !== 'stundenplan') + $this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + $this->load->library('StundenplanLib'); + + $result = $this->stundenplanlib->getEventsByLV($lv_id, $start_date, $end_date, $stundenplan); + + $this->terminateWithSuccess(hasData($result) ? getData($result) : []); + } + //TODO: delete this function if we don't use the old calendar export endpoints anymore public function studiensemesterDateInterval($date) { diff --git a/application/controllers/api/frontend/v1/OtherLvPlan.php b/application/controllers/api/frontend/v1/OtherLvPlan.php index cad654f12..af075fbc3 100644 --- a/application/controllers/api/frontend/v1/OtherLvPlan.php +++ b/application/controllers/api/frontend/v1/OtherLvPlan.php @@ -17,60 +17,59 @@ */ if (!defined('BASEPATH')) - exit('No direct script access allowed'); + exit('No direct script access allowed'); class OtherLvPlan extends FHCAPI_Controller { - /** - * Object initialization - */ - public function __construct() - { - parent::__construct([ - 'otherLvPlanViewData' => ['basis/other_lv_plan:r'], - ]); + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'otherLvPlanViewData' => ['basis/other_lv_plan:r'], + ]); - $this->load->model('ressource/mitarbeiter_model', 'MitarbeiterModel'); - $this->load->model('person/Benutzer_model', 'BenutzerModel'); + $this->load->model('ressource/mitarbeiter_model', 'MitarbeiterModel'); + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + $this->load->library('ProfilLib'); - } + } - //------------------------------------------------------------------------------------------------------------------ - // Public methods + //------------------------------------------------------------------------------------------------------------------ + // Public methods - /** - * retrieves viewData for other lv plan view - * @access public - * @param $uid the userID for which the other lv plan is being viewed - */ - public function otherLvPlanViewData($uid) - { - $isMitarbeiterResult = $this->MitarbeiterModel->isMitarbeiter($uid); - $isMitarbeiter = getData($isMitarbeiterResult); - $isStudent = !$isMitarbeiter; + /** + * retrieves viewData for other lv plan view + * @access public + * @param $uid the userID for which the other lv plan is being viewed + */ + public function otherLvPlanViewData($uid) + { + $isMitarbeiterResult = $this->MitarbeiterModel->isMitarbeiter($uid); + $isMitarbeiter = getData($isMitarbeiterResult); + $isStudent = !$isMitarbeiter; - $this->BenutzerModel->addSelect(["foto", "vorname", "nachname"]); - $this->BenutzerModel->addJoin("tbl_person", "person_id"); - $personResult = $this->BenutzerModel->load([$uid]); - $person = hasData($personResult) ? getData($personResult) : null; + $resProfileData = $this->profillib->getView($uid); + $profileData = hasData($resProfileData) ? getData($resProfileData) : null; - $viewData = [ - "user_data" => [ - "username" => $uid, - "is_student" => $isStudent, - "is_mitarbeiter" => $isMitarbeiter, - "foto" => $person[0]->foto, - "vorname" => $person[0]->vorname, - "nachname" => $person[0]->nachname, - ], - ]; + $viewData = [ + "user_data" => [ + "username" => $uid, + "is_student" => $isStudent, + "is_mitarbeiter" => $isMitarbeiter, + "foto" => $profileData->data->foto, + "vorname" => $profileData->data->vorname, + "nachname" => $profileData->data->nachname, + ], + ]; - $this->terminateWithSuccess($viewData); - } + $this->terminateWithSuccess($viewData); + } - // ----------------------------------------------------------------------------------------------------------------- - // Private methods + // ----------------------------------------------------------------------------------------------------------------- + // Private methods } diff --git a/application/controllers/api/frontend/v1/TabulatorPresets.php b/application/controllers/api/frontend/v1/TabulatorPresets.php new file mode 100644 index 000000000..cb8e23ade --- /dev/null +++ b/application/controllers/api/frontend/v1/TabulatorPresets.php @@ -0,0 +1,162 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the PhrasesLib (back-end) + * Provides data to the ajax get calls about the Phrasen plugin + * This controller works with JSON calls on the HTTP GET and the output is always JSON + */ +class TabulatorPresets extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'getTabulatorPresets' => self::PERM_LOGGED, + 'createTabulatorPreset' => self::PERM_LOGGED, + 'updateTabulatorPreset' => self::PERM_LOGGED, + 'deleteTabulatorPreset' => self::PERM_LOGGED, + ]); + + $this->load->model('tabulator/Tabulator_preset_model', 'TabulatorPresetModel'); + + $this->loadPhrases([ + 'global', + 'tabulator_presets' + ]); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + public function getTabulatorPresets() + { + $tableName = $this->input->get("tableName"); + if (!$tableName) { + $this->terminateWithError(error($this->p->t("tabulator_presets", "invalid_params_err_msg"))); + } + + $uid = getAuthUID(); + $presets = $this->TabulatorPresetModel->getTabulatorPresetsByUserAndTable($uid, $tableName); + $presetsData = $this->getDataOrTerminateWithError($presets) ?? []; + + $this->terminateWithSuccess($presetsData); + } + + public function createTabulatorPreset() + { + $tableName = $this->input->post("tableName"); + $presetName = $this->input->post("presetName"); + $preset = $this->input->post("preset"); + + if (!$tableName || !$presetName || !$preset) { + $this->terminateWithError($this->p->t("tabulator_presets", "invalid_params_err_msg"), "general", 400); + } + + $uid = getAuthUID(); + + $existingPresetsResult = $this->TabulatorPresetModel->getTabulatorPresetsByUserAndTable($uid, $tableName); + $existingPresets = $this->getDataOrTerminateWithError($existingPresetsResult) ?? []; + + if (count($existingPresets) > 19) { + $this->terminateWithError($this->p->t("tabulator_presets", "max_presets_count_err_msg"), "general", 409); + } + + $existingPresetNames = array_map(function($presetInfo) { + return $presetInfo->preset_name; + }, $existingPresets); + + if (in_array($presetName, $existingPresetNames)) { + $this->terminateWithError($this->p->t("tabulator_presets", "preset_name_duplicate_err_msg"), "general", 409); + } + + $presetCreationResult = $this->TabulatorPresetModel->createTabulatorPreset($uid, $tableName, $presetName, $preset); + + if (isError($presetCreationResult)) { + $this->terminateWithError($presetCreationResult->retval); + } + + $newPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetCreationResult->retval); + $newPresetArray = $this->getDataOrTerminateWithError($newPresetData) ?? [null]; + $newPreset = $newPresetArray[0]; + + $this->terminateWithSuccess($newPreset); + } + + public function updateTabulatorPreset() + { + $presetId = $this->input->post("presetId"); + $preset = $this->input->post("preset"); + + if (!$presetId || !$preset) { + $this->terminateWithError($this->p->t("tabulator_presets", "invalid_params_err_msg"), "general", 400); + } + + $existingPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId); + $existingPresetArray = $this->getDataOrTerminateWithError($existingPresetData) ?? [null]; + $existingPreset = $existingPresetArray[0]; + if (!$existingPreset) { + $this->terminateWithError($this->p->t("tabulator_presets", "preset_not_found_err_msg"), "general", 404); + } + + $uid = getAuthUID(); + if ($existingPreset->benutzer_uid !== $uid) { + $this->terminateWithError($this->p->t("tabulator_presets", "preset_not_own_update_err_msg"), "general", 403); + } + + $presetUpdateResult = $this->TabulatorPresetModel->updateTabulatorPreset($presetId, $preset); + if (isError($presetUpdateResult)) { + $this->terminateWithError($presetUpdateResult->retval); + } + + $updatedPresetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId); + $updatedPresetArray = $this->getDataOrTerminateWithError($updatedPresetData) ?? [null]; + $updatedPreset = $updatedPresetArray[0]; + + $this->terminateWithSuccess($updatedPreset); + } + + public function deleteTabulatorPreset() + { + $presetId = $this->input->post("presetId"); + if (!$presetId) { + $this->terminateWithError($this->p->t("tabulator_presets", "invalid_params_err_msg"), "general", 400); + } + + $presetData = $this->TabulatorPresetModel->getTabulatorPreset($presetId); + $presetArray = $this->getDataOrTerminateWithError($presetData) ?? [null]; + $preset = $presetArray[0]; + if (!$preset) { + $this->terminateWithError($this->p->t("tabulator_presets", "preset_not_found_err_msg"), "general", 404); + } + + $uid = getAuthUID(); + if ($preset->benutzer_uid !== $uid) { + $this->terminateWithError($this->p->t("tabulator_presets", "preset_not_own_delete_err_msg"), "general", 403); + } + + $presetDeletionResult = $this->TabulatorPresetModel->deleteTabulatorPreset($presetId); + if (isError($presetDeletionResult)) { + $this->terminateWithError($presetDeletionResult->retval); + } + + $this->terminateWithSuccess(); + } + +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/Treemenu.php b/application/controllers/api/frontend/v1/Treemenu.php new file mode 100644 index 000000000..e25610370 --- /dev/null +++ b/application/controllers/api/frontend/v1/Treemenu.php @@ -0,0 +1,148 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +use \ReflectionMethod as ReflectionMethod; +use \ReflectionFunction as ReflectionFunction; + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about menues + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + */ +class Treemenu extends FHCAPI_Controller +{ + public function __construct() + { + // Load Router + $router = load_class('Router'); + + // Load Config + $config = load_class('Config'); + $config->load('treemenu'); + + // Get Treemenu name & config + $name = $router->method; + $conf = $config->item($name); + + // Get Permissions from config + $permissions = $conf['permissions'] ?? self::PERM_ANONYMOUS; + + // Call constructor + parent::__construct([ + $name => $permissions + ]); + + // Load Library + $libraryName = $conf['library'] ?? null; + $this->load->library($libraryName, null, 'treemenulib'); + } + + /** + * @param string $method + * @param array $params (optional) + * + * @return void + */ + public function _remap($method, $params = []) + { + if (!$this->treemenulib) + show_404(); + + $this->addMeta('ci_method', $method); + $this->addMeta('ci_params', $params); + + $params = $this->uri->uri_to_assoc(6); // 6 = start of $params + $config = $this->treemenulib->config; + + foreach (array_keys($params) as $key) { + if (!isset($config[$key]) || !$config[$key]) + show_404(); + + $config = $config[$key]; + } + + // Prepare aliases + $argumentaliases = []; + if (property_exists($this->treemenulib, 'path_to_argument')) + $argumentaliases = array_flip($this->treemenulib->path_to_argument); + $methodaliases = []; + if (property_exists($this->treemenulib, 'redirect_method')) + $methodaliases = $this->treemenulib->redirect_method; + + $items = []; + foreach ($config as $uripart => $children) { + if (!is_array($children)) { + $uripart = $children; + $children = []; + } + + $methodName = $uripart; + if (isset($methodaliases[$methodName])) { + $methodName = $methodaliases[$methodName]; + } elseif (strpbrk($methodName, ' _-') !== false) { + $methodName = lcfirst(str_replace([' ', '_', '-'], '', ucwords($methodName, " \t\r\n\f\v_-"))); + } + + // Prepare arguments + if (is_callable($methodName, false, $name)) { + if (is_string($methodName) && is_callable([$this->treemenulib, $methodName])) + $callable = [$this->treemenulib, $methodName]; + else + $callable = $methodName; + } else { + $callable = [$this->treemenulib, $methodName]; + } + + if (is_array($callable)) + $reflection = new ReflectionMethod($callable[0], $callable[1]); + elseif (is_string($callable) && strstr($callable, '::')) + $reflection = new ReflectionMethod($callable); + elseif ($name != 'Closure::__invoke' && strstr($name, '::__invoke')) + $reflection = new ReflectionMethod($callable, '__invoke'); + else + $reflection = new ReflectionFunction($callable); + + $arguments = []; + foreach ($reflection->getParameters() as $arg) { + if ($arg->name == 'path_template') { + $path = $params; + $path[$uripart] = '%s'; + $arguments[] = $this->uri->assoc_to_uri($path); + } elseif ($arg->name == 'has_children') { + $arguments[] = $children; + } elseif ($arg->name == 'original_method') { + $arguments[] = $uripart; + } elseif (isset($argumentaliases[$arg->name]) && isset($params[$argumentaliases[$arg->name]])) { + $arguments[] = $params[$argumentaliases[$arg->name]]; + } elseif (isset($params[$arg->name])) { + $arguments[] = $params[$arg->name]; + } else { + $arguments[] = null; + } + } + + // Call function from lib + $items = array_merge($items, call_user_func_array($callable, $arguments)); + } + + $this->terminateWithSuccess($items); + } +} diff --git a/application/controllers/api/frontend/v1/betriebsmittel/BetriebsmittelP.php b/application/controllers/api/frontend/v1/betriebsmittel/BetriebsmittelP.php index 7486f44f0..0ac3c8bca 100644 --- a/application/controllers/api/frontend/v1/betriebsmittel/BetriebsmittelP.php +++ b/application/controllers/api/frontend/v1/betriebsmittel/BetriebsmittelP.php @@ -259,7 +259,7 @@ class BetriebsmittelP extends FHCAPI_Controller $kaution = $this->input->post('kaution'); if($kaution) $kaution = str_replace(',', '.', $kaution); $ausgegebenam = $this->input->post('ausgegebenam'); - $retouram = $this->input->post('retouram'); + $retouram = $this->input->post('retouram') == "" ? null : $this->input->post('retouram'); // NOTE(chris): transform_kartennummer if ($betriebsmitteltyp == 'Zutrittskarte' && $nummer) diff --git a/application/controllers/api/frontend/v1/detailheader/Detailheader.php b/application/controllers/api/frontend/v1/detailheader/Detailheader.php index ada10c5b6..89e9821eb 100644 --- a/application/controllers/api/frontend/v1/detailheader/Detailheader.php +++ b/application/controllers/api/frontend/v1/detailheader/Detailheader.php @@ -9,9 +9,10 @@ class Detailheader extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'getHeader' => ['vertrag/mitarbeiter:r'], - 'getPersonAbteilung' => ['vertrag/mitarbeiter:r'], - 'getLeitungOrg' => ['vertrag/mitarbeiter:r'], + 'getHeader' => ['vertrag/mitarbeiter:r', 'basis/mitarbeiter:r', 'admin:r', 'assistenz:r'], + 'getPersonAbteilung' => ['vertrag/mitarbeiter:r', 'basis/mitarbeiter:r', 'admin:r', 'assistenz:r'], + 'getLeitungOrg' => ['vertrag/mitarbeiter:r', 'basis/mitarbeiter:r', 'admin:r', 'assistenz:r'], + 'getSemesterStati' => ['admin:r', 'assistenz:r'], ]); } @@ -48,6 +49,17 @@ class Detailheader extends FHCAPI_Controller $this->terminateWithSuccess(current($data)); } + public function getSemesterStati($prestudent_id) + { + $this->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel'); + + $result = $this->PrestudentstatusModel->getAllPrestudentstatiWithStudiensemester($prestudent_id); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + } diff --git a/application/controllers/api/frontend/v1/funktionen/Funktionen.php b/application/controllers/api/frontend/v1/funktionen/Funktionen.php index b2787072b..a3efcdb51 100644 --- a/application/controllers/api/frontend/v1/funktionen/Funktionen.php +++ b/application/controllers/api/frontend/v1/funktionen/Funktionen.php @@ -294,6 +294,10 @@ class Funktionen extends FHCAPI_Controller { $this->terminateWithValidationErrors($this->form_validation->error_array()); } + if($datum_von == '') + $datum_von = null; + if($datum_bis == '') + $datum_bis = null; $result = $this->BenutzerfunktionModel->update( [ diff --git a/application/controllers/api/frontend/v1/lv/Config.php b/application/controllers/api/frontend/v1/lv/Config.php new file mode 100644 index 000000000..4f39c6b8b --- /dev/null +++ b/application/controllers/api/frontend/v1/lv/Config.php @@ -0,0 +1,86 @@ + ['admin:r', 'assistenz:r'], + 'set' => ['admin:r', 'assistenz:r'], + ]); + + $this->_ci = &get_instance(); + $this->_setAuthUID(); + + $this->loadPhrases([ + 'lehre' + ]); + + $this->_ci->load->library('VariableLib', ['uid' => $this->_uid]); + $this->_ci->load->library('PermissionLib'); + + } + + + public function get() + { + if (!($this->permissionlib->isBerechtigt('basis/tempus')) && !($this->permissionlib->isBerechtigt('lv-plan'))) + $this->terminateWithSuccess([]); + + $ignore_kollision = $this->_ci->variablelib->getVar('ignore_kollision'); + $ignore_zeitsperre = $this->_ci->variablelib->getVar('ignore_zeitsperre'); + $ignore_reservierung = $this->_ci->variablelib->getVar('ignore_reservierung'); + + $config['ignore_kollision'] = [ + "type" => "checkbox", + "label" => 'ignore_kollision', + "value" => $ignore_kollision, + ]; + + $config['ignore_zeitsperre'] = [ + "type" => "checkbox", + "label" => 'ignore_zeitsperre', + "value" => $ignore_zeitsperre, + ]; + + $config['ignore_reservierung'] = [ + "type" => "checkbox", + "label" => 'ignore_reservierung', + "value" => $ignore_reservierung, + ]; + + $this->terminateWithSuccess($config); + } + public function set() + { + if (!($this->permissionlib->isBerechtigt('basis/tempus')) && !($this->permissionlib->isBerechtigt('lv-plan'))) + $this->terminateWithSuccess([]); + + $this->load->model('system/Variable_model', 'VariableModel'); + + foreach (['ignore_kollision','ignore_zeitsperre','ignore_reservierung'] as $variable) + { + if ($this->_ci->input->post($variable) !== null) + { + $this->VariableModel->update(array('uid' => $this->_uid, 'name' => $variable), array('wert' => $this->input->post($variable))); + + } + } + + $this->terminateWithSuccess(); + } + + private function _setAuthUID() + { + $this->_uid = getAuthUID(); + + if (!$this->_uid) + show_error('User authentification failed'); + } +} diff --git a/application/controllers/api/frontend/v1/lv/Favorites.php b/application/controllers/api/frontend/v1/lv/Favorites.php deleted file mode 100644 index 080a4ec6e..000000000 --- a/application/controllers/api/frontend/v1/lv/Favorites.php +++ /dev/null @@ -1,47 +0,0 @@ - self::PERM_LOGGED, - 'set' => self::PERM_LOGGED - ]); - - // Load models - $this->load->model('system/Variable_model', 'VariableModel'); - } - - public function index() - { - $result = $this->VariableModel->getVariables(getAuthUID(), ['lv_favorites']); - - $data = $this->getDataOrTerminateWithError($result); - - if (!$data) - $this->terminateWithSuccess(null); - else - $this->terminateWithSuccess(isset($data['lv_favorites']) ? $data['lv_favorites'] : null); - } - - public function set() - { - $this->load->library('form_validation'); - - $this->form_validation->set_rules('favorites', 'Favorites', 'required'); - - if (!$this->form_validation->run()) - $this->terminateWithValidationErrors($this->form_validation->error_array()); - - $favorites = $this->input->post('favorites'); - - $result = $this->VariableModel->setVariable(getAuthUID(), 'lv_favorites', $favorites); - - $this->getDataOrTerminateWithError($result); - - $this->terminateWithSuccess(true); - } -} diff --git a/application/controllers/api/frontend/v1/lv/Gruppe.php b/application/controllers/api/frontend/v1/lv/Gruppe.php index daebe8a61..f60d3a99a 100644 --- a/application/controllers/api/frontend/v1/lv/Gruppe.php +++ b/application/controllers/api/frontend/v1/lv/Gruppe.php @@ -15,6 +15,7 @@ class Gruppe extends FHCAPI_Controller 'getBenutzerSearch' => ['admin:r', 'assistenz:r'], 'getAllSearch' => ['admin:r', 'assistenz:r'], 'getByLehreinheit' => ['admin:r', 'assistenz:r'], + 'getGruppe' => ['admin:r', 'assistenz:r'], ]); $this->_ci = &get_instance(); @@ -67,7 +68,7 @@ class Gruppe extends FHCAPI_Controller $this->checkPermission($lehreinheit_id); - $result = $this->_ci->LehreinheitgruppeModel->addGroup($lehreinheit_id, $gid, !($lehrverband === 'false')); + $result = $this->_ci->LehreinheitgruppeModel->addGroup($lehreinheit_id, $gid, $lehrverband === true || $lehrverband === 'true'); if (isError($result)) $this->terminateWithError(getError($result)); @@ -75,6 +76,71 @@ class Gruppe extends FHCAPI_Controller $this->terminateWithSuccess($result); } + public function getGruppe() + { + $lehrverband = $this->input->post('lehrverband'); + + $gruppen_result = array(); + + if ($lehrverband === false) + { + $gruppen_result = $this->_ci->GruppeModel->loadWhere(array( + 'studiengang_kz' => $this->input->post('stg_kz'), + 'gruppe_kurzbz' => $this->input->post('gruppe_kurzbz'), + 'aktiv' => true + )); + } + else if ($lehrverband === true) + { + + if (!isEmptyString($this->input->post('verband'))) + { + $this->LehrverbandModel->db->where('verband', $this->input->post('verband')); + } + else + { + $this->LehrverbandModel->db->group_start(); + $this->LehrverbandModel->db->where("trim(verband) = ''"); + $this->LehrverbandModel->db->or_where("verband IS NULL"); + $this->LehrverbandModel->db->group_end(); + } + + if (!isEmptyString($this->input->post('gruppe'))) + { + $this->LehrverbandModel->db->where('gruppe', $this->input->post('gruppe')); + } + else + { + $this->LehrverbandModel->db->group_start(); + $this->LehrverbandModel->db->where("trim(gruppe) = ''"); + $this->LehrverbandModel->db->or_where("gruppe IS NULL"); + $this->LehrverbandModel->db->group_end(); + } + + if (!isEmptyString((string)$this->input->post('semester'))) + { + $this->LehrverbandModel->db->where('semester', $this->input->post('semester')); + } + else + { + + $this->LehrverbandModel->db->group_start(); + $this->LehrverbandModel->db->where("semester = ''"); + $this->LehrverbandModel->db->or_where("semester IS NULL"); + $this->LehrverbandModel->db->group_end(); + } + + $gruppen_result = $this->LehrverbandModel->loadWhere(array('studiengang_kz' => $this->input->post('stg_kz'), 'aktiv' => true)); + } + + if (!hasData($gruppen_result)) + return $this->terminateWithError('No group found'); + + $gruppen_array = getData($gruppen_result)[0]; + + $this->terminateWithSuccess($gruppen_array->gid); + } + public function getByLehreinheit($lehreinheit_id = null) { if (is_null($lehreinheit_id) || !ctype_digit((string)$lehreinheit_id)) diff --git a/application/controllers/api/frontend/v1/lv/Lektor.php b/application/controllers/api/frontend/v1/lv/Lektor.php index cce7f6e8b..64cfd3f1c 100644 --- a/application/controllers/api/frontend/v1/lv/Lektor.php +++ b/application/controllers/api/frontend/v1/lv/Lektor.php @@ -134,9 +134,14 @@ class Lektor extends FHCAPI_Controller $this->terminateWithValidationErrors($this->form_validation->error_array()); } - if (isset($formData['semesterstunden']) && (!is_numeric($formData['semesterstunden']) || $formData['semesterstunden'] === '')) + $nullable_fields = array('semesterstunden', 'stundensatz', 'planstunden'); + + foreach ($nullable_fields as $nullable_field) { - $formData['semesterstunden'] = null; + if (isset($formData[$nullable_field]) && (!is_numeric($formData[$nullable_field]) || $formData[$nullable_field] === '')) + { + $formData[$nullable_field] = null; + } } $lehreinheit_permission = $this->checkPermission($lehreinheit_id, array('admin', 'assistenz', 'lv-plan')); @@ -144,6 +149,9 @@ class Lektor extends FHCAPI_Controller if (!$lehreinheit_permission) $this->terminateWithError($this->p->t('ui', 'error_fieldWriteAccess')); + if (!is_null($this->getLektorVertrag($lehreinheit_id, $mitarbeiter_uid)) && (array_key_exists('mitarbeiter_uid', $formData) && $mitarbeiter_uid !== $formData['mitarbeiter_uid'])) + $this->terminateWithError($this->p->t('ui', 'error_fieldWriteAccess')); + $result = $this->_ci->lektorlib->updateLektorFromLehreinheit($lehreinheit_id, $mitarbeiter_uid, $formData); if (isError($result)) $this->terminateWithError(getError($result)); @@ -154,7 +162,7 @@ class Lektor extends FHCAPI_Controller { $value = str_replace(',', '.', $value); - if (!is_numeric($value)) + if (!is_numeric($value) && $value !== "") { $this->form_validation->set_message('_check_decimal', 'Das Feld {field} muss eine Zahl sein.'); return false; diff --git a/application/controllers/api/frontend/v1/lv/Noten.php b/application/controllers/api/frontend/v1/lv/Noten.php new file mode 100644 index 000000000..614ee6609 --- /dev/null +++ b/application/controllers/api/frontend/v1/lv/Noten.php @@ -0,0 +1,84 @@ + 'student/noten:r', + 'getTeacherProposal' => 'student/noten:r', + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load Phrases + $this->loadPhrases([ + 'stv', + 'person', + 'lehre' + ]); + } + + public function getCertificate($lv_id, $studiensemester_kurzbz = null) + { + if (is_null($lv_id) || !ctype_digit((string)$lv_id)) + $this->terminateWithError( $this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + $this->load->model('education/lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel'); + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + $result = $this->LehrveranstaltungModel->loadWhere([ + 'lehrveranstaltung_id' => $lv_id + ]); + + $lehrveranstaltung = $this->getDataOrTerminateWithError($result); + + if (!$lehrveranstaltung) + $this->terminateWithSuccess([]); + + if ($studiensemester_kurzbz !== null && !$this->StudiensemesterModel->isValidStudiensemester($studiensemester_kurzbz)) + { + $this->terminateWithError($studiensemester_kurzbz . ' - ' . $this->p->t('lehre', 'error_noStudiensemester')); + } + + $result = $this->ZeugnisnoteModel->getZeugnisnoten(null, $studiensemester_kurzbz, $lehrveranstaltung[0]->lehrveranstaltung_id); + + $grades = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($grades); + } + + public function getTeacherProposal($lv_id, $studiensemester_kurzbz = null) + { + if (is_null($lv_id) || !ctype_digit((string)$lv_id)) + $this->terminateWithError( $this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL); + + $this->load->model('education/lehrveranstaltung_model', 'LehrveranstaltungModel'); + $this->load->model('education/Lvgesamtnote_model', 'LvgesamtnoteModel'); + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + $result = $this->LehrveranstaltungModel->loadWhere([ + 'lehrveranstaltung_id' => $lv_id + ]); + + $lehrveranstaltung = $this->getDataOrTerminateWithError($result); + + if (!$lehrveranstaltung) + $this->terminateWithSuccess([]); + + if ($studiensemester_kurzbz !== null && !$this->StudiensemesterModel->isValidStudiensemester($studiensemester_kurzbz)) + { + $this->terminateWithError($studiensemester_kurzbz . ' - ' . $this->p->t('lehre', 'error_noStudiensemester')); + } + + $result = $this->LvgesamtnoteModel->getLvGesamtNoten($lv_id, null, $studiensemester_kurzbz); + + $grades = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($grades); + } + +} diff --git a/application/controllers/api/frontend/v1/lv/Setup.php b/application/controllers/api/frontend/v1/lv/Setup.php index eea4befa5..4b91b99c1 100644 --- a/application/controllers/api/frontend/v1/lv/Setup.php +++ b/application/controllers/api/frontend/v1/lv/Setup.php @@ -19,6 +19,8 @@ if (!defined('BASEPATH')) exit('No direct script access allowed'); +use CI3_Events as Events; + class Setup extends FHCAPI_Controller { private $_ci; @@ -27,7 +29,8 @@ class Setup extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'getTabs' => ['admin:r', 'assistenz:r'], + 'getLETabs' => ['admin:r', 'assistenz:r'], + 'getLVTabs' => ['admin:r', 'assistenz:r'], 'getStudiensemester' => ['admin:r', 'assistenz:r'], 'getSprache' => ['admin:r', 'assistenz:r'], 'getRaumtyp' => ['admin:r', 'assistenz:r'], @@ -41,9 +44,10 @@ class Setup extends FHCAPI_Controller $this->_ci->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel'); $this->_ci->load->library('VariableLib', ['uid' => $this->_uid]); + $this->_ci->load->helper('hlp_document'); } - public function getTabs() + public function getLETabs() { $tabs['details'] = array ( 'title' => 'Details', @@ -60,11 +64,43 @@ class Setup extends FHCAPI_Controller 'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/Lektor.js'), 'config' => [] ); + $tabs['termine'] = array ( + 'title' => 'Termine', + 'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/Termine.js'), + 'config' => [] + ); $tabs['notiz'] = array ( 'title' => 'Notizen', 'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/Notiz.js'), 'config' => [] ); + + Events::trigger('lvv_conf_tabs', function & () use (&$tabs) { + return $tabs; + }); + + $this->terminateWithSuccess($tabs); + } + + public function getLVTabs() + { + $tabs['termine'] = array ( + 'title' => 'Termine', + 'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/LVTermine.js'), + 'config' => [] + ); + $tabs['noten'] = array ( + 'title' => 'Noten', + 'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/Noten.js'), + 'config' => [ + 'usePoints' => defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE, + 'edit' => 'both', // Possible values: both|header|inline + 'delete' => 'inline', // Possible values: both|header|inline + 'documents' => 'inline', // Possible values: both|header|inline + 'documentslist' => gradesDocumentsList(), + 'semesterSelect' => false + ] + ); $this->terminateWithSuccess($tabs); } diff --git a/application/controllers/api/frontend/v1/lv/StgTree.php b/application/controllers/api/frontend/v1/lv/StgTree.php deleted file mode 100644 index 8272da978..000000000 --- a/application/controllers/api/frontend/v1/lv/StgTree.php +++ /dev/null @@ -1,117 +0,0 @@ -method] = ['admin:r', 'assistenz:r']; - parent::__construct($permissions); - - $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); - } - - public function _remap($method, $params = []) - { - if ($method == '' || $method == 'index') - return $this->getBase(); - - if (!$this->permissionlib->isBerechtigt('assistenz', 's', $method) - && !$this->permissionlib->isBerechtigt('admin', 's', $method) - ) { - return $this->_outputAuthError([$method => ['admin:r', 'assistenz:r']]); - } - - return $this->getStudiengang($method); - show_404(); - } - - protected function getBase() - { - $this->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); - - $this->StudiengangModel->addDistinct(); - $this->StudiengangModel->addSelect("v.studiengang_kz AS link"); - $this->StudiengangModel->addSelect( - "CONCAT(kurzbzlang, ' (', UPPER(CONCAT(typ, kurzbz)), ') - ', tbl_studiengang.bezeichnung) AS name", - false - ); - $this->StudiengangModel->addSelect('erhalter_kz'); - $this->StudiengangModel->addSelect('typ'); - $this->StudiengangModel->addSelect('kurzbz'); - $this->StudiengangModel->addSelect('studiengang_kz'); - $this->StudiengangModel->addSelect('studiengang_kz AS stg_kz'); - - $this->StudiengangModel->addOrder('erhalter_kz'); - $this->StudiengangModel->addOrder('typ'); - $this->StudiengangModel->addOrder('kurzbz'); - - $stgs = $this->permissionlib->getSTG_isEntitledFor('admin') ?: []; - $stgs = array_merge($stgs, $this->permissionlib->getSTG_isEntitledFor('assistenz') ?: []); - - if (!$stgs) - $this->terminateWithSuccess([]); - - $this->StudiengangModel->db->where_in('studiengang_kz', $stgs); - - $result = $this->StudiengangModel->loadWhere(['v.aktiv' => true]); - - $list = $this->getDataOrTerminateWithError($result); - - $this->terminateWithSuccess($list); - } - - protected function getStudiengang($studiengang_kz) - { - $link = $studiengang_kz . '/'; - - $this->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); - - $this->StudiengangModel->addDistinct(); - $this->StudiengangModel->addSelect("CONCAT(" . $this->StudiengangModel->escape($link) . ", semester) AS link", false); - $this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester ORDER BY verband, gruppe LIMIT 1)) AS name", false); - $this->StudiengangModel->addSelect("TRUE AS leaf", false); - - $this->StudiengangModel->addSelect('semester'); - $this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false); - - $this->StudiengangModel->addOrder('semester'); - - $result = $this->StudiengangModel->loadWhere([ - 'v.studiengang_kz' => $studiengang_kz, - 'v.aktiv' => true - ]); - $list = $this->getDataOrTerminateWithError($result); - - $result = $this->StudiengangModel->load($studiengang_kz); - $result = $this->getDataOrTerminateWithError($result); - if ($result) - { - if (current($result)->mischform) - { - $this->load->model('organisation/Studienordnung_model', 'StudienordnungModel'); - - $this->StudienordnungModel->addDistinct(); - $this->StudienordnungModel->addSelect("CONCAT(studiengang_kz, '/', p.orgform_kurzbz) AS link"); - $this->StudienordnungModel->addSelect("p.orgform_kurzbz AS name"); - $this->StudienordnungModel->addSelect("TRUE as leaf", false); - - $this->StudienordnungModel->addJoin('lehre.tbl_studienplan p', 'studienordnung_id'); - - $result = $this->StudienordnungModel->loadWhere([ - 'aktiv' => true, - 'studiengang_kz' => $studiengang_kz, - 'p.orgform_kurzbz !=' => 'DDP' - ]); - $result = $this->getDataOrTerminateWithError($result); - - $list = array_merge($list, $result); - } - } - - $this->terminateWithSuccess($list); - } -} diff --git a/application/controllers/api/frontend/v1/organisation/Studiensemester.php b/application/controllers/api/frontend/v1/organisation/Studiensemester.php index 3c6b72d2f..eaafc6ac5 100644 --- a/application/controllers/api/frontend/v1/organisation/Studiensemester.php +++ b/application/controllers/api/frontend/v1/organisation/Studiensemester.php @@ -26,7 +26,9 @@ class Studiensemester extends FHCAPI_Controller 'getAll' => self::PERM_LOGGED, 'getAktNext' => self::PERM_LOGGED, 'getStudienjahrByStudiensemester' => self::PERM_LOGGED, - 'getAllStudiensemesterAndAktOrNext' => self::PERM_LOGGED + 'getAllStudiensemesterAndAktOrNext' => self::PERM_LOGGED, + 'current' => self::PERM_LOGGED, + 'set' => self::PERM_LOGGED ) ); // Load model StudiensemesterModel @@ -166,4 +168,39 @@ class Studiensemester extends FHCAPI_Controller $this->terminateWithSuccess(array($studiensemester, $aktuell)); } + + public function current() + { + $result = $this->StudiensemesterModel->getNearest(); + + $data = $this->getDataOrTerminateWithError($result); + + if (!$data) + $this->terminateWithError('Kein Studiensemester aktiv'); + if (count($data) > 1) + $this->terminateWithError('Mehrere Studiensemester aktiv'); + + $this->terminateWithSuccess(current($data)->studiensemester_kurzbz); + } + + public function set() + { + $this->load->library('AuthLib'); + $this->load->library('form_validation'); + + $this->form_validation->set_rules('studiensemester_kurzbz', 'Studiensemester', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $stdsem = $this->input->post('studiensemester_kurzbz'); + + $this->load->model('system/Variable_model', 'VariableModel'); + + $result = $this->VariableModel->setVariable(getAuthUID(), 'semester_aktuell', $stdsem); + + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess(true); + } } diff --git a/application/controllers/api/frontend/v1/stv/Abschlusspruefung.php b/application/controllers/api/frontend/v1/stv/Abschlusspruefung.php index def4f6502..7c1c974e2 100644 --- a/application/controllers/api/frontend/v1/stv/Abschlusspruefung.php +++ b/application/controllers/api/frontend/v1/stv/Abschlusspruefung.php @@ -246,12 +246,12 @@ class Abschlusspruefung extends FHCAPI_Controller { $searchString = $this->input->get('searchString') ?? ''; - $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); + $this->load->model('person/Person_model', 'PersonModel'); - $result = $this->MitarbeiterModel->searchMitarbeiter($searchString, 'ohneMaUid'); + $result = $this->PersonModel->searchPerson($searchString, 'mitMaUid'); if (isError($result)) { - $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); } $this->terminateWithSuccess($result ?: []); @@ -371,6 +371,9 @@ class Abschlusspruefung extends FHCAPI_Controller $this->terminateWithValidationErrors($this->form_validation->error_array()); } + $datum = $formData['datum'] == '' ? null : $formData['datum']; + $sponsion = $formData['sponsion'] == '' ? null : $formData['sponsion']; + $result = $this->AbschlusspruefungModel->update( [ 'abschlusspruefung_id' => $abschlusspruefung_id @@ -382,8 +385,8 @@ class Abschlusspruefung extends FHCAPI_Controller 'vorsitz' => $vorsitz, 'pruefungsantritt_kurzbz' => $formData['pruefungsantritt_kurzbz'], 'abschlussbeurteilung_kurzbz' => $formData['abschlussbeurteilung_kurzbz'], - 'datum' => $formData['datum'], - 'sponsion' => $formData['sponsion'], + 'datum' => $datum, + 'sponsion' => $sponsion, 'pruefer1' => $pruefer1, 'pruefer2' => $pruefer2, 'pruefer3' => $pruefer3, diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php index bc1fbebfe..cc6c04430 100644 --- a/application/controllers/api/frontend/v1/stv/Config.php +++ b/application/controllers/api/frontend/v1/stv/Config.php @@ -301,6 +301,7 @@ class Config extends FHCAPI_Controller public function student() { + $this->load->helper('hlp_document'); $result = []; $config = $this->config->item('tabs'); @@ -376,9 +377,9 @@ class Config extends FHCAPI_Controller 'config' => [ 'usePoints' => defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE, 'edit' => 'both', // Possible values: both|header|inline - 'delete' => 'both', // Possible values: both|header|inline - 'documents' => 'both', // Possible values: both|header|inline - 'documentslist' => $this->gradesDocumentsList() + 'delete' => 'inline', // Possible values: both|header|inline + 'documents' => 'inline', // Possible values: both|header|inline + 'documentslist' => gradesDocumentsList() ] ]; @@ -622,188 +623,6 @@ class Config extends FHCAPI_Controller ] + $this->kontoColumns(); } - /** - * Helper function to generate the default documentslist config for the - * grades tab. - * - * The resulting array consists of elements which are associative arrays - * that can have the following entries: - * title (required) on the first level this can be HTML code. - * permissioncheck (optional) an URL to an FHCAPI endpoint which returns - * true or false. - * link (optional) an URL that will be called if "action" and - * "children" are not defined. - * action (optional) an associative array that describes an - * POST action that will be called if "children" is - * not defined. - * It can have the following entries: - * - url (required) an URL to an FHCAPI endpoint. - * - post (optional) an associative array with the POST data to - * be sent. - * - response (optional) a string that will be displayed on success. - * children (optional) an array of child elements - * - * All strings that start with { and end with } in the URLs and the - * actions post parameter will be replaced with the corresponding - * attribute of the current dataset (e.G: {uid} will be replaced with the - * uid of the current dataset) - * - * @return array - */ - protected function gradesDocumentsList() - { - $permissioncheck = site_url("api/frontend/v1/documents/permissionAlternativeFormat/{studiengang_kz}"); - - $title_ger = $this->p->t("global", "deutsch"); - $title_eng = $this->p->t("global", "englisch"); - $title_ff = $this->p->t("stv", "document_certificate"); - $title_lv = $this->p->t("stv", "document_coursecertificate"); - - $link_ff = "documents/export/" . - "zertifikat.rdf.php/" . - "Zertifikat" . - "?stg_kz={studiengang_kz_lv}" . - "&uid={uid}" . - "&ss={studiensemester_kurzbz}" . - "&lvid={lehrveranstaltung_id}"; - $link_lv_ger = "documents/export/" . - "lehrveranstaltungszeugnis.rdf.php/" . - "LVZeugnis" . - "?stg_kz={studiengang_kz}" . - "&uid={uid}" . - "&ss={studiensemester_kurzbz}" . - "&lvid={lehrveranstaltung_id}"; - $link_lv_eng = "documents/export/" . - "lehrveranstaltungszeugnis.rdf.php/" . - "LVZeugnisEng" . - "?stg_kz={studiengang_kz}" . - "&uid={uid}" . - "&ss={studiensemester_kurzbz}" . - "&lvid={lehrveranstaltung_id}"; - - $archive_url = "api/frontend/v1/documents/archiveSigned"; - $archive_response = $this->p->t("stv", "document_signed_and_archived"); - $archive_post_ff = [ - "xml" => "zertifikat.rdf.php", - "xsl" => "Zertifikat", - "stg_kz" => "{studiengang_kz_lv}", - "uid" => "{uid}", - "ss" => "{studiensemester_kurzbz}", - "lvid" => "{lehrveranstaltung_id}" - ]; - $archive_post_lv_ger = [ - "xml" => "lehrveranstaltungszeugnis.rdf.php", - "xsl" => "LVZeugnis", - "stg_kz" => "{studiengang_kz}", - "uid" => "{uid}", - "ss" => "{studiensemester_kurzbz}", - "lvid" => "{lehrveranstaltung_id}" - ]; - $archive_post_lv_eng = [ - "xml" => "lehrveranstaltungszeugnis.rdf.php", - "xsl" => "LVZeugnisEng", - "stg_kz" => "{studiengang_kz}", - "uid" => "{uid}", - "ss" => "{studiensemester_kurzbz}", - "lvid" => "{lehrveranstaltung_id}" - ]; - - $list = [ - [ - 'title' => '', - 'children' => [ - [ - 'title' => $title_ff, - 'link' => site_url($link_ff) - ], - [ - 'title' => $title_lv, - 'children' => [ - [ - 'title' => $title_ger, - 'link' => site_url($link_lv_ger), - 'children' => [ - [ - 'title' => 'PDF', - 'permissioncheck' => $permissioncheck, - 'link' => site_url($link_lv_ger) - ], - [ - 'title' => 'DOC', - 'permissioncheck' => $permissioncheck, - 'link' => site_url($link_lv_ger . "&output=doc") - ], - [ - 'title' => 'ODT', - 'permissioncheck' => $permissioncheck, - 'link' => site_url($link_lv_ger . "&output=odt") - ] - ] - ], - [ - 'title' => $title_eng, - 'link' => site_url($link_lv_eng), - 'children' => [ - [ - 'title' => 'PDF', - 'permissioncheck' => $permissioncheck, - 'link' => site_url($link_lv_eng) - ], - [ - 'title' => 'DOC', - 'permissioncheck' => $permissioncheck, - 'link' => site_url($link_lv_eng . "&output=doc") - ], - [ - 'title' => 'ODT', - 'permissioncheck' => $permissioncheck, - 'link' => site_url($link_lv_eng . "&output=odt") - ] - ] - ] - ] - ] - ] - ], - [ - 'title' => '', - 'children' => [ - [ - 'title' => $title_ff, - 'action' => [ - 'url' => site_url($archive_url), - 'post' => $archive_post_ff, - 'response' => $archive_response - ] - ], - [ - 'title' => $title_lv, - 'children' => [ - [ - 'title' => $title_ger, - 'action' => [ - 'url' => site_url($archive_url), - 'post' => $archive_post_lv_ger, - 'response' => $archive_response - ] - ], - [ - 'title' => $title_eng, - 'action' => [ - 'url' => site_url($archive_url), - 'post' => $archive_post_lv_eng, - 'response' => $archive_response - ] - ] - ] - ] - ] - ] - ]; - - return $list; - } - /** * Sort tab list * diff --git a/application/controllers/api/frontend/v1/stv/Dokumente.php b/application/controllers/api/frontend/v1/stv/Dokumente.php index 913a57dc4..b47a4af87 100644 --- a/application/controllers/api/frontend/v1/stv/Dokumente.php +++ b/application/controllers/api/frontend/v1/stv/Dokumente.php @@ -170,6 +170,8 @@ class Dokumente extends FHCAPI_Controller $this->terminateWithValidationErrors($this->form_validation->error_array()); } + $nachgereicht_am = $this->input->post('nachgereicht_am') == '' ? null : $this->input->post('nachgereicht_am'); + $uid = getAuthUID(); $result = $this->AkteModel->update( @@ -180,7 +182,7 @@ class Dokumente extends FHCAPI_Controller 'dokument_kurzbz' => $this->input->post('dokument_kurzbz'), 'anmerkung_intern' => $this->input->post('anmerkung_intern'), 'titel_intern' => $this->input->post('titel_intern'), - 'nachgereicht_am' => $this->input->post('nachgereicht_am'), + 'nachgereicht_am' => $nachgereicht_am, 'updateamum' => date('c'), 'updatevon' => $uid, ] diff --git a/application/controllers/api/frontend/v1/stv/Konto.php b/application/controllers/api/frontend/v1/stv/Konto.php index d59bc76b0..2654c18f6 100644 --- a/application/controllers/api/frontend/v1/stv/Konto.php +++ b/application/controllers/api/frontend/v1/stv/Konto.php @@ -429,9 +429,12 @@ class Konto extends FHCAPI_Controller 'updateamum' => date('c'), 'updatevon' => getAuthUID() ]; + foreach ($allowed as $field) if ($this->input->post($field) !== null) $data[$field] = $this->input->post($field); + if($data['buchungsdatum'] == '') + $data['buchungsdatum'] = null; $result = $this->KontoModel->update($id, $data); diff --git a/application/controllers/api/frontend/v1/stv/LvTermine.php b/application/controllers/api/frontend/v1/stv/LvTermine.php index c29f56964..b9e23e77d 100644 --- a/application/controllers/api/frontend/v1/stv/LvTermine.php +++ b/application/controllers/api/frontend/v1/stv/LvTermine.php @@ -34,7 +34,7 @@ class LvTermine extends FHCAPI_Controller //TODO Build own lib or combine with Controller Stundenplan.php //here use of logic of Stundenplan.php, extended with parameters uid, grouping, and used dbTable - public function getStundenplan($uid, $start_date = null, $end_date = null, $groupConsecutiveHours = false, $dbStundenplanTable = "stundenplan") + public function getStundenplan($uid, $start_date = null, $end_date = null, $dbStundenplanTable = "stundenplan", $groupConsecutiveHours = false) { $student_uid = $uid; $semester_range = $this->studienSemesterErmitteln($start_date, $end_date); @@ -80,13 +80,6 @@ class LvTermine extends FHCAPI_Controller $stundenplan_data = $this->getDataOrTerminateWithError($stundenplan_data) ?? []; $this->terminateWithSuccess($stundenplan_data); - - $this->expand_object_information($stundenplan_data); - - $this->returnObj['$stundenplan_query'] = $stundenplan_query; - $this->returnObj['$student_lehrverband'] = $student_lehrverband; - $this->returnObj['$benutzer_gruppen'] = $benutzer_gruppen; - $this->terminateWithSuccess($stundenplan_data); } public function getStudiensemester() diff --git a/application/controllers/api/frontend/v1/stv/Mobility.php b/application/controllers/api/frontend/v1/stv/Mobility.php index f61816086..5639da549 100644 --- a/application/controllers/api/frontend/v1/stv/Mobility.php +++ b/application/controllers/api/frontend/v1/stv/Mobility.php @@ -237,8 +237,8 @@ class Mobility extends FHCAPI_Controller } $formData = $this->input->post('formData'); - $von = $formData['von'] ?? null; - $bis = $formData['bis'] ?? null; + $von = ($formData['von'] ?? '') ?: null; + $bis = ($formData['bis'] ?? '') ?: null; $nation_code = $formData['nation_code'] ?? null; $mobilitaetsprogramm_code = $formData['mobilitaetsprogramm_code'] ?? null; $herkunftsland_code = $formData['herkunftsland_code'] ?? null; diff --git a/application/controllers/api/frontend/v1/stv/Prestudent.php b/application/controllers/api/frontend/v1/stv/Prestudent.php index d8c8d1ff2..33957872f 100644 --- a/application/controllers/api/frontend/v1/stv/Prestudent.php +++ b/application/controllers/api/frontend/v1/stv/Prestudent.php @@ -193,7 +193,7 @@ class Prestudent extends FHCAPI_Controller } elseif ( $val === '' - && in_array($prop, ['zgvnation', 'zgvmanation', 'zgvdoktornation', 'berufstaetigkeit_code', 'ausbildungcode']) + && in_array($prop, ['zgvnation', 'zgvmanation', 'zgvdoktornation', 'berufstaetigkeit_code', 'ausbildungcode', 'zgvdatum', 'zgvmadatum']) ) { $val = null; diff --git a/application/controllers/api/frontend/v1/stv/Pruefung.php b/application/controllers/api/frontend/v1/stv/Pruefung.php index 4521c2033..becda03ee 100644 --- a/application/controllers/api/frontend/v1/stv/Pruefung.php +++ b/application/controllers/api/frontend/v1/stv/Pruefung.php @@ -448,8 +448,10 @@ class Pruefung extends FHCAPI_Controller } } + $datum = $this->input->post('datum') == '' ? null : $this->input->post('datum'); if(intval($pruefung_id) > 0) { + $result = $this->PruefungModel->update( [ 'pruefung_id' => $pruefung_id @@ -459,7 +461,7 @@ class Pruefung extends FHCAPI_Controller 'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'), 'note' => $this->input->post('note'), 'pruefungstyp_kurzbz' => $this->input->post('pruefungstyp_kurzbz'), - 'datum' => $this->input->post('datum'), + 'datum' => $datum, 'anmerkung' => $this->input->post('anmerkung'), 'updatevon' => $authUID, 'updateamum' => date('c'), @@ -472,7 +474,7 @@ class Pruefung extends FHCAPI_Controller 'lehreinheit_id' => $this->input->post('lehreinheit_id'), 'student_uid' => $this->input->post('student_uid'), 'mitarbeiter_uid' => $this->input->post('mitarbeiter_uid'), - 'datum' => $this->input->post('datum'), + 'datum' => $datum, 'pruefungstyp_kurzbz' => $this->input->post('pruefungstyp_kurzbz'), 'note' => $this->input->post('note'), 'anmerkung' => $this->input->post('anmerkung'), @@ -520,7 +522,7 @@ class Pruefung extends FHCAPI_Controller 'studiensemester_kurzbz' => $studiensemester_kurzbz, 'note' => $this->input->post('note'), 'uebernahmedatum' => date('c'), - 'benotungsdatum' => $this->input->post('datum'), + 'benotungsdatum' => $datum, 'insertamum' => date('c'), 'insertvon' => $authUID, 'punkte' => $this->input->post('punkte') ? str_replace(',', '.', $this->input->post('punkte')) : null @@ -555,7 +557,7 @@ class Pruefung extends FHCAPI_Controller ], [ 'note' => $this->input->post('note'), 'uebernahmedatum' => date('c'), - 'benotungsdatum' => $this->input->post('datum'), + 'benotungsdatum' => $datum, 'updateamum' => date('c'), 'updatevon' => $authUID, 'punkte' => $this->input->post('punkte') ? str_replace(',', '.', $this->input->post('punkte')) : null diff --git a/application/controllers/api/frontend/v1/stv/Student.php b/application/controllers/api/frontend/v1/stv/Student.php index 7694807e7..943df7b91 100644 --- a/application/controllers/api/frontend/v1/stv/Student.php +++ b/application/controllers/api/frontend/v1/stv/Student.php @@ -144,6 +144,7 @@ class Student extends FHCAPI_Controller . $this->PrestudentModel->escape($studiensemester_kurzbz) . ") AS statusofsemester" ); + $this->PrestudentModel->addSelect($this->PrestudentModel->escape($studiensemester_kurzbz) . ' as query_studiensemester_kurzbz'); $this->PrestudentModel->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT'); $this->PrestudentModel->addJoin('public.tbl_benutzer b', 'student_uid = uid', 'LEFT'); @@ -296,6 +297,10 @@ class Student extends FHCAPI_Controller $fotoval = ($val == '') ? null : str_replace('data:image/jpeg;base64,', '', $val); $update_person[$prop] = $fotoval; } + else if ($prop == 'gebdatum') + { + $update_person[$prop] = ($val == '') ? null : $val; + } else { $update_person[$prop] = $val; diff --git a/application/controllers/api/frontend/v1/stv/Students.php b/application/controllers/api/frontend/v1/stv/Students.php index 047652452..39ed72072 100644 --- a/application/controllers/api/frontend/v1/stv/Students.php +++ b/application/controllers/api/frontend/v1/stv/Students.php @@ -138,7 +138,7 @@ class Students extends FHCAPI_Controller $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } @@ -190,14 +190,14 @@ class Students extends FHCAPI_Controller ) AS semester", false); $this->studentlistlib->addSelect("COALESCE(v.verband::text, ''::text) AS verband"); $this->studentlistlib->addSelect("COALESCE(v.gruppe::text, ''::text) AS gruppe"); - + $this->addFilter($studiensemester_kurzbz); $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } @@ -235,59 +235,66 @@ class Students extends FHCAPI_Controller ) AS semester", false); $this->studentlistlib->addSelect("COALESCE(v.verband::text, ''::text) AS verband"); $this->studentlistlib->addSelect("COALESCE(v.gruppe::text, ''::text) AS gruppe"); - + $this->addFilter($studiensemester_kurzbz); $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } public function getPrestudents( - $studiengang_kz, + $studiengang_typ_kurzbz, $studiensemester_kurzbz = null, $filter = null ) { $this->addMeta('ci_method', __FUNCTION__); $this->addMeta('ci_params', array( - 'studiengang_kz' => $studiengang_kz, + 'studiengang_typ_kurzbz' => $studiengang_typ_kurzbz, 'studiensemester_kurzbz' => $studiensemester_kurzbz, 'filter' => $filter )); - $this->fetchPrestudents($studiengang_kz, $studiensemester_kurzbz, $filter); + $this->fetchPrestudents($studiengang_typ_kurzbz, $studiensemester_kurzbz, $filter); } public function getPrestudentsOrgform( - $studiengang_kz, + $studiengang_typ_kurzbz, $orgform_kurzbz, $studiensemester_kurzbz = null, $filter = null ) { $this->addMeta('ci_method', __FUNCTION__); $this->addMeta('ci_params', array( - 'studiengang_kz' => $studiengang_kz, + 'studiengang_typ_kurzbz' => $studiengang_typ_kurzbz, 'studiensemester_kurzbz' => $studiensemester_kurzbz, 'filter' => $filter, 'orgform_kurzbz' => $orgform_kurzbz )); - $this->fetchPrestudents($studiengang_kz, $studiensemester_kurzbz, $filter, $orgform_kurzbz); + $this->fetchPrestudents($studiengang_typ_kurzbz, $studiensemester_kurzbz, $filter, $orgform_kurzbz); } /** - * @param integer $studiengang_kz + * @param string $studiengang_typ_kurzbz * @param string $studiensemester_kurzbz (optional) * @param string $filter (optional) * @param string $orgform_kurzbz (optional) * * @return void */ - protected function fetchPrestudents($studiengang_kz, $studiensemester_kurzbz = null, $filter = null, $orgform_kurzbz = null) + protected function fetchPrestudents($studiengang_typ_kurzbz, $studiensemester_kurzbz = null, $filter = null, $orgform_kurzbz = null) { + $studiengang_kz = $this->getStudiengangKz($studiengang_typ_kurzbz); + $studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz); + $orgform_kurzbz = $this->getOrgformKurzbz($orgform_kurzbz); + + if ($studiengang_kz === null) + show_404(); + $stdsemEsc = $studiensemester_kurzbz ? $this->PrestudentModel->escape($studiensemester_kurzbz) : 'NULL'; $selectRT = " @@ -439,19 +446,21 @@ class Students extends FHCAPI_Controller $this->studentlistlib->addSelect("'' AS verband"); $this->studentlistlib->addSelect("'' AS gruppe"); + $query_studiensemester_kurzbz = $studiensemester_kurzbz ? $this->PrestudentModel->escape($studiensemester_kurzbz) : '\'NULL\''; + $this->studentlistlib->addSelect($query_studiensemester_kurzbz . ' as query_studiensemester_kurzbz'); $this->addFilter($studiensemester_kurzbz); $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } public function getStudents( $studiensemester_kurzbz, - $studiengang_kz, + $studiengang_typ_kurzbz, $semester = null, $verband = null, $gruppe = null @@ -459,18 +468,18 @@ class Students extends FHCAPI_Controller $this->addMeta('ci_method', __FUNCTION__); $this->addMeta('ci_params', array( 'studiensemester_kurzbz' => $studiensemester_kurzbz, - 'studiengang_kz' => $studiengang_kz, + 'studiengang_typ_kurzbz' => $studiengang_typ_kurzbz, 'semester' => $semester, 'verband' => $verband, 'gruppe' => $gruppe )); - $this->fetchStudents($studiensemester_kurzbz, $studiengang_kz, $semester, $verband, $gruppe, null, null); + $this->fetchStudents($studiensemester_kurzbz, $studiengang_typ_kurzbz, $semester, $verband, $gruppe, null, null); } public function getStudentsOrgform( $studiensemester_kurzbz, - $studiengang_kz, + $studiengang_typ_kurzbz, $orgform_kurzbz, $semester = null, $verband = null, @@ -479,37 +488,37 @@ class Students extends FHCAPI_Controller $this->addMeta('ci_method', __FUNCTION__); $this->addMeta('ci_params', array( 'studiensemester_kurzbz' => $studiensemester_kurzbz, - 'studiengang_kz' => $studiengang_kz, + 'studiengang_typ_kurzbz' => $studiengang_typ_kurzbz, 'orgform_kurzbz' => $orgform_kurzbz, 'semester' => $semester, 'verband' => $verband, 'gruppe' => $gruppe )); - $this->fetchStudents($studiensemester_kurzbz, $studiengang_kz, $semester, $verband, $gruppe, null, $orgform_kurzbz); + $this->fetchStudents($studiensemester_kurzbz, $studiengang_typ_kurzbz, $semester, $verband, $gruppe, null, $orgform_kurzbz); } public function getStudentsSpezialgruppe( $studiensemester_kurzbz, - $studiengang_kz, + $studiengang_typ_kurzbz, $semester, $gruppe_kurzbz ) { $this->addMeta('ci_method', __FUNCTION__); $this->addMeta('ci_params', array( 'studiensemester_kurzbz' => $studiensemester_kurzbz, - 'studiengang_kz' => $studiengang_kz, + 'studiengang_typ_kurzbz' => $studiengang_typ_kurzbz, 'semester' => $semester, 'gruppe_kurzbz' => $gruppe_kurzbz )); - $this->fetchStudents($studiensemester_kurzbz, $studiengang_kz, $semester, null, null, $gruppe_kurzbz, null); + $this->fetchStudents($studiensemester_kurzbz, $studiengang_typ_kurzbz, $semester, null, null, $gruppe_kurzbz, null); } public function getStudentsOrgformSpezialgruppe( $studiensemester_kurzbz, $orgform_kurzbz, - $studiengang_kz, + $studiengang_typ_kurzbz, $semester, $gruppe_kurzbz ) { @@ -517,17 +526,17 @@ class Students extends FHCAPI_Controller $this->addMeta('ci_params', array( 'studiensemester_kurzbz' => $studiensemester_kurzbz, 'orgform_kurzbz' => $orgform_kurzbz, - 'studiengang_kz' => $studiengang_kz, + 'studiengang_typ_kurzbz' => $studiengang_typ_kurzbz, 'semester' => $semester, 'gruppe_kurzbz' => $gruppe_kurzbz )); - $this->fetchStudents($studiensemester_kurzbz, $studiengang_kz, $semester, null, null, $gruppe_kurzbz, $orgform_kurzbz); + $this->fetchStudents($studiensemester_kurzbz, $studiengang_typ_kurzbz, $semester, null, null, $gruppe_kurzbz, $orgform_kurzbz); } /** - * @param integer $studiengang_kz * @param string $studiensemester_kurzbz + * @param string $studiengang_typ_kurzbz * @param integer $semester (optional) * @param string $verband (optional) * @param integer $gruppe (optional) @@ -538,13 +547,20 @@ class Students extends FHCAPI_Controller */ protected function fetchStudents( $studiensemester_kurzbz, - $studiengang_kz, + $studiengang_typ_kurzbz, $semester = null, $verband = null, $gruppe = null, $gruppe_kurzbz = null, $orgform_kurzbz = null ) { + $studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz); + $studiengang_kz = $this->getStudiengangKz($studiengang_typ_kurzbz); + $orgform_kurzbz = $this->getOrgformKurzbz($orgform_kurzbz); + + if ($studiengang_kz === null) + show_404(); + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); if (!$this->StudiensemesterModel->isValidStudiensemester($studiensemester_kurzbz)) @@ -556,6 +572,7 @@ class Students extends FHCAPI_Controller $this->studentlistlib->addJoin("public.tbl_student s", "prestudent_id"); $this->studentlistlib->addSelect("'' AS priorisierung_relativ"); + $this->studentlistlib->addSelect($this->PrestudentModel->escape($studiensemester_kurzbz) . ' as query_studiensemester_kurzbz'); if ($gruppe_kurzbz !== null) { $this->studentlistlib->addJoin('public.tbl_benutzergruppe g', 'uid', '', 'after_b'); @@ -594,10 +611,22 @@ class Students extends FHCAPI_Controller $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } + protected function decodeTagsJsonInResult(&$data) + { + if(defined('STV_TAGS_ENABLED') && STV_TAGS_ENABLED) { + array_walk($data, function($item, $key) { + if(isset($item->tags)) + { + $item->tags = json_decode($item->tags); + } + }); + } + } + /** * @param string $prestudent_id * @@ -611,6 +640,8 @@ class Students extends FHCAPI_Controller 'prestudent_id' => $prestudent_id, )); + $studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz); + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); if (!$this->StudiensemesterModel->isValidStudiensemester($studiensemester_kurzbz)) @@ -618,7 +649,7 @@ class Students extends FHCAPI_Controller $this->terminateWithError($studiensemester_kurzbz . ' - ' . $this->p->t('lehre', 'error_noStudiensemester')); } - + $this->studentlistlib->addSelect("COALESCE( v.semester::text, CASE @@ -638,7 +669,7 @@ class Students extends FHCAPI_Controller $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } @@ -655,6 +686,8 @@ class Students extends FHCAPI_Controller 'student_uid' => $student_uid, )); + $studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz); + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); if (!$this->StudiensemesterModel->isValidStudiensemester($studiensemester_kurzbz)) @@ -664,14 +697,14 @@ class Students extends FHCAPI_Controller $this->studentlistlib->addWhere('s.student_uid', $student_uid); - + $this->addFilter($studiensemester_kurzbz); $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } @@ -689,6 +722,8 @@ class Students extends FHCAPI_Controller 'person_id' => $person_id, )); + $studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz); + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); if (!$this->StudiensemesterModel->isValidStudiensemester($studiensemester_kurzbz)) @@ -699,13 +734,13 @@ class Students extends FHCAPI_Controller $this->studentlistlib->addWhere('p.person_id', $person_id); - + $this->addFilter($studiensemester_kurzbz); $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } @@ -721,6 +756,8 @@ class Students extends FHCAPI_Controller 'studiensemester_kurzbz' => $studiensemester_kurzbz )); + $studiensemester_kurzbz = $this->getStudiensemesterKurzbz($studiensemester_kurzbz); + $this->load->library('SearchLib', [ 'config' => 'searchstv' ]); $this->load->library('form_validation'); @@ -755,6 +792,7 @@ class Students extends FHCAPI_Controller END ) AS semester", false); + $this->studentlistlib->addSelect($this->PrestudentModel->escape($studiensemester_kurzbz) . ' as query_studiensemester_kurzbz'); if ($prestudent_ids && $student_uids) { $this->studentlistlib->addWhere('tbl_prestudent.prestudent_id', $prestudent_ids); @@ -773,7 +811,7 @@ class Students extends FHCAPI_Controller $result = $this->studentlistlib->execute($studiensemester_kurzbz); $data = $this->getDataOrTerminateWithError($result); - + $this->decodeTagsJsonInResult($data); $this->terminateWithSuccess($data); } @@ -802,4 +840,55 @@ class Students extends FHCAPI_Controller } } } + + protected function getStudiengangKz($studiengang_typ_kurzbz) + { + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $result = $this->StudiengangModel->loadWhere([ + 'LOWER(CONCAT(typ, kurzbz)) =' => strtolower($studiengang_typ_kurzbz) + ]); + + $data = $this->getDataOrTerminateWithError($result); + + if (!$data) + return null; + + return current($data)->studiengang_kz; + } + + protected function getStudiensemesterKurzbz($studiensemester_kurzbz) + { + if ($studiensemester_kurzbz === null) + return null; + + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + $result = $this->StudiensemesterModel->loadWhere([ + 'LOWER(studiensemester_kurzbz) =' => strtolower($studiensemester_kurzbz) + ]); + + $data = $this->getDataOrTerminateWithError($result); + + if (!$data) + return $studiensemester_kurzbz; + + return current($data)->studiensemester_kurzbz; + } + + protected function getOrgformKurzbz($orgform_kurzbz) + { + if ($orgform_kurzbz === null) + return null; + + $this->load->model('codex/Orgform_model', 'OrgformModel'); + $result = $this->OrgformModel->loadWhere([ + 'LOWER(orgform_kurzbz) =' => strtolower($orgform_kurzbz) + ]); + + $data = $this->getDataOrTerminateWithError($result); + + if (!$data) + return $orgform_kurzbz; + + return current($data)->orgform_kurzbz; + } } diff --git a/application/controllers/api/frontend/v1/stv/Tags.php b/application/controllers/api/frontend/v1/stv/Tags.php index 3004a1f3b..bd5c61953 100644 --- a/application/controllers/api/frontend/v1/stv/Tags.php +++ b/application/controllers/api/frontend/v1/stv/Tags.php @@ -15,7 +15,9 @@ class Tags extends Tag_Controller 'addTag' => self::BERECHTIGUNG_KURZBZ, 'updateTag' => self::BERECHTIGUNG_KURZBZ, 'doneTag' => self::BERECHTIGUNG_KURZBZ, - 'deleteTag' => self::BERECHTIGUNG_KURZBZ + 'deleteTag' => self::BERECHTIGUNG_KURZBZ, + 'getAllTags' => self::BERECHTIGUNG_KURZBZ, + 'rebuildTagsForTypeId' => self::BERECHTIGUNG_KURZBZ, ]); $this->config->load('stv'); @@ -45,4 +47,5 @@ class Tags extends Tag_Controller { parent::doneTag($this->config->item('stv_prestudent_tags')); } + } diff --git a/application/controllers/api/frontend/v1/stv/Verband.php b/application/controllers/api/frontend/v1/stv/Verband.php deleted file mode 100644 index 32ef30a45..000000000 --- a/application/controllers/api/frontend/v1/stv/Verband.php +++ /dev/null @@ -1,513 +0,0 @@ -. - */ - -if (! defined('BASEPATH')) exit('No direct script access allowed'); - -/** - * This controller operates between (interface) the JS (GUI) and the back-end - * Provides data to the ajax get calls about verbände - * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON - */ -class Verband extends FHCAPI_Controller -{ - public function __construct() - { - $permissions = []; - $router = load_class('Router'); - $permissions[$router->method] = ['admin:r', 'assistenz:r']; - parent::__construct($permissions); - - // Load Models - $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); - } - - /** - * Remap calls: - * / - * /(studiengang_kz) => getStudiengang - * /(studiengang_kz)/(semester) => getSemester - * /(studiengang_kz)/(semester)/(verband) => getVerband - * /(studiengang_kz)/(org_form) => getStudiengang - * /(studiengang_kz)/(org_form)/(semester) => getSemester - * /(studiengang_kz)/(org_form)/(semester)/(verband) => getVerband - * - * @param string $method - * @param array $params (optional) - * - * @return void - */ - public function _remap($method, $params = []) - { - if ($method == '' || $method == 'index') - return $this->getBase(); - - // NOTE(chris): Test if access is allowed ($method is the Studiengang) - if (!$this->permissionlib->isBerechtigt('assistenz', 's', $method) - && !$this->permissionlib->isBerechtigt('admin', 's', $method) - ) { - return $this->_outputAuthError([$method => ['admin:r', 'assistenz:r']]); - } - - $count = count($params); - if (!$count) - return $this->getStudiengang($method); - - if ($count == 1) { - if (is_numeric($params[0])) - return $this->getSemester($method, $params[0]); - elseif ($params[0] == 'prestudent') - return $this->terminateWithSuccess($this->getStdSem($method . '/prestudent/', $method)); - else - return $this->getStudiengang($method, $params[0]); - } - if ($count == 2) { - if (is_numeric($params[0])) - return $this->getVerband($method, $params[0], $params[1]); - elseif ($params[1] == 'prestudent') - return $this->terminateWithSuccess($this->getStdSem($method . '/' . $params[0] . '/prestudent/', $method)); - else - return $this->getSemester($method, $params[1], $params[0]); - } - if ($count == 3 && !is_numeric($params[0]) && is_numeric($params[1]) && !is_numeric($params[2])) - return $this->getVerband($method, $params[1], $params[2], $params[0]); - - show_404(); - } - - /** - * @return void - */ - protected function getBase() - { - $this->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); - - $this->StudiengangModel->addDistinct(); - $this->StudiengangModel->addSelect("v.studiengang_kz AS link"); - $this->StudiengangModel->addSelect( - "CONCAT(kurzbzlang, ' (', UPPER(CONCAT(typ, kurzbz)), ') - ', tbl_studiengang.bezeichnung) AS name", - false - ); - $this->StudiengangModel->addSelect('erhalter_kz'); - $this->StudiengangModel->addSelect('typ'); - $this->StudiengangModel->addSelect('kurzbz'); - $this->StudiengangModel->addSelect('studiengang_kz'); - $this->StudiengangModel->addSelect('studiengang_kz AS stg_kz'); - - $this->StudiengangModel->addOrder('erhalter_kz'); - $this->StudiengangModel->addOrder('typ'); - $this->StudiengangModel->addOrder('kurzbz'); - - $stgs = $this->permissionlib->getSTG_isEntitledFor('admin') ?: []; - $stgs = array_merge($stgs, $this->permissionlib->getSTG_isEntitledFor('assistenz') ?: []); - - if (!$stgs) - $this->terminateWithSuccess([]); - - $this->StudiengangModel->db->where_in('studiengang_kz', $stgs); - - $result = $this->StudiengangModel->loadWhere(['v.aktiv' => true]); - - $list = $this->getDataOrTerminateWithError($result); - - if ($this->permissionlib->isBerechtigt('inout/uebersicht')) - $list[] = [ - 'name' => 'International', - 'link' => 'inout', - 'children' => [ - [ - 'name' => 'Incoming', - 'link' => 'inout/incoming', - 'leaf' => true - ], - [ - 'name' => 'Outgoing', - 'link' => 'inout/outgoing', - 'leaf' => true - ], - [ - 'name' => 'Gemeinsame Studien', - 'link' => 'inout/gemeinsamestudien', - 'leaf' => true - ] - ] - ]; - $this->terminateWithSuccess($list); - } - - /** - * @param integer $studiengang_kz - * @param string $orgform (optional) - * - * @return void - */ - protected function getStudiengang($studiengang_kz, $org_form = null) - { - $link = $studiengang_kz . '/'; - if ($org_form !== null) - $link .= $org_form . '/'; - - $this->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); - - $this->StudiengangModel->addDistinct(); - $this->StudiengangModel->addSelect("CONCAT(" . $this->StudiengangModel->escape($link) . ", semester) AS link", false); - $this->StudiengangModel->addSelect("CONCAT( - UPPER(CONCAT(typ, kurzbz)), - '-', - semester, - ( - SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END - FROM public.tbl_lehrverband - WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester - ORDER BY verband, gruppe LIMIT 1 - ) - ) AS name", false); - - $this->StudiengangModel->addSelect('semester'); - $this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false); - - $this->StudiengangModel->addOrder('semester'); - - if ($org_form !== null) { - $this->StudiengangModel->addSelect("v.orgform_kurzbz"); - $this->StudiengangModel->db->group_start(); - $this->StudiengangModel->db->where('v.semester', 0); - $this->StudiengangModel->db->or_where('v.orgform_kurzbz', $org_form); - $this->StudiengangModel->db->group_end(); - } - - $result = $this->StudiengangModel->loadWhere([ - 'v.studiengang_kz' => $studiengang_kz, - 'v.aktiv' => true - ]); - $list = $this->getDataOrTerminateWithError($result); - - array_unshift($list, [ - 'name' => 'PreStudent', - 'link' => $link . 'prestudent', - 'no_sem_reload' => true, - 'stg_kz' => (int)$studiengang_kz, - 'children' => $this->getStdSem($link . 'prestudent/', $studiengang_kz) - ]); - - if ($org_form === null) { - // NOTE(chris): if mischform show orgforms - $result = $this->StudiengangModel->load($studiengang_kz); - $result = $this->getDataOrTerminateWithError($result); - if ($result) { - if (current($result)->mischform) { - $this->load->model('organisation/Studienordnung_model', 'StudienordnungModel'); - - $this->StudienordnungModel->addDistinct(); - $this->StudienordnungModel->addSelect("CONCAT(studiengang_kz, '/', p.orgform_kurzbz) AS link"); - $this->StudienordnungModel->addSelect("p.orgform_kurzbz AS name"); - $this->StudienordnungModel->addSelect("studiengang_kz AS stg_kz"); - - $this->StudienordnungModel->addJoin('lehre.tbl_studienplan p', 'studienordnung_id'); - - $result = $this->StudienordnungModel->loadWhere([ - 'aktiv' => true, - 'studiengang_kz' => $studiengang_kz, - 'p.orgform_kurzbz !=' => 'DDP' - ]); - $result = $this->getDataOrTerminateWithError($result); - - $list = array_merge($list, $result); - } - } - } - $this->terminateWithSuccess($list); - } - - /** - * @param integer $studiengang_kz - * @param integer $semester - * @param string $orgform - * - * @return void - */ - protected function getSemester($studiengang_kz, $semester, $org_form = null) - { - $link = $studiengang_kz . '/'; - if ($org_form !== null) - $link .= $org_form . '/'; - $link .= $semester . '/'; - - - $this->load->model('organisation/Gruppe_model', 'GruppeModel'); - - $this->GruppeModel->addDistinct(); - $this->GruppeModel->addSelect("CONCAT(" . $this->GruppeModel->escape($link . 'grp/') . ", gruppe_kurzbz) AS link", false); - $this->GruppeModel->addSelect("CONCAT(gruppe_kurzbz, ' (', bezeichnung, ')') AS name", false); - $this->GruppeModel->addSelect("TRUE AS leaf", false); - - $this->GruppeModel->addSelect('sort'); - $this->GruppeModel->addSelect('gruppe_kurzbz'); - $this->GruppeModel->addSelect($this->GruppeModel->escape($studiengang_kz) . '::integer AS stg_kz', false); - - $this->GruppeModel->addOrder('sort'); - $this->GruppeModel->addOrder('gruppe_kurzbz'); - - $where = [ - 'studiengang_kz' => $studiengang_kz, - 'semester' => $semester, - 'lehre' => true, - 'sichtbar' => true, - 'aktiv' => true, - 'direktinskription' => false - ]; - - if ($org_form !== null) - $where['orgform_kurzbz'] = $org_form; - - $result = $this->GruppeModel->loadWhere($where); - - $list = $this->getDataOrTerminateWithError($result); - - $this->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); - - $this->StudiengangModel->addSelect("CONCAT(" . $this->StudiengangModel->escape($link) . ", verband) AS link", false); - $this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, verband, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester AND verband=v.verband ORDER BY gruppe LIMIT 1)) AS name", false); - $this->StudiengangModel->addSelect("CASE WHEN MAX(gruppe)='' OR MAX(gruppe)=' ' THEN TRUE ELSE FALSE END AS leaf"); - - $this->StudiengangModel->addSelect($this->StudiengangModel->escape($semester) . ' AS semester'); - $this->StudiengangModel->addSelect('verband'); - $this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false); - - $this->StudiengangModel->addOrder('verband'); - - $this->StudiengangModel->addGroupBy('link, name, verband'); - - $where = [ - 'v.studiengang_kz' => $studiengang_kz, - 'v.semester' => $semester, - 'v.verband !=' => '', - 'v.aktiv' => true - ]; - - if ($org_form !== null && $semester) // NOTE(chris): on semester 0 show all? - $where['v.orgform_kurzbz'] = $org_form; - - $result = $this->StudiengangModel->loadWhere($where); - $result = $this->getDataOrTerminateWithError($result); - - $list = array_merge($list, $result); - - $this->terminateWithSuccess($list); - } - - /** - * @param integer $studiengang_kz - * @param integer $semester - * @param integer $verband - * @param string $orgform - * - * @return void - */ - protected function getVerband($studiengang_kz, $semester, $verband, $org_form = null) - { - $link = $studiengang_kz . '/'; - if ($org_form !== null) - $link .= $org_form . '/'; - $link .= $semester . '/'. $verband . '/'; - - - $this->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); - - $this->StudiengangModel->addDistinct(); - $this->StudiengangModel->addSelect("CONCAT(" . $this->StudiengangModel->escape($link) . ", gruppe) AS link", false); - $this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, verband, gruppe, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester AND verband=v.verband AND gruppe=v.gruppe ORDER BY gruppe LIMIT 1)) AS name", false); - $this->StudiengangModel->addSelect("TRUE AS leaf", false); - - $this->StudiengangModel->addSelect('v.semester'); - $this->StudiengangModel->addSelect('v.verband'); - $this->StudiengangModel->addSelect('gruppe'); - $this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false); - - $this->StudiengangModel->addOrder('gruppe'); - - $where = [ - 'v.studiengang_kz' => $studiengang_kz, - 'v.semester' => $semester, - 'v.verband' => $verband, - 'v.gruppe !=' => '', - 'v.aktiv' => true - ]; - - if ($org_form !== null && $semester) // NOTE(chris): on semester 0 show all? - $where['v.orgform_kurzbz'] = $org_form; - - $result = $this->StudiengangModel->loadWhere($where); - - $list = $this->getDataOrTerminateWithError($result); - - $this->terminateWithSuccess($list); - } - - /** - * @param string $link - * @param integer $studiengang_kz - * - * @return array - */ - protected function getStdSem($link, $studiengang_kz) - { - $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); - - $this->load->model('system/Variable_model', 'VariableModel'); - $result = $this->VariableModel->getVariables(getAuthUID(), ['number_displayed_past_studiensemester']); - $data = $this->getDataOrTerminateWithError($result); - - $this->load->config('stv'); - $number_displayed_past_studiensemester_default = $this->config->item('number_displayed_past_studiensemester_default'); - - $number_displayed_past_studiensemester = $data['number_displayed_past_studiensemester'] ?? $number_displayed_past_studiensemester_default; - - $this->StudiensemesterModel->addPlusMinus(null, $number_displayed_past_studiensemester); - $this->StudiensemesterModel->addOrder('ende'); - $result = $this->StudiensemesterModel->load(); - - $studiensemester = $this->getDataOrTerminateWithError($result); - $result = []; - - $studiengang_kz = (int)$studiengang_kz; - - foreach ($studiensemester as $sem) { - $semlink = $link . $sem->studiensemester_kurzbz; - $intlink = $semlink . '/interessenten'; - $result[] = [ - 'name' => $sem->studiensemester_kurzbz, - 'link' => $semlink, - 'stg_kz' => $studiengang_kz, - 'children' => [ - [ - 'name' => 'Interessenten', - 'link' => $intlink, - 'stg_kz' => $studiengang_kz, - 'children' => [ - [ - 'name' => 'Bewerbung nicht abgeschickt', - 'link' => $intlink . '/bewerbungnichtabgeschickt', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'Bewerbung abgeschickt, Status unbestätigt', - 'link' => $intlink . '/bewerbungabgeschickt', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'ZGV erfüllt', - 'link' => $intlink . '/zgv', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'Status bestätigt', - 'link' => $intlink . '/statusbestaetigt', - 'stg_kz' => $studiengang_kz, - 'children' => [ - [ - 'name' => 'Nicht zum Reihungstest angemeldet', - 'link' => $intlink . '/statusbestaetigtrtnichtangemeldet', - 'leaf' => true - ], - [ - 'name' => 'Reihungstest angemeldet', - 'link' => $intlink . '/statusbestaetigtrtangemeldet', - 'leaf' => true - ] - ] - ], - [ - 'name' => 'Nicht zum Reihungstest angemeldet', - 'link' => $intlink . '/reihungstestnichtangemeldet', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'Reihungstest angemeldet', - 'link' => $intlink . '/reihungstestangemeldet', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ] - ] - ], - [ - 'name' => 'Bewerber', - 'link' => $semlink . '/bewerber', - 'stg_kz' => $studiengang_kz, - 'children' => [ - [ - 'name' => 'Nicht zum Reihungstest angemeldet', - 'link' => $intlink . '/bewerberrtnichtangemeldet', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'Reihungstest angemeldet', - 'link' => $intlink . '/bewerberrtangemeldet', - 'stg_kz' => $studiengang_kz, - 'children' => [ - [ - 'name' => 'Teilgenommen', - 'link' => $intlink . '/bewerberrtangemeldetteilgenommen', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'Nicht teilgenommen', - 'link' => $intlink . '/bewerberrtangemeldetnichtteilgenommen', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ] - ] - ] - ] - ], - [ - 'name' => 'Aufgenommen', - 'link' => $semlink . '/aufgenommen', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'Warteliste', - 'link' => $semlink . '/warteliste', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'Absage', - 'link' => $semlink . '/absage', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ], - [ - 'name' => 'Incoming', - 'link' => $semlink . '/incoming', - 'stg_kz' => $studiengang_kz, - 'leaf' => true - ] - ] - ]; - } - - return $result; - } -} diff --git a/application/controllers/api/v1/system/Tag.php b/application/controllers/api/v1/system/Tag.php index 7ce375217..df3003b44 100644 --- a/application/controllers/api/v1/system/Tag.php +++ b/application/controllers/api/v1/system/Tag.php @@ -37,6 +37,7 @@ class Tag extends API_Controller if (isset($tag)) { + $this->TagModel->addJoin('public.tbl_notiz_typ', 'typ_kurzbz', 'typ_kurzbz'); $result = $this->TagModel->load($tag); $this->response($result, REST_Controller::HTTP_OK); diff --git a/application/controllers/components/Cis/Mylv.php b/application/controllers/components/Cis/Mylv.php index b6d10931c..c632baa39 100644 --- a/application/controllers/components/Cis/Mylv.php +++ b/application/controllers/components/Cis/Mylv.php @@ -15,11 +15,11 @@ class Mylv extends Auth_Controller { parent::__construct([ - 'Student' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions? - 'Studiensemester' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions? - 'Lvs' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions? - 'Info' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'], // TODO(chris): permissions? - 'Pruefungen' => ['student/anrechnung_beantragen:r','user:r', 'basis/cis:r'] // TODO(chris): permissions? + 'Student' => self::PERM_LOGGED, + 'Studiensemester' => self::PERM_LOGGED, + 'Lvs' => self::PERM_LOGGED, + 'Info' => self::PERM_LOGGED, + 'Pruefungen' => self::PERM_LOGGED ]); } diff --git a/application/controllers/components/stv/Studiensemester.php b/application/controllers/components/stv/Studiensemester.php deleted file mode 100644 index c3db99686..000000000 --- a/application/controllers/components/stv/Studiensemester.php +++ /dev/null @@ -1,78 +0,0 @@ - self::PERM_LOGGED, - 'now' => self::PERM_LOGGED, - 'set' => self::PERM_LOGGED - ]); - } - - public function index() - { - $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); - - $this->StudiensemesterModel->addOrder('start'); - - $result = $this->StudiensemesterModel->load(); - - if (isError($result)) { - $this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR); - } - $this->outputJson($result); - } - - public function now() - { - $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); - - $result = $this->StudiensemesterModel->getNearest(); - - if (isError($result)) { - $this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR); - $this->outputJson(getError($result)); - } - $result = getData($result) ?: []; - - if (count($result) != 1) { - $this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR); - $this->outputJsonError(count($result) ? 'Mehrere Studiensemester aktiv' : 'Kein Studiensemester aktiv'); - } else { - $this->outputJsonSuccess(current($result)->studiensemester_kurzbz); - } - } - - public function set() - { - $this->load->library('AuthLib'); - $this->load->library('form_validation'); - - $_POST = json_decode(utf8_encode($this->input->raw_input_stream), true); - - $this->form_validation->set_rules('studiensemester', 'Studiensemester', 'required'); - - if ($this->form_validation->run() == false) { - $this->output->set_status_header(REST_Controller::HTTP_BAD_REQUEST); - return $this->outputJsonError($this->form_validation->error_array()); - } - - $stdsem = $this->input->post('studiensemester'); - - $this->load->model('system/Variable_model', 'VariableModel'); - - $result = $this->VariableModel->setVariable(getAuthUID(), 'semester_aktuell', $stdsem); - - if (isError($result)) { - $this->output->set_status_header(REST_Controller::HTTP_INTERNAL_SERVER_ERROR); - return $this->outputJson($result); - } - - $this->outputJsonSuccess(true); - } -} diff --git a/application/controllers/jobs/AntragJob.php b/application/controllers/jobs/AntragJob.php index 5b72518d2..8bcb898a9 100644 --- a/application/controllers/jobs/AntragJob.php +++ b/application/controllers/jobs/AntragJob.php @@ -125,7 +125,7 @@ class AntragJob extends JOB_Controller $stgLeitungen[$leitung->uid]['stgs'][] = $antrag->studiengang_kz; } - $result = $this->StudierendenantragModel->getStgAndSem($antrag->studierendenantrag_id); + $result = $this->StudierendenantragModel->getStgAndDetails($antrag->studierendenantrag_id); if (isError($result)) { $this->logError(getError($result)); diff --git a/application/controllers/jobs/TagJob.php b/application/controllers/jobs/TagJob.php new file mode 100644 index 000000000..ae21883e9 --- /dev/null +++ b/application/controllers/jobs/TagJob.php @@ -0,0 +1,110 @@ +load->config('stv'); + + // Library + $this->load->library('TagLib'); + + // Load Models + $this->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel'); + $this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + $this->load->model('system/Notiztyp_model', 'NotiztypModel'); + + $this->loadPhrases([ + 'lehre' + ]); + } + + public function rebuildAutomatedTags() + { + $automatedTagsRes = $this->NotiztypModel->loadWhere(array('automatisiert' => true, 'taglib IS NOT NULL' => null)); + $automatedTags = hasData($automatedTagsRes) ? getData($automatedTagsRes) : []; + + + $result = $this->StudiensemesterModel->getAktOrNextSemester(); + if (isError($result)) + { + $this->logInfo('Start Job rebuild Automated Tags'); + $this->logError('Error occurred during retrieving studiensemester'); + return $this->logInfo('End Job rebuild Automated Tags'); + } + + if (empty($result->retval) || !isset($result->retval[0])) { + $this->logInfo('Start Job rebuild Automated Tags'); + $this->logError('No Studiensemester found'); + return $this->logInfo('End Job rebuild Automated Tags'); + } + $studiensemester_kurzbz = $result->retval[0]->studiensemester_kurzbz ?? null; + $params = array( + 'studiensemester_kurzbz' => $studiensemester_kurzbz + ); + + $this->logInfo('Start Job rebuild Automated Tags ' . $studiensemester_kurzbz); + foreach($automatedTags as $autoTag) + { + // getPath: must not be lost + $filePath = APPPATH . 'libraries/' . $autoTag->taglib . '.php'; // APPPATH = application/ + + if(file_exists($filePath)) { + require_once($filePath); + } else { + $this->logInfo("File not found: " . $filePath); + continue; + } + + $kurz_bz = $autoTag->typ_kurzbz; + // className without PATH (basename) + $className = basename($autoTag->taglib); + + $obj = new $className(); + + $outputArray = $obj->getZuordnungIds($params); + $typeId = $outputArray->typeId; + + $paramsTag = array( + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'kurzbz' => $kurz_bz, + 'data' => $outputArray->data, + 'typeId' => $typeId + ); + + $result = $this->taglib->updateAutomatedTags($paramsTag); + + if (isError($result)) { + $this->logError('Error occurred during updateAutomatedTags ' . $kurz_bz); + continue; + } + + $data = is_array($result) ? $result['retval'] : $result->retval; + + //SUMMARY + $this->logInfo("Tag " . $result->retval['input']['tag'] . " | type_id " . $typeId . " --" + . " Count Recycled: " . $result->retval['summary']['recycled'] + . " Count Added: ". $result->retval['summary']['added'] + . " Count Deleted: ". $result->retval['summary']['deleted']); + + //DETAILS + if($result->retval['results']['newTags']) + $this->logInfo("Tag " . $result->retval['input']['tag'] . "New tag(s): " . implode(', ', $result->retval['results']['newTags'])); + if($result->retval['results']['deletedTagsIds']) + $this->logInfo("Tag " . $result->retval['input']['tag'] . "Deleted tags(s: " . implode(', ', $result->retval['results']['deletedTagsIds'])); + if ($result->retval['results']['retaggedIds']) + $this->logInfo("Tag " . $result->retval['input']['tag'] . "Recycled tag(s): " . implode(', ', $result->retval['results']['retaggedIds'])); + } + $this->logInfo( "End Job rebuild Automated Tags"); + } +} diff --git a/application/controllers/js/Tabulatorcolumns.php b/application/controllers/js/Tabulatorcolumns.php new file mode 100644 index 000000000..1a4621219 --- /dev/null +++ b/application/controllers/js/Tabulatorcolumns.php @@ -0,0 +1,60 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller generates javascript from snippets for tabulator columns + */ +class Tabulatorcolumns extends Auth_Controller +{ + public function __construct() + { + parent::__construct([ + 'stv' => ['admin:r', 'assistenz:r'], + 'lvverwaltung' => ['admin:r', 'assistenz:r'] + ]); + } + + /** + * Render tabulator columns for config + * + * @param string $config + * + * @return void + */ + public function _remap($config) + { + $this->load->config($config); + $list_columns = $this->config->item('list_columns') ?: []; + + $snippets = [ + 'jssnippets/tabulatorcolumns/' . $config . '.js' + ]; + + foreach ($list_columns as $col) { + $snippets[] = $col['js']; + } + + $this->output->set_content_type('application/javascript'); + + $this->load->view('jssnippets/tabulatorcolumns.js', [ + 'snippets' => $snippets + ]); + } +} diff --git a/application/controllers/lehre/Studierendenantrag.php b/application/controllers/lehre/Studierendenantrag.php index ebc1d2eaa..ad333679d 100644 --- a/application/controllers/lehre/Studierendenantrag.php +++ b/application/controllers/lehre/Studierendenantrag.php @@ -25,7 +25,8 @@ class Studierendenantrag extends FHC_Controller // Load language phrases $this->loadPhrases([ - 'studierendenantrag' + 'studierendenantrag', + 'ui' ]); $this->load->config('studierendenantrag'); diff --git a/application/controllers/person/Gradelist.php b/application/controllers/person/Gradelist.php index 230c7f67f..d6df2c43a 100644 --- a/application/controllers/person/Gradelist.php +++ b/application/controllers/person/Gradelist.php @@ -198,7 +198,19 @@ class Gradelist extends Auth_Controller if (!isset($row_noten->found)) { $result_lv = $this->LehrveranstaltungModel->load($row_noten->lehrveranstaltung_id); - $result_stg = $this->StudiengangModel->load($result_lv->retval[0]->studiengang_kz); + + $studiengang_kz = null; + + if (!empty($result_lv->retval) && isset($result_lv->retval[0]) && isset($result_lv->retval[0]->studiengang_kz)) + { + $result_stg = $this->StudiengangModel->load($result_lv->retval[0]->studiengang_kz); + + if (!empty($result_stg->retval) && isset($result_stg->retval[0]) && is_object($result_stg->retval[0]) && isset($result_stg->retval[0]->kurzbzlang)) + { + $studiengang_kz = $result_stg->retval[0]->kurzbzlang; + } + } + $courses['semester'][$row_noten->studiensemester_kurzbz]['lvs_nonstpl'][] = array( 'lehrveranstaltung_id' => $row_noten->lehrveranstaltung_id, 'lehrtyp_kurzbz' => $result_lv->retval[0]->lehrtyp_kurzbz, @@ -212,8 +224,8 @@ class Gradelist extends Auth_Controller 'semester' => $result_lv->retval[0]->semester, 'note' => $row_noten->note, 'datum' => $row_noten->benotungsdatum, - 'zugeordnet' => true, - 'studiengang_kurzbz' => $result_stg->retval[0]->kurzbzlang + 'studiengang_kurzbz' => $studiengang_kz, + 'zugeordnet' => true ); if(!isset($courses['semester'][$row_noten->studiensemester_kurzbz]['data']['ectssumme_nonstpl'])) $courses['semester'][$row_noten->studiensemester_kurzbz]['data']['ectssumme_nonstpl'] = 0; diff --git a/application/core/Tag_Controller.php b/application/core/Tag_Controller.php index 5b9bac6c5..c4b6edb81 100644 --- a/application/core/Tag_Controller.php +++ b/application/core/Tag_Controller.php @@ -7,7 +7,7 @@ class Tag_Controller extends FHCAPI_Controller { private $_uid; - const BERECHTIGUNG_KURZBZ = 'admin:rw'; + const BERECHTIGUNG_KURZBZ = ['admin:rw']; public function __construct($permissions) { @@ -15,10 +15,11 @@ class Tag_Controller extends FHCAPI_Controller 'getTag' => self::BERECHTIGUNG_KURZBZ, 'getTags' => self::BERECHTIGUNG_KURZBZ, 'addTag' => self::BERECHTIGUNG_KURZBZ, - 'updateTag' => self::BERECHTIGUNG_KURZBZ, 'doneTag' => self::BERECHTIGUNG_KURZBZ, 'deleteTag' => self::BERECHTIGUNG_KURZBZ, + 'getAllTags' => self::BERECHTIGUNG_KURZBZ, + 'rebuildTagsForTypeId' => self::BERECHTIGUNG_KURZBZ, ]; $merged_permissions = array_merge($default_permissions, $permissions); @@ -26,6 +27,10 @@ class Tag_Controller extends FHCAPI_Controller parent::__construct($merged_permissions); $this->_setAuthUID(); + + // Library + $this->load->library('TagLib'); + $this->load->model('person/Notiz_model', 'NotizModel'); $this->load->model('system/Notiztyp_model', 'NotiztypModel'); $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); @@ -38,6 +43,8 @@ class Tag_Controller extends FHCAPI_Controller public function getTag($readonly_tags = null) { $language = $this->_getLanguageIndex(); + $index_bezeichnung_mehrsprachig = $language - 1; + $id = $this->input->get('id'); if (is_array($readonly_tags) && !isEmptyArray($readonly_tags)) @@ -62,14 +69,18 @@ class Tag_Controller extends FHCAPI_Controller $this->NotizModel->addSelect( "tbl_notiz.titel, tbl_notiz.text, - array_to_json(bezeichnung_mehrsprachig::varchar[])->>". $language. " as bezeichnung, + array_to_json(bezeichnung_mehrsprachig::varchar[])->>". $index_bezeichnung_mehrsprachig . " as bezeichnung, tbl_notiz.notiz_id, tbl_notiz_typ.style, + tbl_notiz_typ.automatisiert, + tbl_notiz_typ.prioritaet, tbl_notiz.erledigt as done, tbl_notiz.insertamum, tbl_notiz.updateamum, (verfasserperson.vorname || ' ' || verfasserperson.nachname || ' ' || '(' || verfasserbenutzer.uid || ')') as verfasser, - (bearbeiterperson.vorname || ' ' || bearbeiterperson.nachname || ' ' || '(' || bearbeiterbenutzer.uid || ')') as bearbeiter + (bearbeiterperson.vorname || ' ' || bearbeiterperson.nachname || ' ' || '(' || bearbeiterbenutzer.uid || ')') as bearbeiter, + tbl_notiz.start, + tbl_notiz.ende " ); $this->NotizModel->addJoin('public.tbl_notiz_typ', 'public.tbl_notiz.typ = public.tbl_notiz_typ.typ_kurzbz'); @@ -82,18 +93,24 @@ class Tag_Controller extends FHCAPI_Controller $notiz = $this->NotizModel->loadWhere(array('notiz_id' => $id)); + $this->terminateWithSuccess(hasData($notiz) ? getData($notiz)[0] : array()); } public function getTags($tags = null) { + $language = $this->_getLanguageIndex(); + $index_bezeichnung_mehrsprachig = $language - 1; + $this->NotiztypModel->addSelect( - 'typ_kurzbz as tag_typ_kurzbz, - array_to_json(bezeichnung_mehrsprachig::varchar[])->>0 as bezeichnung, + "typ_kurzbz as tag_typ_kurzbz, + array_to_json(bezeichnung_mehrsprachig::varchar[])->>". $index_bezeichnung_mehrsprachig . " as bezeichnung, style, beschreibung, - tag - ' + tag, + automatisiert, + prioritaet + " ); $this->NotiztypModel->addOrder('prioritaet'); @@ -271,6 +288,90 @@ class Tag_Controller extends FHCAPI_Controller $this->terminateWithSuccess($deleteNotiz); } + public function getAllTags($readonly_tags = false) + { + $language = $this->_getLanguageIndex(); + $index_bezeichnung_mehrsprachig = $language - 1; + $prestudent_id = $this->input->get('prestudent_id'); + + if (is_array($readonly_tags) && !isEmptyArray($readonly_tags)) + { + $readonly_tags = $this->_filterTag($readonly_tags, true); + + foreach ($readonly_tags as $key => $tag) + { + $readonly_tags[$key] = $this->NotizModel->db->escape($tag); + } + $tags = '(' . implode(',', $readonly_tags) . ')'; + + $this->NotizModel->addSelect(" + CASE + WHEN tbl_notiz_typ.typ_kurzbz IN $tags + THEN TRUE + ELSE FALSE + END as readonly + "); + } + $this->NotizModel->addSelect( + "tbl_notiz.titel, + tbl_notiz.text, + array_to_json(bezeichnung_mehrsprachig::varchar[])->>". $index_bezeichnung_mehrsprachig . " as bezeichnung, + tbl_notiz.notiz_id, + tbl_notiz_typ.style, + tbl_notiz_typ.automatisiert, + tbl_notiz_typ.prioritaet, + tbl_notiz.erledigt as done, + tbl_notiz.insertamum, + tbl_notiz.updateamum, + (verfasserperson.vorname || ' ' || verfasserperson.nachname || ' ' || '(' || verfasserbenutzer.uid || ')') as verfasser, + (bearbeiterperson.vorname || ' ' || bearbeiterperson.nachname || ' ' || '(' || bearbeiterbenutzer.uid || ')') as bearbeiter, + tbl_notiz.start, + tbl_notiz.ende + " + ); + $this->NotizModel->addJoin('public.tbl_notiz_typ', 'public.tbl_notiz.typ = public.tbl_notiz_typ.typ_kurzbz'); + + $this->NotizModel->addJoin('public.tbl_benutzer verfasserbenutzer', 'tbl_notiz.verfasser_uid = verfasserbenutzer.uid', 'LEFT'); + $this->NotizModel->addJoin('public.tbl_person verfasserperson', 'verfasserbenutzer.person_id = verfasserperson.person_id', 'LEFT'); + + $this->NotizModel->addJoin('public.tbl_benutzer bearbeiterbenutzer', 'tbl_notiz.bearbeiter_uid = bearbeiterbenutzer.uid', 'LEFT'); + $this->NotizModel->addJoin('public.tbl_person bearbeiterperson', 'bearbeiterbenutzer.person_id = bearbeiterperson.person_id', 'LEFT'); + + $this->NotizModel->addJoin('public.tbl_notizzuordnung notizzuordnung', 'tbl_notiz.notiz_id = notizzuordnung.notiz_id'); + + $this->NotizModel->addOrder('done'); + $this->NotizModel->addOrder('tbl_notiz_typ.prioritaet'); + $notiz = $this->NotizModel->loadWhere(array('prestudent_id' => $prestudent_id)); + + + $this->terminateWithSuccess(hasData($notiz) ? getData($notiz) : array()); + } + + public function rebuildTagsForTypeId() + { + $ids = $this->input->post('ids'); + $typeId = $this->input->post('typeId'); + $semester = $this->input->post('sem'); + + $idsSuccess = []; + $idsError = []; + $outputResult = []; + + foreach ($ids as $id) + { + $result = $this->taglib->rebuildTagsForTypeId($typeId, $id, $semester); + + if (isError($result)) + $idsError[] = $id; + else + $idsSuccess[] = $id; + + $outputResult[] = $result; + } + + $this->terminateWithSuccess([$outputResult, $idsSuccess, $idsError]); + } + private function _setAuthUID() { $this->_uid = getAuthUID(); @@ -311,4 +412,4 @@ class Tag_Controller extends FHCAPI_Controller )); } -} \ No newline at end of file +} diff --git a/application/helpers/hlp_document_helper.php b/application/helpers/hlp_document_helper.php new file mode 100644 index 000000000..5b457fe82 --- /dev/null +++ b/application/helpers/hlp_document_helper.php @@ -0,0 +1,188 @@ +load->library('PhrasesLib', array('stv'), 'p'); + + $permissioncheck = site_url("api/frontend/v1/documents/permissionAlternativeFormat/{studiengang_kz}"); + + $title_ger = $ci->p->t("global", "deutsch"); + $title_eng = $ci->p->t("global", "englisch"); + $title_ff = $ci->p->t("stv", "document_certificate"); + $title_lv = $ci->p->t("stv", "document_coursecertificate"); + + $link_ff = "documents/export/" . + "zertifikat.rdf.php/" . + "Zertifikat" . + "?stg_kz={studiengang_kz_lv}" . + "&uid={uid}" . + "&ss={studiensemester_kurzbz}" . + "&lvid={lehrveranstaltung_id}"; + $link_lv_ger = "documents/export/" . + "lehrveranstaltungszeugnis.rdf.php/" . + "LVZeugnis" . + "?stg_kz={studiengang_kz}" . + "&uid={uid}" . + "&ss={studiensemester_kurzbz}" . + "&lvid={lehrveranstaltung_id}"; + $link_lv_eng = "documents/export/" . + "lehrveranstaltungszeugnis.rdf.php/" . + "LVZeugnisEng" . + "?stg_kz={studiengang_kz}" . + "&uid={uid}" . + "&ss={studiensemester_kurzbz}" . + "&lvid={lehrveranstaltung_id}"; + + $archive_url = "api/frontend/v1/documents/archiveSigned"; + $archive_response = $ci->p->t("stv", "document_signed_and_archived"); + $archive_post_ff = [ + "xml" => "zertifikat.rdf.php", + "xsl" => "Zertifikat", + "stg_kz" => "{studiengang_kz_lv}", + "uid" => "{uid}", + "ss" => "{studiensemester_kurzbz}", + "lvid" => "{lehrveranstaltung_id}" + ]; + $archive_post_lv_ger = [ + "xml" => "lehrveranstaltungszeugnis.rdf.php", + "xsl" => "LVZeugnis", + "stg_kz" => "{studiengang_kz}", + "uid" => "{uid}", + "ss" => "{studiensemester_kurzbz}", + "lvid" => "{lehrveranstaltung_id}" + ]; + $archive_post_lv_eng = [ + "xml" => "lehrveranstaltungszeugnis.rdf.php", + "xsl" => "LVZeugnisEng", + "stg_kz" => "{studiengang_kz}", + "uid" => "{uid}", + "ss" => "{studiensemester_kurzbz}", + "lvid" => "{lehrveranstaltung_id}" + ]; + + $list = [ + [ + 'title' => '', + 'children' => [ + [ + 'title' => $title_ff, + 'link' => site_url($link_ff) + ], + [ + 'title' => $title_lv, + 'children' => [ + [ + 'title' => $title_ger, + 'link' => site_url($link_lv_ger), + 'children' => [ + [ + 'title' => 'PDF', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_ger) + ], + [ + 'title' => 'DOC', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_ger . "&output=doc") + ], + [ + 'title' => 'ODT', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_ger . "&output=odt") + ] + ] + ], + [ + 'title' => $title_eng, + 'link' => site_url($link_lv_eng), + 'children' => [ + [ + 'title' => 'PDF', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_eng) + ], + [ + 'title' => 'DOC', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_eng . "&output=doc") + ], + [ + 'title' => 'ODT', + 'permissioncheck' => $permissioncheck, + 'link' => site_url($link_lv_eng . "&output=odt") + ] + ] + ] + ] + ] + ] + ], + [ + 'title' => '', + 'children' => [ + [ + 'title' => $title_ff, + 'action' => [ + 'url' => site_url($archive_url), + 'post' => $archive_post_ff, + 'response' => $archive_response + ] + ], + [ + 'title' => $title_lv, + 'children' => [ + [ + 'title' => $title_ger, + 'action' => [ + 'url' => site_url($archive_url), + 'post' => $archive_post_lv_ger, + 'response' => $archive_response + ] + ], + [ + 'title' => $title_eng, + 'action' => [ + 'url' => site_url($archive_url), + 'post' => $archive_post_lv_eng, + 'response' => $archive_response + ] + ] + ] + ] + ] + ] + ]; + + return $list; +} diff --git a/application/helpers/hlp_header_helper.php b/application/helpers/hlp_header_helper.php index 27dfba5a1..eb2cd9f85 100644 --- a/application/helpers/hlp_header_helper.php +++ b/application/helpers/hlp_header_helper.php @@ -111,7 +111,8 @@ function generateJSDataStorageObject($indexPage, $calledPath, $calledMethod) 'theme' => [ 'name'=>$ci->config->item('theme_name'), 'modes'=>$ci->config->item('theme_modes'), - ] + ], + 'domain' => DOMAIN ); $toPrint = "\n"; @@ -270,6 +271,28 @@ function absoluteJsImportUrl($relurl) return $url; } +/* + * Generate Css File Include if Extension contains file + * + * @param $relativeFilePath path relative to Extension public/css dir + */ +function generateCSSsIncludeIfExtensionCssExists($relativeFilePath) +{ + $fsiterator = new FilesystemIterator(FHCPATH . 'application/extensions'); + foreach ($fsiterator as $fsitem) + { + if(preg_match('/^FHC-Core-/', $fsitem->getBasename())) + { + $extensionfile = 'public/extensions/' . $fsitem->getBasename() + . '/css/' . $relativeFilePath; + if(is_readable(FHCPATH . $extensionfile)) + { + generateCSSsInclude($extensionfile); + } + } + } +} + /* * Manipulate CI views includes Array to load * - public/js/FhcApps.js via customJSs and diff --git a/application/libraries/LektorLib.php b/application/libraries/LektorLib.php index bbe630eaf..8a60e0aaa 100644 --- a/application/libraries/LektorLib.php +++ b/application/libraries/LektorLib.php @@ -17,7 +17,12 @@ class LektorLib $this->_ci->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel'); $this->_ci->load->model('ressource/mitarbeiter_model', 'MitarbeiterModel'); $this->_ci->load->model('person/Benutzer_model', 'BenutzerModel'); + $this->_ci->load->model('ressource/Zeitsperre_model', 'ZeitsperreModel'); + $this->_ci->load->model('ressource/Reservierung_model', 'ReservierungModel'); + $this->_ci->load->model('ressource/stundenplandev_model', 'StundenplandevModel'); + $this->_ci->load->library('PhrasesLib', array('lehre')); + $this->_ci->load->library('VariableLib', ['uid' => getAuthUID()]); } public function addLektorToLehreinheit($lehreinheit_id, $mitarbeiter_uid) @@ -78,10 +83,13 @@ class LektorLib public function updateLektorFromLehreinheit($lehreinheit_id, $mitarbeiter_uid, $new_data) { + $old_uid = $mitarbeiter_uid; + $new_uid = isset($new_data['mitarbeiter_uid']) ? $new_data['mitarbeiter_uid'] : $mitarbeiter_uid; + $this->_ci->LehreinheitmitarbeiterModel->addSelect('lehre.tbl_lehreinheitmitarbeiter.*, lehre.tbl_lehreinheit.studiensemester_kurzbz, tbl_lehrveranstaltung.studiengang_kz'); $this->_ci->LehreinheitmitarbeiterModel->addJoin('lehre.tbl_lehreinheit', 'lehreinheit_id'); $this->_ci->LehreinheitmitarbeiterModel->addJoin('lehre.tbl_lehrveranstaltung', 'lehrveranstaltung_id'); - $lehreinheit_result = $this->_ci->LehreinheitmitarbeiterModel->loadWhere(array('lehreinheit_id' => $lehreinheit_id, 'mitarbeiter_uid' => $mitarbeiter_uid)); + $lehreinheit_result = $this->_ci->LehreinheitmitarbeiterModel->loadWhere(array('lehreinheit_id' => $lehreinheit_id, 'mitarbeiter_uid' => $old_uid)); if (isError($lehreinheit_result)) return $lehreinheit_result; @@ -89,27 +97,47 @@ class LektorLib $lehreinheit = getData($lehreinheit_result)[0]; + $semesterstunden_alt = $lehreinheit->semesterstunden; + $semesterstunden_neu = isset($new_data['semesterstunden']) ? $new_data['semesterstunden'] : $semesterstunden_alt; + $bismelden_neu = isset($new_data['bismelden']) ? $new_data['bismelden'] : $lehreinheit->bismelden; + $neue_stunden_eingerechnet = (bool)$bismelden_neu; + $alte_stunden_eingerechnet = (bool)$lehreinheit->bismelden; + $stundenplan_update = false; - //TODO kollision check, wird vorerst nicht implementiert -> nur über das FAS möglich - if (isset($new_data['mitarbeiter_uid']) && $new_data['mitarbeiter_uid'] !== $mitarbeiter_uid) + if ($old_uid !== $new_uid) { - $this->_ci->load->model('ressource/stundenplandev_model', 'StundenplandevModel'); - $this->_ci->StundenplandevModel->addGroupBy('stundenplandev_id'); - $this->_ci->StundenplandevModel->addGroupBy('mitarbeiter_uid'); - $this->_ci->StundenplandevModel->addGroupBy('mitarbeiter_uid'); - $verplant = $this->_ci->StundenplandevModel->loadWhere(array('lehreinheit_id' => $lehreinheit_id, 'mitarbeiter_uid' => $mitarbeiter_uid)); - - if (hasData($verplant)) - return error($this->_ci->phraseslib->t("lehre", "lektorbereitsverplant")); - - $lehreinheit_data = $this->_ci->LehreinheitmitarbeiterModel->loadWhere(array('mitarbeiter_uid' => $new_data['mitarbeiter_uid'], 'lehreinheit_id' => $lehreinheit_id)); + $lehreinheit_data = $this->_ci->LehreinheitmitarbeiterModel->loadWhere(array('mitarbeiter_uid' => $new_uid, 'lehreinheit_id' => $lehreinheit_id)); if (hasData($lehreinheit_data)) return error($this->_ci->phraseslib->t("lehre", "bereitzugeteilt")); + $this->_ci->StundenplandevModel->addGroupBy('stundenplandev_id'); + $this->_ci->StundenplandevModel->addGroupBy('mitarbeiter_uid'); + $verplant = $this->_ci->StundenplandevModel->loadWhere(array('lehreinheit_id' => $lehreinheit_id, 'mitarbeiter_uid' => $old_uid)); + + if (hasData($verplant)) + { + $kollision = $this->hasKollision(getData($verplant), $new_uid); + + $ignore_kollision = $this->_ci->variablelib->getVar('ignore_kollision'); + + if ($kollision === false || $ignore_kollision == 'true') + { + $stundenplan_update = true; + } + else if (is_array($kollision)) + { + return error( "Änderung fehlgeschlagen! Die Änderung des Lektors führt zu ".count($kollision)." Kollision(en) im LV-Plan. Deaktivieren Sie die Kollisionspruefung oder wenden Sie sich an die LV-Planung!\n zB. $kollision[0]"); + } + else + { + return error($kollision); + } + } } + $warning = ''; - if (isset($new_data['semesterstunden'])) + if (($semesterstunden_neu !== '' && $semesterstunden_alt !== '') && (($semesterstunden_neu > $semesterstunden_alt) || $neue_stunden_eingerechnet)) { $studiengang_result = $this->_ci->StudiengangModel->loadWhere(array('studiengang_kz' => $lehreinheit->studiengang_kz)); if (isError($studiengang_result)) return $studiengang_result; @@ -120,7 +148,7 @@ class LektorLib if (isError($studiensemester_result)) return $studiensemester_result; $studiensemester = getData($studiensemester_result)[0]; - $echter_dv_result = $this->_ci->DienstverhaeltnisModel->existsDienstverhaeltnis($mitarbeiter_uid, $studiensemester->start, $studiensemester->ende, 'echterdv'); + $echter_dv_result = $this->_ci->DienstverhaeltnisModel->existsDienstverhaeltnis($new_uid, $studiensemester->start, $studiensemester->ende, 'echterdv'); $echter_dv = false; @@ -129,83 +157,81 @@ class LektorLib $echter_dv = true; } - $neue_stunden_eingerechnet = isset($new_data['bismelden']) ? $new_data['bismelden'] : $lehreinheit->bismelden; - $alte_stunden_eingerechnet = $lehreinheit->bismelden; + $stundengrenze_result = $this->_ci->OrganisationseinheitModel->getStundengrenze($studiengang->oe_kurzbz, $echter_dv); + if (isError($stundengrenze_result)) return $stundengrenze_result; - if (($new_data['semesterstunden'] > $lehreinheit->semesterstunden) || $neue_stunden_eingerechnet) + $stundengrenze = getData($stundengrenze_result)[0]; + + $oe_result = $this->_ci->OrganisationseinheitModel->getChilds($stundengrenze->oe_kurzbz); + $oe_array = hasData($oe_result) ? array_column(getData($oe_result), 'oe_kurzbz') : array(); + + $semesterstunden_alt_sql = is_null($semesterstunden_alt) ? 0 : $semesterstunden_alt; + $semesterstunden_neu_sql = is_null($semesterstunden_neu) ? 0 : $semesterstunden_neu; + + if ($alte_stunden_eingerechnet && $neue_stunden_eingerechnet) + $this->_ci->LehreinheitmitarbeiterModel->addSelect("(SUM(tbl_lehreinheitmitarbeiter.semesterstunden) - ($semesterstunden_alt_sql) + {$this->_ci->LehreinheitmitarbeiterModel->db->escape($semesterstunden_neu_sql)}) as summe"); + else if ($alte_stunden_eingerechnet && !$neue_stunden_eingerechnet) + $this->_ci->LehreinheitmitarbeiterModel->addSelect("(SUM(tbl_lehreinheitmitarbeiter.semesterstunden) - ($semesterstunden_alt_sql)) as summe"); + else if (!$alte_stunden_eingerechnet && $neue_stunden_eingerechnet) + $this->_ci->LehreinheitmitarbeiterModel->addSelect("(SUM(tbl_lehreinheitmitarbeiter.semesterstunden) + ({$this->_ci->LehreinheitmitarbeiterModel->db->escape($semesterstunden_neu_sql)})) as summe"); + else if (!$alte_stunden_eingerechnet && !$neue_stunden_eingerechnet) + $this->_ci->LehreinheitmitarbeiterModel->addSelect("(SUM(tbl_lehreinheitmitarbeiter.semesterstunden)) as summe"); + + $this->_ci->LehreinheitmitarbeiterModel->addJoin('lehre.tbl_lehreinheit', 'lehreinheit_id'); + $this->_ci->LehreinheitmitarbeiterModel->addJoin('lehre.tbl_lehrveranstaltung', 'lehrveranstaltung_id'); + $this->_ci->LehreinheitmitarbeiterModel->addJoin('public.tbl_studiengang', 'studiengang_kz'); + + $this->_ci->LehreinheitmitarbeiterModel->db->where('mitarbeiter_uid', $new_uid); + $this->_ci->LehreinheitmitarbeiterModel->db->where('studiensemester_kurzbz', $lehreinheit->studiensemester_kurzbz); + $this->_ci->LehreinheitmitarbeiterModel->db->where('bismelden', true); + $this->_ci->LehreinheitmitarbeiterModel->db->where('lower(mitarbeiter_uid) NOT LIKE', '_dummy%'); + + if (count($oe_array) > 0) { - $stundengrenze_result = $this->_ci->OrganisationseinheitModel->getStundengrenze($studiengang->oe_kurzbz, $echter_dv); - if (isError($stundengrenze_result)) return $stundengrenze_result; - - $stundengrenze = getData($stundengrenze_result)[0]; - - $oe_result = $this->_ci->OrganisationseinheitModel->getChilds($stundengrenze->oe_kurzbz); - $oe_array = hasData($oe_result) ? array_column(getData($oe_result), 'oe_kurzbz') : array(''); - - if ($alte_stunden_eingerechnet && $neue_stunden_eingerechnet) - $this->_ci->LehreinheitmitarbeiterModel->addSelect("(SUM(tbl_lehreinheitmitarbeiter.semesterstunden) - ($lehreinheit->semesterstunden) + {$this->_ci->LehreinheitmitarbeiterModel->db->escape($new_data['semesterstunden'])}) as summe"); - else if ($alte_stunden_eingerechnet && !$neue_stunden_eingerechnet) - $this->_ci->LehreinheitmitarbeiterModel->addSelect("(SUM(tbl_lehreinheitmitarbeiter.semesterstunden) - ($lehreinheit->semesterstunden)) as summe"); - else if (!$alte_stunden_eingerechnet && $neue_stunden_eingerechnet) - $this->_ci->LehreinheitmitarbeiterModel->addSelect("(SUM(tbl_lehreinheitmitarbeiter.semesterstunden) + ({$this->_ci->LehreinheitmitarbeiterModel->db->escape($new_data['semesterstunden'])})) as summe"); - else if (!$alte_stunden_eingerechnet && !$neue_stunden_eingerechnet) - $this->_ci->LehreinheitmitarbeiterModel->addSelect("(SUM(tbl_lehreinheitmitarbeiter.semesterstunden)) as summe"); - - $this->_ci->LehreinheitmitarbeiterModel->addJoin('lehre.tbl_lehreinheit', 'lehreinheit_id'); - $this->_ci->LehreinheitmitarbeiterModel->addJoin('lehre.tbl_lehrveranstaltung', 'lehrveranstaltung_id'); - $this->_ci->LehreinheitmitarbeiterModel->addJoin('public.tbl_studiengang', 'studiengang_kz'); - - $this->_ci->LehreinheitmitarbeiterModel->db->where('mitarbeiter_uid', (isset($new_data['mitarbeiter_uid']) ? $new_data['mitarbeiter_uid'] : $mitarbeiter_uid)); - $this->_ci->LehreinheitmitarbeiterModel->db->where('studiensemester_kurzbz', $lehreinheit->studiensemester_kurzbz); - $this->_ci->LehreinheitmitarbeiterModel->db->where('bismelden', true); - $this->_ci->LehreinheitmitarbeiterModel->db->where('lower(mitarbeiter_uid) NOT LIKE', '_dummy%'); - $this->_ci->LehreinheitmitarbeiterModel->db->where_in('tbl_studiengang.oe_kurzbz', $oe_array); + } + if(defined('FAS_LV_LEKTORINNENZUTEILUNG_STUNDEN_IGNORE_OE') + && is_array(FAS_LV_LEKTORINNENZUTEILUNG_STUNDEN_IGNORE_OE) + && count(FAS_LV_LEKTORINNENZUTEILUNG_STUNDEN_IGNORE_OE) > 0) + { + $this->_ci->LehreinheitmitarbeiterModel->db->where_not_in('tbl_studiengang.oe_kurzbz', FAS_LV_LEKTORINNENZUTEILUNG_STUNDEN_IGNORE_OE); + } - if(defined('FAS_LV_LEKTORINNENZUTEILUNG_STUNDEN_IGNORE_OE') - && is_array(FAS_LV_LEKTORINNENZUTEILUNG_STUNDEN_IGNORE_OE) - && count(FAS_LV_LEKTORINNENZUTEILUNG_STUNDEN_IGNORE_OE) > 0) + $summe_result = $this->_ci->LehreinheitmitarbeiterModel->load(); + + if (isError($summe_result)) return $summe_result; + + if (!hasData($summe_result)) return error('Fehler beim Ermitteln der Gesamtstunden'); + + $summe = getData($summe_result)[0]->summe; + + if ($summe > $stundengrenze->stunden) + { + if (!$echter_dv && (!$this->_ci->permissionlib->isBerechtigt('admin'))) { - $this->_ci->LehreinheitmitarbeiterModel->db->where_not_in('tbl_studiengang.oe_kurzbz', FAS_LV_LEKTORINNENZUTEILUNG_STUNDEN_IGNORE_OE); + if (!$this->LehrauftragAufFirma($new_uid)) + return error("ACHTUNG: Die maximal erlaubte Semesterstundenanzahl des Lektors von $summe Stunden ($stundengrenze->stunden) wurde ueberschritten!\nDaten wurden NICHT gespeichert!\n\n"); + } + else + { + $warning .= "ACHTUNG: Die maximal erlaubte Semesterstundenanzahl des Lektors von $summe Stunden ($stundengrenze->stunden) wurde ueberschritten!\nDaten wurden gespeichert!\n\n"; } - $summe_result = $this->_ci->LehreinheitmitarbeiterModel->load(); + $stunden_limit_result = $this->getStundenInstitut($new_uid, $lehreinheit->studiensemester_kurzbz, $oe_array); - if (isError($summe_result)) return $summe_result; - - if (!hasData($summe_result)) return error('Fehler beim Ermitteln der Gesamtstunden'); - - $summe = getData($summe_result)[0]->summe; - - if ($summe > $stundengrenze->stunden) + if (hasData($stunden_limit_result)) { - - if (!$echter_dv && (!$this->_ci->permissionlib->isBerechtigt('admin'))) + $stunden_limit_array = getData($stunden_limit_result); + foreach ($stunden_limit_array as $stunden_limit) { - if (!$this->LehrauftragAufFirma(isset($formData['mitarbeiter_uid']) ? $formData['mitarbeiter_uid'] : $mitarbeiter_uid)) - return error("ACHTUNG: Die maximal erlaubte Semesterstundenanzahl des Lektors von $summe Stunden ($stundengrenze->stunden) wurde ueberschritten!\nDaten wurden NICHT gespeichert!\n\n"); - } - else - { - $warning .= "ACHTUNG: Die maximal erlaubte Semesterstundenanzahl des Lektors von $summe Stunden ($stundengrenze->stunden) wurde ueberschritten!\nDaten wurden gespeichert!\n\n"; - } - - $stunden_limit_result = $this->getStundenInstitut($mitarbeiter_uid, $lehreinheit->studiensemester_kurzbz, $oe_array); - - if (hasData($stunden_limit_result)) - { - $stunden_limit_array = getData($stunden_limit_result); - foreach ($stunden_limit_array as $stunden_limit) - { - $warning .= $stunden_limit->summe . ' Stunden ' . $stunden_limit->bezeichnung . "\n"; - } + $warning .= $stunden_limit->summe . ' Stunden ' . $stunden_limit->bezeichnung . "\n"; } } } } - $benutzer_result = $this->_ci->BenutzerModel->load(array(isset($formData['mitarbeiter_uid']) ? $formData['mitarbeiter_uid'] : $mitarbeiter_uid)); + $benutzer_result = $this->_ci->BenutzerModel->load(array($new_uid)); if (isError($benutzer_result)) return $benutzer_result; @@ -227,12 +253,23 @@ class LektorLib 'bismelden' ); + $nullable_fields = array('stundensatz', 'semesterstunden', 'planstunden'); + $updateData = array(); foreach ($updatableFields as $field) { - $value = isset($new_data[$field]) ? $new_data[$field] : null; + if (!array_key_exists($field, $new_data)) + { + continue; + } - if ($value !== null) + $value = $new_data[$field]; + + if (in_array($field, $nullable_fields)) + { + $updateData[$field] = $value; + } + elseif ($value !== null) { $updateData[$field] = $value; } @@ -240,10 +277,24 @@ class LektorLib $updateData['updatevon'] = getAuthUID(); $updateData['updateamum'] = date('Y-m-d H:i:s'); - $result = $this->_ci->LehreinheitmitarbeiterModel->update(array('lehreinheit_id' => $lehreinheit_id, 'mitarbeiter_uid' => $mitarbeiter_uid), $updateData); + $result = $this->_ci->LehreinheitmitarbeiterModel->update(array('lehreinheit_id' => $lehreinheit_id, 'mitarbeiter_uid' => $old_uid), $updateData); if (isError($result)) return $result; + if ($stundenplan_update) + { + $update_result = $this->_ci->StundenplandevModel->update([ + 'lehreinheit_id' => $lehreinheit_id, + 'mitarbeiter_uid' => $old_uid, + ], [ + 'mitarbeiter_uid' => $new_uid, + 'updateamum' => date('Y-m-d H:i:s'), + 'updatevon' => getAuthUID() + ]); + + if (isError($update_result)) return $update_result; + } + if ($warning !== '') return success(['warning' => $warning]); return success('Erfolgreich geupdated'); @@ -347,4 +398,52 @@ class LektorLib $this->_ci->LehreinheitmitarbeiterModel->addGroupBy('tbl_studiengang.bezeichnung'); return $this->_ci->LehreinheitmitarbeiterModel->load(); } + + private function hasKollision($stunden, $mitarbeiter) + { + $kollision = array(); + + $ignore_zeitsperre = $this->_ci->variablelib->getVar('ignore_zeitsperre'); + $ignore_reservierung = $this->_ci->variablelib->getVar('ignore_reservierung'); + + foreach ($stunden as $stunde) + { + $stundenplan_result = $this->_ci->StundenplandevModel->lektorHasStundenplandevEintrag($mitarbeiter, $stunde->datum, $stunde->stunde); + + if (isError($stundenplan_result)) + return $stundenplan_result; + + if (hasData($stundenplan_result)) + { + $stundenplan_result = getData($stundenplan_result)[0]; + $kollision[] = "Kollision stundenplandev: $stundenplan_result->stundenplandev_id|$stundenplan_result->lektor|$stundenplan_result->ort_kurzbz|$stundenplan_result->stg_kurzbz-$stundenplan_result->semester$stundenplan_result->verband$stundenplan_result->gruppe$stundenplan_result->gruppe_kurzbz - $stundenplan_result->datum/$stundenplan_result->stunde"; + } + else + { + if ($ignore_zeitsperre == 'false' && (!defined('KOLLISIONSFREIE_USER') || !in_array($mitarbeiter, unserialize(KOLLISIONSFREIE_USER)))) + { + $zeitsperre_result = $this->_ci->ZeitsperreModel->checkIfZeitsperreExists($mitarbeiter, $stunde->datum, $stunde->stunde); + + if (hasData($zeitsperre_result)) + { + $zeitsperre_result = getData($zeitsperre_result)[0]; + $kollision[] = "Kollision (Zeitsperre): $zeitsperre_result->zeitsperre_id|$zeitsperre_result->mitarbeiter_uid|$zeitsperre_result->zeitsperretyp_kurzbz - $zeitsperre_result->vondatum/$zeitsperre_result->vonstunde|$zeitsperre_result->bisdatum/$zeitsperre_result->bisstunde"; + } + } + + if ($ignore_reservierung == 'false' && (!defined('KOLLISIONSFREIE_USER') || !in_array($mitarbeiter, unserialize(KOLLISIONSFREIE_USER)))) + { + $reservierung_result = $this->_ci->ReservierungModel->lektorHasReservierung($mitarbeiter, $stunde->datum, $stunde->stunde); + + if (hasData($reservierung_result)) + { + $reservierung_result = getData($reservierung_result)[0]; + $kollision[] = "Kollision (Reservierung): $reservierung_result->reservierung_id|$reservierung_result->uid|$reservierung_result->ort_kurzbz|$reservierung_result->stg_kurzbz-$reservierung_result->semester$reservierung_result->verband$reservierung_result->gruppe$reservierung_result->gruppe_kurzbz - $reservierung_result->datum/$reservierung_result->stunde"; + } + } + } + } + + return isEmptyArray($kollision) ? false : $kollision; + } } diff --git a/application/libraries/StundenplanLib.php b/application/libraries/StundenplanLib.php index a2f8bbfe6..0a460079b 100644 --- a/application/libraries/StundenplanLib.php +++ b/application/libraries/StundenplanLib.php @@ -80,10 +80,12 @@ class StundenplanLib if (isError($semester_range)) return $semester_range; $semester_range = getData($semester_range); + $this->_ci->addMeta('semester_range', $semester_range); $this->sortStudienSemester($semester_range); $function_error = $this->applyLoadUeberSemesterHaelfte($semester_range); + $this->_ci->addMeta('nach_apply_semester_range', $semester_range); if ($function_error) return $function_error; @@ -185,6 +187,21 @@ class StundenplanLib return success($stundenplan_data); } + public function getEventsByLE($lehreinheit_id, $start, $end, $stundenplan) + { + $this->_ci =& get_instance(); + + $this->_ci->load->model('ressource/Stundenplan_model', 'StundenplanModel'); + return $this->_ci->StundenplanModel->getStundenplanLE($lehreinheit_id, $start, $end, $stundenplan); + } + + public function getEventsByLV($lehrveranstaltung_id, $start, $end, $stundenplan) + { + $this->_ci =& get_instance(); + + $this->_ci->load->model('ressource/Stundenplan_model', 'StundenplanModel'); + return $this->_ci->StundenplanModel->getStundenplanLV($lehrveranstaltung_id, $start, $end, $stundenplan); + } /** * Get stundenplan for a room * @@ -1024,7 +1041,8 @@ class StundenplanLib $this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); // gets all studiensemester from the student from start_date to end_date - $semester_range = $this->_ci->StudiensemesterModel->getByDateRange($start_date, $end_date); + //$semester_range = $this->_ci->StudiensemesterModel->getByDateRange($start_date, $end_date); + $semester_range = $this->_ci->StudiensemesterModel->getContainingOrNearestByDateRange($start_date, $end_date); if (isError($semester_range)) return $semester_range; diff --git a/application/libraries/TagLib.php b/application/libraries/TagLib.php new file mode 100644 index 000000000..637431360 --- /dev/null +++ b/application/libraries/TagLib.php @@ -0,0 +1,392 @@ +_ci =& get_instance(); + + // Configs + $this->_ci->load->config('stv'); + + // Models + $this->_ci->load->model('person/Notiz_model', 'NotizModel'); + $this->_ci->load->model('system/Notiztyp_model', 'NotiztypModel'); + $this->_ci->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); + $this->_ci->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel'); + $this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + // Libraries + $this->_ci->load->library('PermissionLib'); + $this->_ci->load->library('PrestudentLib'); + + $this->batchUser = $this->_ci->config->item('userAutomatedTags'); + } + + public function updateAutomatedTags($paramsTag) + { + // --------------------------------- + // check params + // --------------------------------- + $required = ['kurzbz', 'data', 'typeId']; + + foreach ($required as $key) { + if (!isset($paramsTag[$key])) { + return error('Missing Parameter: ' . $key); + } + } + + $studiensemester_kurzbz = (isset ($paramsTag['studiensemester_kurzbz']) ? $paramsTag['studiensemester_kurzbz'] : null); + $tag = $paramsTag['kurzbz']; + $inputData = $paramsTag['data']; + $typeId = $paramsTag['typeId']; + + // --------------------------------- + // prepare input + // --------------------------------- + $zeitraum = []; + $arrayIds = []; + + foreach ($inputData as $item) { + $id = $item['id']; + $arrayIds[] = $id; + + $zeitraum[$id] = [ + 'von' => $item['von'] ?? null, + 'bis' => $item['bis'] ?? null + ]; + } + $arrayIds = array_unique($arrayIds); + + $result = $this->_ci->StudiensemesterModel->load($studiensemester_kurzbz); + if (isError($result)) { + return $result; + } + $data = $result->retval[0] ?? null; + + $von = $data->start ?? null; + $bis = $data->ende ?? null; + + // --------------------------------- + // load existing tags + // --------------------------------- + $allTags = []; + $resultAllTags = $this->_ci->NotizModel->getAllTags($tag, $von, $bis); + if (isError($resultAllTags)) { + return $resultAllTags; + } + $allTagsData = getData($resultAllTags); + + if (!empty($allTagsData)) { + foreach ($allTagsData as $item) { + $allTags[$item->$typeId] = $item->notiz_id; + } + } + + // --------------------------------- + // map the data + // --------------------------------- + $toRecycle = []; + $toAdd = []; + $toDelete = []; + + foreach ($arrayIds as $id) { + if (isset($allTags[$id])) { + $toRecycle[$id] = $allTags[$id]; + } else { + $toAdd[] = $id; + } + } + + foreach ($allTags as $id => $notizId) { + if (!in_array($id, $arrayIds)) { + $toDelete[$id] = $notizId; + + } + } + + // --------------------------------- + // recycle (update existing) + // --------------------------------- + $countRecycled = 0; + $retagged = []; + + foreach ($toRecycle as $id => $notizId) + { + $this->_updateTag($notizId, $zeitraum[$id]['von'], $zeitraum[$id]['bis']); + + $countRecycled++; + $retagged[] = $id; + } + + // --------------------------------- + // ADD + // --------------------------------- + $countAdded = 0; + $tagged = []; + + foreach ($toAdd as $id) + { + $this->_insertTag($typeId, $id, $tag, $zeitraum[$id]['von'], $zeitraum[$id]['bis']); + $countAdded++; + + $tagged[] = $id; + } + + // --------------------------------- + // delete + // --------------------------------- + $countDeleted = 0; + $deleted = []; + + foreach ($toDelete as $id => $notizId) + { + $result = $this->_deleteTag($notizId); + + if (isError($result)) { + return $result; + } + + $countDeleted++; + $deleted[] = $id; + } + + // --------------------------------- + // return + // --------------------------------- + return success([ + 'input' => [ + 'tag' => $tag, + 'arrayIds' => $arrayIds + ], + 'summary' => [ + 'recycled' => $countRecycled, + 'added' => $countAdded, + 'deleted' => $countDeleted + ], + 'details' => [ + 'existingTags' => $allTags, + 'toAdd' => $toAdd, + 'toRecycle' => $toRecycle, + 'toDelete' => $toDelete + ], + 'results' => [ + 'retaggedIds' => $retagged, + 'newTags' => $tagged, + 'deletedTagsIds' => $deleted + ] + ]); + } + + public function updateAutomatedTagsForTypeId(array $params) + { + $return = null; + $notiz_id = null; + + $von = $params['von']; + $bis = $params['bis']; + $tag = $params['kurzbz']; + $id = $params['id']; + $typeId = $params['typeId']; + + $this->_ci->NotizModel->addSelect('nz.notiz_id'); + $this->_ci->NotizModel->addSelect($typeId); + $this->_ci->NotizModel->addJoin('public.tbl_notizzuordnung nz', 'notiz_id'); + $resultAllTags = $this->_ci->NotizModel->loadWhere([ + 'typ' => $tag, + $typeId => $id + ]); + if(hasData($resultAllTags)) + { + $notiz_id = $resultAllTags->retval[0]->notiz_id; + } + + //RECYCLE + if ($notiz_id !== null) + { + $resultUpdateNotiz = $this->_updateTag($notiz_id, $von, $bis); + $return = ['recycled' => $resultUpdateNotiz]; + } + else + //ADD + { + $resultInsertNotiz = $this->_insertTag($typeId, $id, $tag, $von, $bis); + $return = ['added' => $resultInsertNotiz]; + } + return success($return); + } + + /* + * main function for rebuild Tags for typeId + * */ + public function rebuildTagsForTypeId($typeId, $id, $studiensemester_kurzbz) + { + $automatedTagsRes = $this->_ci->NotiztypModel->loadWhere(array('automatisiert' => true, 'taglib IS NOT NULL' => null)); + $automatedTags = hasData($automatedTagsRes) ? getData($automatedTagsRes) : []; + + $result = $this->_ci->StudiensemesterModel->load($studiensemester_kurzbz); + if (isError($result)) + return error('Error occurred during retrieving studiensemester'); + if (empty($result->retval) || !isset($result->retval[0])) { + return error('No studiensemester found'); + } + + $startSem = $result->retval[0]->start ?? null; + $endeSem = $result->retval[0]->ende ?? null; + $return = []; + + foreach ($automatedTags as $autoTag) + { + // getPath: must not be lost + $filePath = APPPATH . 'libraries/' . $autoTag->taglib . '.php'; // APPPATH = application/ + + if (file_exists($filePath)) { + require_once($filePath); + } else { + echo "File not found: " . $filePath; + continue; + } + + $className = basename($autoTag->taglib); + $kurz_bz = $autoTag->typ_kurzbz; + + $obj = new $className(); + + $criteriaIsSet = $obj->isCriteriaSetFor([ + 'typeId' => $typeId, + 'id' => $id, + 'studiensemester_kurzbz' => $studiensemester_kurzbz + ]); + + if (hasData($criteriaIsSet)) + { + $von = isset($criteriaIsSet->retval[0]->von) ? $criteriaIsSet->retval[0]->von : ''; + $bis = isset($criteriaIsSet->retval[0]->bis) ? $criteriaIsSet->retval[0]->bis : ''; + + $params = [ + 'von' => $von, + 'bis' => $bis, + 'kurzbz' => $autoTag->typ_kurzbz, + 'typeId' => $typeId, + 'id' => $id, + ]; + + $result = $this->updateAutomatedTagsForTypeId($params); + if (isError($result)) + return error('Error occurred during updateAutomatedTags' . $kurz_bz); + + $return[$kurz_bz] = $result; + } + else + { + //CHECK FOR DELETE + $params = [ + 'von' => $startSem, + 'bis' => $endeSem, + 'kurzbz' => $autoTag->typ_kurzbz, + 'typeId' => $typeId, + 'id' => $id, + ]; + $result = $this->_ci->NotizModel->checkIfExistingTag($kurz_bz, $typeId, $id, $startSem, $endeSem); + if (hasData($result)) + { + $notizId = $result->retval[0]->notiz_id; + $this->_deleteTag($notizId); + $return[$kurz_bz] = ['deleted' => $notizId]; + } + } + } + return success($return); + } + + private function _insertTag($typeId, $id, $tag, $von, $bis) + { + $resultInsert = $this->_ci->NotizModel->insert([ + 'titel' => 'TAG', + 'text' => 'AUTOMATED TAG', + 'verfasser_uid' => $this->batchUser, + 'erledigt' => false, + 'insertamum' => date('Y-m-d H:i:s'), + 'insertvon' => 'BatchJobTagAdd', + 'typ' => $tag, + 'start' => $von, + 'ende' => $bis + ]); + + if (isError($resultInsert)) { + return error('Error inserting tag for ' . $typeId . ': ' . $id); + } + + $notizId = $resultInsert->retval; + + $resultZuordnung = $this->_ci->NotizzuordnungModel->insert([ + 'notiz_id' => $notizId, + $typeId => $id + ]); + + if (isError($resultZuordnung)) { + return error('Error inserting relation for ' . $typeId . ': ' . $id); + } + + return $notizId; + } + + private function _updateTag($notiz_id, $von, $bis) + { + $resultUpdateNotiz = $this->_ci->NotizModel->update( + [ + 'notiz_id' => $notiz_id + ], + array( + 'updateamum' => date('Y-m-d H:i:s'), + 'updatevon' => 'BatchJobTagUpdate', + 'start' => $von, + 'ende' => $bis + )); + + + if (isError($resultUpdateNotiz)) + return error ('Error occurred during Update ' . $notiz_id); + + return $notiz_id; + } + + private function _deleteTag($notiz_id) + { + $result = $this->_ci->NotizzuordnungModel->delete([ + 'notiz_id' => $notiz_id + ]); + if (isError($result)) { + return error('Error occurred during delete Notizzuordnung ' . $notiz_id); + } + + $result = $this->_ci->NotizModel->delete([ + 'notiz_id' => $notiz_id + ]); + if (isError($result)) { + return error('Error occurred during delete Notiz ' . $notiz_id); + } + + return success([ + 'deleted' => $notiz_id + ]); + } + +} diff --git a/application/libraries/stv/StudentListLib.php b/application/libraries/stv/StudentListLib.php index bb44c8bb0..9365ddc9a 100644 --- a/application/libraries/stv/StudentListLib.php +++ b/application/libraries/stv/StudentListLib.php @@ -28,6 +28,7 @@ class StudentListLib private $_allowedStgs = []; private $_selects = []; private $_joins = []; + private $_wheres = []; /** * Gets the CI instance, loads model and prepares default values @@ -40,6 +41,8 @@ class StudentListLib { $this->_ci =& get_instance(); // get code igniter instance + $this->_ci->load->config('stv'); + $this->_ci->load->model('crm/Prestudent_model', 'PrestudentModel'); if (isset($params['allowedStgs'])) @@ -47,8 +50,6 @@ class StudentListLib // Add default SELECTs $this->addSelect("b.uid"); - if (defined('STV_TAGS_ENABLED') && STV_TAGS_ENABLED) - $this->addSelect('tag_data_agg.tags'); $this->addSelect('titelpre'); $this->addSelect('nachname'); $this->addSelect('vorname'); @@ -72,6 +73,13 @@ class StudentListLib $this->addSelect('pls.status_kurzbz AS status'); $this->addSelect('pls.datum AS status_datum'); $this->addSelect('pls.bestaetigtam AS status_bestaetigung'); + $this->addSelect(" + CASE + WHEN pls.status_kurzbz IN ('Interessent','Aufgenommener','Bewerber','Wartender') + THEN pls.ausbildungssemester + ELSE v.semester + END AS semester_berechnet + "); $this->addSelect( "(SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp='email' AND person_id=p.person_id AND zustellung LIMIT 1) AS mail_privat", false @@ -111,7 +119,7 @@ class StudentListLib ) prest WHERE laststatus NOT IN ('Abbrecher', 'Abgewiesener', 'Absolvent') AND priorisierung <= tbl_prestudent.priorisierung - ) || ' (' || COALESCE(tbl_prestudent.priorisierung::text, ' '::text) || ')' AS priorisierung_relativ", false); // TODO(chris): overwrite in fetchStudents + ) || ' (' || COALESCE(tbl_prestudent.priorisierung::text, ' '::text) || ')' AS priorisierung_relativ", false); $this->addSelect('mentor'); $this->addSelect('b.aktiv AS bnaktiv'); $this->addSelect('unruly'); @@ -119,7 +127,7 @@ class StudentListLib // Add default JOINs $this->addJoin('public.tbl_studiengang stg', 'studiengang_kz', 'LEFT'); $this->addJoin('public.tbl_person p', 'person_id'); - $this->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT'); // TODO(chris): overwrite in fetchStudents + $this->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT'); $this->addJoin('public.tbl_prestudentstatus pls', ' pls.status_kurzbz=public.get_rolle_prestudent(tbl_prestudent.prestudent_id, NULL) AND pls.prestudent_id=tbl_prestudent.prestudent_id @@ -129,40 +137,14 @@ class StudentListLib $this->addJoin('public.tbl_benutzer b', 's.student_uid=b.uid', 'LEFT'); $this->addJoin("v", "", ""); // Will be replaced later $this->addJoin("ps", "", ""); // Will be replaced later - if (defined('STV_TAGS_ENABLED') && STV_TAGS_ENABLED) { - $this->_ci->load->config('stv'); - $tags = $this->_ci->config->item('stv_prestudent_tags'); - $whereTags = ''; - if (is_array($tags) && !isEmptyArray($tags)) { - $tags = array_keys($tags); - - foreach ($tags as $key => $tag) { - $tags[$key] = $this->_ci->PrestudentModel->escape($tag); - } - $whereTags = " AND nt.typ_kurzbz IN (" . implode(",", $tags) . ")"; - } - $subQueryTag = "( - SELECT - tag.prestudent_id, - COALESCE(json_agg(tag ORDER BY tag.done), '[]'::json) AS tags - FROM ( - SELECT DISTINCT ON (n.notiz_id) - n.notiz_id AS id, - nt.typ_kurzbz, - array_to_json(nt.bezeichnung_mehrsprachig)->>0 AS beschreibung, - n.text AS notiz, - nt.style, - n.erledigt AS done, - nz.prestudent_id - FROM public.tbl_notizzuordnung AS nz - JOIN public.tbl_notiz AS n ON nz.notiz_id = n.notiz_id - JOIN public.tbl_notiz_typ AS nt ON n.typ = nt.typ_kurzbz " . $whereTags . " - ) AS tag - GROUP BY tag.prestudent_id - ) AS tag_data_agg"; - - $this->addJoin($subQueryTag, 'tag_data_agg.prestudent_id = tbl_prestudent.prestudent_id', 'LEFT'); + // Add SELECTs and JOINs from config + $config = $this->_ci->config->item('list_columns') ?: []; + foreach ($config as $conf) { + $this->addSelect($conf['default']); + if (isset($conf['joins'])) + foreach ($conf['joins'] as $join) + call_user_func_array([$this, 'addJoin'], $join); } } @@ -249,11 +231,7 @@ class StudentListLib */ public function addWhere($key, $value = null, $escape = true) { - if (!is_array($key) && is_array($value)) { - $this->_ci->PrestudentModel->db->where_in($key, $value, $escape); - } else { - $this->_ci->PrestudentModel->db->where($key, $value, $escape); - } + $this->_wheres[] = [$key, $value, $escape, false]; } /** @@ -267,11 +245,7 @@ class StudentListLib */ public function addOrWhere($key, $value = null, $escape = true) { - if (!is_array($key) && is_array($value)) { - $this->_ci->PrestudentModel->db->or_where_in($key, $value, $escape); - } else { - $this->_ci->PrestudentModel->db->or_where($key, $value, $escape); - } + $this->_wheres[] = [$key, $value, $escape, true]; } /** @@ -283,8 +257,9 @@ class StudentListLib */ public function execute($studiensemester_kurzbz) { - $stdsemEsc = $studiensemester_kurzbz ? $this->_ci->PrestudentModel->escape($studiensemester_kurzbz) : 'NULL'; + $this->addSelectAndJoinForTagsIfConfigured($studiensemester_kurzbz); + $stdsemEsc = $studiensemester_kurzbz ? $this->_ci->PrestudentModel->escape($studiensemester_kurzbz) : 'NULL'; $this->addSelect( "public.get_rolle_prestudent( @@ -315,6 +290,20 @@ class StudentListLib foreach ($this->_selects as $select) $this->_ci->PrestudentModel->addSelect($select[0], $select[1]); + foreach ($this->_wheres as $where) { + if (!is_array($where[0]) && is_array($where[1])) { + if ($where[3]) + $this->_ci->PrestudentModel->db->or_where_in($where[0], $where[1], $where[2]); + else + $this->_ci->PrestudentModel->db->where_in($where[0], $where[1], $where[2]); + } else { + if ($where[3]) + $this->_ci->PrestudentModel->db->or_where($where[0], $where[1], $where[2]); + else + $this->_ci->PrestudentModel->db->where($where[0], $where[1], $where[2]); + } + } + $this->_ci->PrestudentModel->addOrder('nachname'); $this->_ci->PrestudentModel->addOrder('vorname'); @@ -325,6 +314,78 @@ class StudentListLib //------------------------------------------------------------------------------------------------------------------ // Protected methods + /** + * Add Select and Join for Tags if configured + * + * @param string studiensemester_kurzbz + * + */ + protected function addSelectAndJoinForTagsIfConfigured($studiensemester_kurzbz) + { + if (defined('STV_TAGS_ENABLED') && STV_TAGS_ENABLED) { + $this->_ci->load->config('stv'); + $tags = $this->_ci->config->item('stv_prestudent_tags'); + + $whereTags = ''; + if (is_array($tags) && !isEmptyArray($tags)) { + $tags = array_keys($tags); + + foreach ($tags as $key => $tag) { + $tags[$key] = $this->_ci->PrestudentModel->escape($tag); + } + $whereTags = " AND nt.typ_kurzbz IN (" . implode(",", $tags) . ")"; + } + $studiensemester_kurzbz_escaped = $this->_ci->PrestudentModel->escape($studiensemester_kurzbz); + $subQueryTag = "( + SELECT + tag.prestudent_id, + COALESCE(json_agg(tag ORDER BY tag.done, tag.prioritaet), '[]'::json) AS tags + FROM ( + SELECT DISTINCT ON (n.notiz_id) + n.notiz_id AS id, + nt.typ_kurzbz, + nt.bezeichnung_mehrsprachig[( + SELECT index + FROM tbl_sprache + WHERE sprache=" . $this->_ci->PrestudentModel->escape(getUserLanguage()) . " + )] AS beschreibung, + n.text AS notiz, + nt.style, + n.erledigt AS done, + nt.prioritaet AS prioritaet, + nt.automatisiert AS automatisiert, + nz.prestudent_id, + n.start, + n.ende + FROM public.tbl_notizzuordnung AS nz + JOIN public.tbl_notiz AS n ON nz.notiz_id = n.notiz_id AND nz.prestudent_id IS NOT NULL + JOIN public.tbl_notiz_typ AS nt ON n.typ = nt.typ_kurzbz " . $whereTags . " + WHERE + COALESCE(n.start, '1970-01-01') <= ( + SELECT + ende + FROM + public.tbl_studiensemester + WHERE + studiensemester_kurzbz = " . $studiensemester_kurzbz_escaped . " + ) + AND COALESCE(n.ende, '2170-12-31') >= ( + SELECT + start + FROM + public.tbl_studiensemester + WHERE + studiensemester_kurzbz = " . $studiensemester_kurzbz_escaped . " + ) + ) AS tag + GROUP BY tag.prestudent_id + ) AS tag_data_agg"; + + $this->addSelect('tag_data_agg.tags'); + $this->addJoin($subQueryTag, 'tag_data_agg.prestudent_id = tbl_prestudent.prestudent_id', 'LEFT'); + } + } + /** * Get alias of a table or select statement * diff --git a/application/libraries/tags/CoreDoubleDegreeTagLib.php b/application/libraries/tags/CoreDoubleDegreeTagLib.php new file mode 100644 index 000000000..13b113a21 --- /dev/null +++ b/application/libraries/tags/CoreDoubleDegreeTagLib.php @@ -0,0 +1,81 @@ +ci = get_instance(); + $this->ci->load->model('codex/Mobilitaet_model', 'MobilitaetModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $this->ci->MobilitaetModel->addJoin('bis.tbl_gsprogramm', 'gsprogramm_id'); + $this->ci->MobilitaetModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + + $result = $this->ci->MobilitaetModel-> loadWhere(array( + 'gsprogrammtyp_kurzbz' => 'Double', + 'studiensemester_kurzbz' => $semester + )); + $data = $result->retval; + + $doubledegree_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->start, + 'bis' => $item->ende + ]; + }, $data); + + return (object) array( + 'data' => $doubledegree_data, + 'typeId' => 'prestudent_id' + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $this->ci->MobilitaetModel->addSelect('prestudent_id'); + $this->ci->MobilitaetModel->addSelect('start as von'); + $this->ci->MobilitaetModel->addSelect('ende as bis'); + + $this->ci->MobilitaetModel->addJoin('bis.tbl_gsprogramm', 'gsprogramm_id'); + $this->ci->MobilitaetModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + + $result = $this->ci->MobilitaetModel->loadWhere(array( + 'gsprogrammtyp_kurzbz' => 'Double', + 'studiensemester_kurzbz' => $semester, + 'prestudent_id' => $prestudent_id + )); + + if(hasData($result)) + { + //array mit prestudent_id, von und bis + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CoreFiftyFiveTagLib.php b/application/libraries/tags/CoreFiftyFiveTagLib.php new file mode 100644 index 000000000..4e2738e8b --- /dev/null +++ b/application/libraries/tags/CoreFiftyFiveTagLib.php @@ -0,0 +1,81 @@ +ci = get_instance(); + $this->ci->load->model('person/Person_model', 'PersonModel'); + $this->ci-> load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'person_id' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $result = $this->ci->StudiensemesterModel->loadWhere(array( + 'studiensemester_kurzbz' => $semester + )); + $data = $result->retval[0]; + + $semVon = $data->start; + $semBis = $data->ende; + $result = $this->ci->PersonModel->getFiftyFivers($semVon, $semBis); + + $data = $result->retval; + $fiftyFiveData = array_map(function($item) { + return [ + 'id' => $item->person_id + ]; + }, $data); + + return (object) array( + 'data' => $fiftyFiveData, + 'typeId' => 'person_id' + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'person_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $person_id = $params['id']; + $typeId = $params['typeId']; + + + $result = $this->ci->StudiensemesterModel->loadWhere(array( + 'studiensemester_kurzbz' => $semester + )); + $data = $result->retval[0]; + + $semVon = $data->start; + $semBis = $data->ende; + $result = $this->ci->PersonModel->isFiftyFive($semVon, $semBis, $person_id); + + + if(hasData($result)) + { + return $result; + } + else + return null; + } + +} + diff --git a/application/libraries/tags/CoreIncomingTagLib.php b/application/libraries/tags/CoreIncomingTagLib.php new file mode 100644 index 000000000..26d7dad0b --- /dev/null +++ b/application/libraries/tags/CoreIncomingTagLib.php @@ -0,0 +1,63 @@ +ci = get_instance(); + $this->ci->load->model('codex/Bisio_model', 'BisioModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $result = $this->ci->BisioModel->getIncomingsOfSemester($semester); + + $data = $result->retval; + + $outgoing_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->von, + 'bis' => $item->bis + ]; + }, $data); + + return (object) array( + 'data' => $outgoing_data, + 'typeId' => 'prestudent_id', + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $result = $this->ci->BisioModel->isPrestudentIncoming($semester, $prestudent_id); + + if (hasData($result)) { + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CoreJgvTagLib.php b/application/libraries/tags/CoreJgvTagLib.php new file mode 100644 index 000000000..15e77dc98 --- /dev/null +++ b/application/libraries/tags/CoreJgvTagLib.php @@ -0,0 +1,64 @@ +ci = get_instance(); + $this->ci->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $result = $this->ci->BenutzerfunktionModel->getPrestudentsOfJgv($semester); + + $data = $result->retval; + + $jgv_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->datum_von, + 'bis' => $item->datum_bis + ]; + }, $data); + + return (object) array( + 'data' => $jgv_data, + 'typeId' => 'prestudent_id' + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $result = $this->ci->BenutzerfunktionModel->isJgv($semester, $prestudent_id); + + if(hasData($result)) + { + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CoreMissingZgvTagLib.php b/application/libraries/tags/CoreMissingZgvTagLib.php new file mode 100644 index 000000000..e25aed4cc --- /dev/null +++ b/application/libraries/tags/CoreMissingZgvTagLib.php @@ -0,0 +1,81 @@ +ci = get_instance(); + $this->ci->load->model('crm/Prestudent_model', 'PrestudentModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $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'); + $result = $this->ci->PrestudentModel-> loadWhere(array( + 'bn.aktiv' => true, //check if necessary + 'zgvdatum' => null, + 'typ' => 'b', + 'studiensemester_kurzbz' => $semester + )); + + $data = $result->retval; + + $zgvmissing_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => null, + 'bis' => null + ]; + }, $data); + + return (object) array( + 'typeId' => 'prestudent_id', + 'data' => $zgvmissing_data + ); + + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $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'); + $result = $this->ci->PrestudentModel->loadWhere(array( + 'bn.aktiv' => true, //check if necessary + 'zgvdatum' => null, + 'typ' => 'b', + 'studiensemester_kurzbz' => $semester, + 'prestudent_id' => $prestudent_id + )); + if(hasData($result)) + { + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CoreOutgoingTagLib.php b/application/libraries/tags/CoreOutgoingTagLib.php new file mode 100644 index 000000000..904839dee --- /dev/null +++ b/application/libraries/tags/CoreOutgoingTagLib.php @@ -0,0 +1,63 @@ +ci = get_instance(); + $this->ci->load->model('codex/Bisio_model', 'BisioModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $result = $this->ci->BisioModel->getOutgoingsOfSemester($semester); + + $data = $result->retval; + + $outgoing_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->von, + 'bis' => $item->bis + ]; + }, $data); + + return (object) array( + 'data' => $outgoing_data, + 'typeId' => 'prestudent_id', + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $result = $this->ci->BisioModel->isPrestudentOutgoing($semester, $prestudent_id); + + if (hasData($result)) { + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CorePreabbrecherTagLib.php b/application/libraries/tags/CorePreabbrecherTagLib.php new file mode 100644 index 000000000..41712ac3b --- /dev/null +++ b/application/libraries/tags/CorePreabbrecherTagLib.php @@ -0,0 +1,80 @@ +ci = get_instance(); + $this->ci->load->model('education/Studierendenantrag_model', 'StudierendenantragModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $this->ci->StudierendenantragModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + $this->ci->StudierendenantragModel->db->where_in('typ', self::TYP_ABMELDUNG); + $this->ci->StudierendenantragModel->db->where('studiensemester_kurzbz', $semester); + $result = $this->ci->StudierendenantragModel->getWithLastStatusWhere([ + 'studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_CREATED + ]); + + $data = $result->retval; + + $prewiederholer_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->start, + 'bis' => $item->ende + ]; + }, $data); + + return (object) array( + 'data' => $prewiederholer_data, + 'typeId' => 'prestudent_id', + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $this->ci->StudierendenantragModel->addSelect('prestudent_id'); + $this->ci->StudierendenantragModel->addSelect('start as von'); + $this->ci->StudierendenantragModel->addSelect('ende as bis'); + + $this->ci->StudierendenantragModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + $this->ci->StudierendenantragModel->db->where_in('typ', self::TYP_ABMELDUNG); + $this->ci->StudierendenantragModel->db->where('studiensemester_kurzbz', $semester); + $this->ci->StudierendenantragModel->db->where('prestudent_id', $prestudent_id); + $result = $this->ci->StudierendenantragModel->getWithLastStatusWhere([ + 'studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_CREATED + ]); + + if(hasData($result)) + { + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CorePrewiederholerTagLib.php b/application/libraries/tags/CorePrewiederholerTagLib.php new file mode 100644 index 000000000..8b7037b07 --- /dev/null +++ b/application/libraries/tags/CorePrewiederholerTagLib.php @@ -0,0 +1,76 @@ +ci = get_instance(); + $this->ci->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $this->ci->PrestudentstatusModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + $result = $this->ci->PrestudentstatusModel-> loadWhere(array( + 'statusgrund_id' => 15, + 'studiensemester_kurzbz' => $semester + )); + $data = $result->retval; + + $prewiederholer_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->start, + 'bis' => $item->ende + ]; + }, $data); + + return (object) array( + 'data' => $prewiederholer_data, + 'typeId' => 'prestudent_id', + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $this->ci->PrestudentstatusModel->addSelect('prestudent_id'); + $this->ci->PrestudentstatusModel->addSelect('start as von'); + $this->ci->PrestudentstatusModel->addSelect('ende as bis'); + + $this->ci->PrestudentstatusModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + + $result = $this->ci->PrestudentstatusModel->loadWhere(array( + 'statusgrund_id' => 15, + 'studiensemester_kurzbz' => $semester, + 'prestudent_id' => $prestudent_id + )); + if(hasData($result)) + { + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CoreStbErhoehtTagLib.php b/application/libraries/tags/CoreStbErhoehtTagLib.php new file mode 100644 index 000000000..886aff468 --- /dev/null +++ b/application/libraries/tags/CoreStbErhoehtTagLib.php @@ -0,0 +1,79 @@ +ci = get_instance(); + $this->ci->load->model('crm/Konto_model', 'KontoModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $this->ci->KontoModel->addJoin('public.tbl_prestudent', 'person_id'); + $this->ci->KontoModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + + $result = $this->ci->KontoModel-> loadWhere(array( + 'buchungstyp_kurzbz' => 'StudiengebuehrErhoeht', + 'studiensemester_kurzbz' => $semester + )); + $data = $result->retval; + + $konto_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->start, + 'bis' => $item->ende + ]; + }, $data); + + return (object) array( + 'data' => $konto_data, + 'typeId' => 'prestudent_id' + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $this->ci->KontoModel->addSelect('prestudent_id'); + $this->ci->KontoModel->addSelect('start as von'); + $this->ci->KontoModel->addSelect('ende as bis'); + $this->ci->KontoModel->addJoin('public.tbl_prestudent', 'person_id'); + $this->ci->KontoModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + + $result = $this->ci->KontoModel-> loadWhere(array( + 'buchungstyp_kurzbz' => 'StudiengebuehrErhoeht', + 'studiensemester_kurzbz' => $semester, + 'prestudent_id' => $prestudent_id + )); + + if(hasData($result)) + { + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CoreUnterbrecherTagLib.php b/application/libraries/tags/CoreUnterbrecherTagLib.php new file mode 100644 index 000000000..ddf8f33cd --- /dev/null +++ b/application/libraries/tags/CoreUnterbrecherTagLib.php @@ -0,0 +1,76 @@ +ci = get_instance(); + $this->ci->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + $this->ci->PrestudentstatusModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + + $result = $this->ci->PrestudentstatusModel-> loadWhere(array( + 'status_kurzbz' => 'Unterbrecher', + 'studiensemester_kurzbz' => $semester + )); + $data = $result->retval; + + $unterbrecher_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->start, + 'bis' => $item->ende + ]; + }, $data); + + return (object) array( + 'data' => $unterbrecher_data, + 'typeId' => 'prestudent_id' + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $this->ci->PrestudentstatusModel->addSelect('prestudent_id'); + $this->ci->PrestudentstatusModel->addSelect('start as von'); + $this->ci->PrestudentstatusModel->addSelect('ende as bis'); + + $this->ci->PrestudentstatusModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + $result = $this->ci->PrestudentstatusModel-> loadWhere(array( + 'status_kurzbz' => 'Unterbrecher', + 'studiensemester_kurzbz' => $semester, + 'prestudent_id' => $prestudent_id + )); + + if(hasData($result)) + { + return $result; + } + else + return null; + } + +} diff --git a/application/libraries/tags/CoreWiederholerTagLib.php b/application/libraries/tags/CoreWiederholerTagLib.php new file mode 100644 index 000000000..8f4eb1dd7 --- /dev/null +++ b/application/libraries/tags/CoreWiederholerTagLib.php @@ -0,0 +1,76 @@ +ci = get_instance(); + $this->ci->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel'); + } + + public function getZuordnungIds(array $params) + { + if(!isset($params['studiensemester_kurzbz'])) + { + return (object) array( + 'idArray' => [] + ); + } + + $semester = $params['studiensemester_kurzbz']; + + $this->ci->PrestudentstatusModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + $result = $this->ci->PrestudentstatusModel-> loadWhere(array( + 'statusgrund_id' => 16, + 'studiensemester_kurzbz' => $semester + )); + $data = $result->retval; + + $wiederholer_data = array_map(function($item) { + return [ + 'id' => $item->prestudent_id, + 'von' => $item->start, + 'bis' => $item->ende + ]; + }, $data); + + return (object) array( + 'typeId' => 'prestudent_id', + 'data' => $wiederholer_data + ); + } + + public function isCriteriaSetFor(array $params) + { + if ( !isset($params['id'], $params['studiensemester_kurzbz'], $params['typeId']) || $params['typeId'] !== 'prestudent_id') + return false; + + $semester = $params['studiensemester_kurzbz']; + $prestudent_id = $params['id']; + + $this->ci->PrestudentstatusModel->addSelect('prestudent_id'); + $this->ci->PrestudentstatusModel->addSelect('start as von'); + $this->ci->PrestudentstatusModel->addSelect('ende as bis'); + + $this->ci->PrestudentstatusModel->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + + $result = $this->ci->PrestudentstatusModel->loadWhere(array( + 'statusgrund_id' => 16, + 'studiensemester_kurzbz' => $semester, + 'prestudent_id' => $prestudent_id, + )); + + if(hasData($result)) + { + return $result; + } + else + return null; + } +} diff --git a/application/libraries/treemenu/LvVwLib.php b/application/libraries/treemenu/LvVwLib.php new file mode 100644 index 000000000..d3c91a196 --- /dev/null +++ b/application/libraries/treemenu/LvVwLib.php @@ -0,0 +1,192 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * LvVw Menu library + */ +class LvVwLib +{ + protected $_ci = null; + + public $config = []; + + public function __construct() + { + // Get code igniter instance + $this->_ci =& get_instance(); + + // Load Tree-config + $this->_ci->load->config('treemenu/custom/lvvw'); + $this->config = $this->_ci->config->item('root'); + + // Load Libraries + $this->_ci->load->library('treemenu/base/StgLib'); + + // Load Model + $this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel'); + } + + public function stg($path_template) + { + $permittedStgs = $this->getPermittedStudiengangKzs(); + + if (!$permittedStgs) + return []; + + $this->_ci->StudiengangModel->addSelect("ARRAY['link', FORMAT('{" . + '"type": "verband",' . + '"id": "lv/%1$s",' . + '"gruppe_kurzbz": null,' . + '"semester": null,' . + '"verband": null,' . + '"gruppe": null,' . + '"lehrverband": true,' . + '"stg_kz": "%1$s"' . + "}', studiengang_kz)] AS draggable", false); + + return $this->_ci->stglib->studiengang($path_template, $permittedStgs); + } + + public function semester($path_template, $has_children, $stg) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + if (!$has_children) + $this->_ci->StudiengangModel->db->select('TRUE AS leaf', false); + $this->_ci->StudiengangModel->addSelect("ARRAY['link', FORMAT('{" . + '"type": "verband",' . + '"id": "lv/%1$s/%2$s",' . + '"gruppe_kurzbz": null,' . + '"semester": "%2$s",' . + '"verband": null,' . + '"gruppe": null,' . + '"lehrverband": true,' . + '"stg_kz": "%1$s"' . + "}', v.studiengang_kz, v.semester)] AS draggable", false); + + return $this->_ci->stglib->semester($path_template, $stg->studiengang_kz); + } + + public function verband($path_template, $stg, $semester) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + $this->_ci->StudiengangModel->addSelect("CASE WHEN MAX(gruppe)='' OR MAX(gruppe)=' ' THEN TRUE ELSE FALSE END AS leaf"); + $this->_ci->StudiengangModel->addSelect("ARRAY['link', FORMAT('{" . + '"type": "verband",' . + '"id": "lv/%1$s/%2$s/%3$s",' . + '"gruppe_kurzbz": null,' . + '"semester": "%2$s",' . + '"verband": "%3$s",' . + '"gruppe": null,' . + '"lehrverband": true,' . + '"stg_kz": "%1$s"' . + "}', v.studiengang_kz, v.semester, v.verband)] AS draggable", false); + $this->_ci->StudiengangModel->addGroupBy('v.studiengang_kz, v.semester'); + + return $this->_ci->stglib->verband($path_template, $stg->studiengang_kz, $semester); + } + + public function group($path_template, $stg, $semester, $verband) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + $this->_ci->StudiengangModel->db->select('TRUE AS leaf', false); + + if ($verband === null) { + $this->_ci->StudiengangModel->addSelect("ARRAY['link', FORMAT('{" . + '"type": "verband",' . + '"id": "grp/%1$s/%2$s/%3$s",' . + '"gruppe_kurzbz": "%3$s",' . + '"semester": "%2$s",' . + '"verband": null,' . + '"gruppe": null,' . + '"lehrverband": false,' . + '"stg_kz": "%1$s"' . + "}', studiengang_kz, semester, gruppe_kurzbz)] AS draggable", false); + + return $this->_ci->stglib->group($path_template, $stg->studiengang_kz, $semester); + } + + $this->_ci->StudiengangModel->addSelect("ARRAY['link', FORMAT('{" . + '"type": "verband",' . + '"id": "lv/%1$s/%2$s/%3$s/%4$s",' . + '"gruppe_kurzbz": null,' . + '"semester": "%2$s",' . + '"verband": "%3$s",' . + '"gruppe": "%4$s",' . + '"lehrverband": true,' . + '"stg_kz": "%1$s"' . + "}', v.studiengang_kz, v.semester, v.verband, v.gruppe)] AS draggable", false); + + return $this->_ci->stglib->verbandsgroup($path_template, $stg->studiengang_kz, $semester, $verband); + } + + public function orgform($path_template, $stg) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + if (!$stg->mischform) + return []; + + $this->_ci->StudiengangModel->db->select('TRUE AS leaf', false); + + return $this->_ci->stglib->orgform($path_template, $stg->studiengang_kz); + } + + protected function getPermittedStudiengangKzs() + { + $permittedStgs = $this->_ci->permissionlib->getSTG_isEntitledFor('admin') ?: []; + $permittedStgs = array_merge($permittedStgs, $this->_ci->permissionlib->getSTG_isEntitledFor('assistenz') ?: []); + + return $permittedStgs; + } + + protected function getStgFromUrl($stg) + { + $permittedStgs = $this->getPermittedStudiengangKzs(); + + if (!$permittedStgs) + return null; + + $this->_ci->StudiengangModel->db->where_in('studiengang_kz', $permittedStgs); + $stg = $this->_ci->StudiengangModel->loadWhere([ + 'LOWER(CONCAT(typ, kurzbz)) =' => $stg + ]); + + if (!hasData($stg)) + return null; + + return current(getData($stg)); + } +} diff --git a/application/libraries/treemenu/StudVwLib.php b/application/libraries/treemenu/StudVwLib.php new file mode 100644 index 000000000..ee3e2cbdc --- /dev/null +++ b/application/libraries/treemenu/StudVwLib.php @@ -0,0 +1,257 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * StudVw Menu library + */ +class StudVwLib +{ + protected $_ci = null; + + public $config = []; + + public $redirect_method = [ + 'interessenten' => 'filter', + 'bewerbungnichtabgeschickt' => 'filter', + 'bewerbungabgeschickt' => 'filter', + 'zgv' => 'filter', + 'statusbestaetigt' => 'filter', + 'statusbestaetigtrtnichtangemeldet' => 'filter', + 'statusbestaetigtrtangemeldet' => 'filter', + 'reihungstestnichtangemeldet' => 'filter', + 'reihungstestangemeldet' => 'filter', + 'bewerber' => 'filter', + 'bewerberrtnichtangemeldet' => 'filter', + 'bewerberrtangemeldet' => 'filter', + 'bewerberrtangemeldetteilgenommen' => 'filter', + 'bewerberrtangemeldetnichtteilgenommen' => 'filter', + 'aufgenommen' => 'filter', + 'warteliste' => 'filter', + 'absage' => 'filter', + 'outgoing' => 'inout', + 'shared_studies' => 'inout' + ]; + + public function __construct() + { + // Get code igniter instance + $this->_ci =& get_instance(); + + // Load Tree-config + $this->_ci->load->config('treemenu/stg_base'); + $this->config = $this->_ci->config->item('root'); + + // Load Libraries + $this->_ci->load->library('treemenu/base/StgLib'); + + // Load Model + $this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel'); + } + + public function stg($path_template) + { + $permittedStgs = $this->getPermittedStudiengangKzs(); + + if (!$permittedStgs) + return []; + + return $this->_ci->stglib->studiengang($path_template, $permittedStgs); + } + + public function prestudent($path_template, $stg) + { + // Check permission + if ($this->getStgFromUrl($stg) === null) + return []; + + return [ + [ + 'path' => sprintf($path_template, '1'), + 'name' => ['lehre', 'prestudent'], + 'no_sem_reload' => true + ] + ]; + } + + public function stdsem($path_template, $stg) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + return $this->_ci->stglib->studiensemester($path_template, $stg->studiengang_kz); + } + + public function semester($path_template, $stg, $orgform) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + if ($orgform !== null) { + $orgform = $this->getOrgform($orgform); + if ($orgform === null) + return []; + $orgform = $orgform->orgform_kurzbz; + } + + return $this->_ci->stglib->semester($path_template, $stg->studiengang_kz, $orgform); + } + + public function verband($path_template, $stg, $semester, $orgform) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + if ($orgform !== null) { + $orgform = $this->getOrgform($orgform); + if ($orgform === null) + return []; + $orgform = $orgform->orgform_kurzbz; + } + + $this->_ci->StudiengangModel->addSelect("CASE WHEN MAX(gruppe)='' OR MAX(gruppe)=' ' THEN TRUE ELSE FALSE END AS leaf"); + + return $this->_ci->stglib->verband($path_template, $stg->studiengang_kz, $semester, $orgform); + } + + public function group($path_template, $stg, $semester, $verband, $orgform) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + if ($orgform !== null) { + $orgform = $this->getOrgform($orgform); + if ($orgform === null) + return []; + $orgform = $orgform->orgform_kurzbz; + } + + $this->_ci->StudiengangModel->db->select('TRUE AS leaf', false); + + if ($verband === null) + return $this->_ci->stglib->group($path_template, $stg->studiengang_kz, $semester, $orgform); + + return $this->_ci->stglib->verbandsgroup($path_template, $stg->studiengang_kz, $semester, $verband, $orgform); + } + + public function orgform($path_template, $stg) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + if (!$stg->mischform) + return []; + + return $this->_ci->stglib->orgform($path_template, $stg->studiengang_kz); + } + + public function incoming($has_children, $path_template, $stg) + { + if ($stg === null) + return $this->inout('incoming', $has_children, $path_template); + + return $this->filter('incoming', $has_children, $path_template, $stg); + } + + public function filter($original_method, $has_children, $path_template, $stg) + { + $stg = $this->getStgFromUrl($stg); + + if ($stg === null) + return []; + + // TODO(chris): phrasen + $item = [ + 'path' => sprintf($path_template, '1'), + 'name' => ['treemenu', 'filter_' . $original_method] + ]; + + if (!$has_children) + $item['leaf'] = true; + + return [ $item ]; + } + + public function inout($original_method, $has_children, $path_template) + { + if (!$this->_ci->permissionlib->isBerechtigt('inout/uebersicht')) + return []; + + // TODO(chris): phrasen + $item = [ + 'path' => sprintf($path_template, '1'), + 'name' => ['treemenu', 'inout_' . $original_method] + ]; + + if (!$has_children) + $item['leaf'] = true; + + return [ $item ]; + } + + protected function getOrgform($orgform) + { + $this->_ci->load->model('codex/Orgform_model', 'OrgformModel'); + $orgform = $this->_ci->OrgformModel->loadWhere([ + 'LOWER(orgform_kurzbz) =' => $orgform + ]); + + if (!hasData($orgform)) + return null; + + return current(getData($orgform)); + } + + protected function getPermittedStudiengangKzs() + { + $permittedStgs = $this->_ci->permissionlib->getSTG_isEntitledFor('admin') ?: []; + $permittedStgs = array_merge($permittedStgs, $this->_ci->permissionlib->getSTG_isEntitledFor('assistenz') ?: []); + + return $permittedStgs; + } + + protected function getStgFromUrl($stg) + { + $permittedStgs = $this->getPermittedStudiengangKzs(); + + if (!$permittedStgs) + return null; + + $this->_ci->StudiengangModel->db->where_in('studiengang_kz', $permittedStgs); + $stg = $this->_ci->StudiengangModel->loadWhere([ + 'LOWER(CONCAT(typ, kurzbz)) =' => $stg + ]); + + if (!hasData($stg)) + return null; + + return current(getData($stg)); + } +} diff --git a/application/libraries/treemenu/base/StgLib.php b/application/libraries/treemenu/base/StgLib.php new file mode 100644 index 000000000..84665639d --- /dev/null +++ b/application/libraries/treemenu/base/StgLib.php @@ -0,0 +1,313 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * Stg Base library + */ +class StgLib +{ + protected $_ci = null; + + public function __construct() + { + // Get code igniter instance + $this->_ci =& get_instance(); + } + + public function studiengang($path_template, $permittedStgs = []) + { + $this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $this->_ci->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); + + $this->_ci->StudiengangModel->addDistinct(); + $this->_ci->StudiengangModel->addSelect("FORMAT(" . $this->_ci->StudiengangModel->escape($path_template) . ", LOWER(CONCAT(typ, kurzbz))) AS path"); + $this->_ci->StudiengangModel->addSelect( + "CONCAT(kurzbzlang, ' (', UPPER(CONCAT(typ, kurzbz)), ') - ', tbl_studiengang.bezeichnung) AS name", + false + ); + $this->_ci->StudiengangModel->addSelect("studiengang_kz AS title"); + $this->_ci->StudiengangModel->addSelect("studiengang_kz AS search"); + $this->_ci->StudiengangModel->addSelect('erhalter_kz'); + $this->_ci->StudiengangModel->addSelect('typ'); + $this->_ci->StudiengangModel->addSelect('kurzbz'); + $this->_ci->StudiengangModel->addSelect('studiengang_kz'); + $this->_ci->StudiengangModel->addSelect('studiengang_kz AS stg_kz'); + + $this->_ci->StudiengangModel->addOrder('erhalter_kz'); + $this->_ci->StudiengangModel->addOrder('typ'); + $this->_ci->StudiengangModel->addOrder('kurzbz'); + + if ($permittedStgs) + $this->_ci->StudiengangModel->db->where_in('studiengang_kz', $permittedStgs); + + $result = $this->_ci->StudiengangModel->loadWhere(['v.aktiv' => true]); + + return getData($result) ?: []; + } + + public function semester($path_template, $studiengang_kz, $orgform_kurzbz = null) + { + $this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $this->_ci->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); + + $this->_ci->StudiengangModel->addDistinct(); + $this->_ci->StudiengangModel->addSelect("FORMAT(" . $this->_ci->StudiengangModel->escape($path_template) . ", semester) AS path", false); + $this->_ci->StudiengangModel->addSelect("CONCAT( + UPPER(CONCAT(typ, kurzbz)), + '-', + semester, + ( + SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END + FROM public.tbl_lehrverband + WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester + ORDER BY verband, gruppe LIMIT 1 + ) + ) AS name", false); + + $this->_ci->StudiengangModel->addSelect('semester'); + $this->_ci->StudiengangModel->addSelect('v.studiengang_kz AS stg_kz'); + $this->_ci->StudiengangModel->addSelect("ARRAY['link-strict', 'student-collection'] AS droplink"); + + $this->_ci->StudiengangModel->addOrder('semester'); + + if ($orgform_kurzbz !== null) { + $this->_ci->StudiengangModel->addSelect($this->_ci->StudiengangModel->escape($orgform_kurzbz) . " AS orgform_kurzbz", false); + $this->_ci->StudiengangModel->addDistinct(); + $this->_ci->StudiengangModel->db->group_start(); + $this->_ci->StudiengangModel->db->where('v.semester', 0); + $this->_ci->StudiengangModel->db->or_where('v.orgform_kurzbz', $orgform_kurzbz); + $this->_ci->StudiengangModel->db->group_end(); + } + + $result = $this->_ci->StudiengangModel->loadWhere([ + 'studiengang_kz' => $studiengang_kz, + 'v.aktiv' => true + ]); + + + return getData($result) ?: []; + } + + public function group($path_template, $studiengang_kz, $semester, $orgform_kurzbz = null) + { + $this->_ci->load->model('organisation/Gruppe_model', 'GruppeModel'); + + $this->_ci->GruppeModel->addDistinct(); + $this->_ci->GruppeModel->addSelect("FORMAT(" . $this->_ci->GruppeModel->escape($path_template) . ", gruppe_kurzbz) AS path", false); + $this->_ci->GruppeModel->addSelect("CONCAT(gruppe_kurzbz, ' (', bezeichnung, ')') AS name", false); + + $this->_ci->GruppeModel->addSelect('sort'); + $this->_ci->GruppeModel->addSelect('gruppe_kurzbz'); + $this->_ci->GruppeModel->addSelect($this->_ci->GruppeModel->escape($studiengang_kz) . '::integer AS stg_kz', false); + $this->_ci->GruppeModel->addSelect("ARRAY['link-strict', 'student-collection'] AS droplink"); + + $this->_ci->GruppeModel->addOrder('sort'); + $this->_ci->GruppeModel->addOrder('gruppe_kurzbz'); + + $where = [ + 'studiengang_kz' => $studiengang_kz, + 'semester' => $semester, + 'lehre' => true, + 'sichtbar' => true, + 'aktiv' => true, + 'direktinskription' => false + ]; + + if ($orgform_kurzbz !== null) + $where['orgform_kurzbz'] = $orgform_kurzbz; + + $result = $this->_ci->GruppeModel->loadWhere($where); + + return getData($result) ?: []; + } + + public function verband($path_template, $studiengang_kz, $semester, $orgform_kurzbz = null) + { + $this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $this->_ci->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); + + $this->_ci->StudiengangModel->addSelect("FORMAT(" . $this->_ci->StudiengangModel->escape($path_template) . ", verband) AS path", false); + $this->_ci->StudiengangModel->addSelect("CONCAT( + UPPER(CONCAT(typ, kurzbz)), + '-', + semester, + verband, + ( + SELECT + CASE + WHEN bezeichnung IS NULL OR bezeichnung='' + THEN ''::TEXT + ELSE CONCAT(' (', bezeichnung, ')') + END + FROM public.tbl_lehrverband + WHERE studiengang_kz=v.studiengang_kz + AND semester=v.semester + AND verband=v.verband + ORDER BY gruppe + LIMIT 1 + ) + ) AS name", false); + + $this->_ci->StudiengangModel->addSelect($this->_ci->StudiengangModel->escape($semester) . ' AS semester'); + $this->_ci->StudiengangModel->addSelect('verband'); + $this->_ci->StudiengangModel->addSelect($this->_ci->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false); + $this->_ci->StudiengangModel->addSelect("ARRAY['link-strict', 'student-collection'] AS droplink"); + + $this->_ci->StudiengangModel->addOrder('verband'); + + $this->_ci->StudiengangModel->addGroupBy('path, name, verband'); + + $where = [ + 'v.studiengang_kz' => $studiengang_kz, + 'v.semester' => $semester, + 'v.verband !=' => '', + 'v.aktiv' => true + ]; + + if ($orgform_kurzbz !== null && $semester) // NOTE(chris): on semester 0 show all? + $where['v.orgform_kurzbz'] = $orgform_kurzbz; + + $result = $this->_ci->StudiengangModel->loadWhere($where); + + return getData($result) ?: []; + } + + public function verbandsgroup($path_template, $studiengang_kz, $semester, $verband, $orgform_kurzbz = null) + { + $this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel'); + + $this->_ci->StudiengangModel->addJoin('public.tbl_lehrverband v', 'studiengang_kz'); + + $this->_ci->StudiengangModel->addDistinct(); + $this->_ci->StudiengangModel->addSelect("FORMAT( + " . $this->_ci->StudiengangModel->escape($path_template) . ", + gruppe + ) AS path", false); + $this->_ci->StudiengangModel->addSelect("CONCAT( + UPPER(CONCAT(typ, kurzbz)), + '-', + semester, + verband, + gruppe, + ( + SELECT + CASE + WHEN bezeichnung IS NULL OR bezeichnung='' + THEN ''::TEXT + ELSE CONCAT(' (', bezeichnung, ')') + END + FROM public.tbl_lehrverband + WHERE studiengang_kz=v.studiengang_kz + AND semester=v.semester + AND verband=v.verband + AND gruppe=v.gruppe + ORDER BY gruppe + LIMIT 1 + ) + ) AS name", false); + + $this->_ci->StudiengangModel->addSelect('v.semester'); + $this->_ci->StudiengangModel->addSelect('v.verband'); + $this->_ci->StudiengangModel->addSelect('gruppe'); + $this->_ci->StudiengangModel->addSelect($this->_ci->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false); + $this->_ci->StudiengangModel->addSelect("ARRAY['link-strict', 'student-collection'] AS droplink"); + + $this->_ci->StudiengangModel->addOrder('gruppe'); + + $where = [ + 'v.studiengang_kz' => $studiengang_kz, + 'v.semester' => $semester, + 'v.verband' => $verband, + 'v.gruppe !=' => '', + 'v.aktiv' => true + ]; + + if ($orgform_kurzbz !== null && $semester) // NOTE(chris): on semester 0 show all? + $where['v.orgform_kurzbz'] = $orgform_kurzbz; + + $result = $this->_ci->StudiengangModel->loadWhere($where); + + return getData($result) ?: []; + } + + public function orgform($path_template, $studiengang_kz) + { + $this->_ci->load->model('organisation/Studienordnung_model', 'StudienordnungModel'); + + $this->_ci->StudienordnungModel->addDistinct(); + $this->_ci->StudienordnungModel->addSelect("FORMAT( + " . $this->_ci->StudienordnungModel->escape($path_template) . ", + LOWER(p.orgform_kurzbz) + ) AS path"); + $this->_ci->StudienordnungModel->addSelect("p.orgform_kurzbz AS name"); + $this->_ci->StudienordnungModel->addSelect("studiengang_kz AS stg_kz"); + + $this->_ci->StudienordnungModel->addJoin('lehre.tbl_studienplan p', 'studienordnung_id'); + + $result = $this->_ci->StudienordnungModel->loadWhere([ + 'aktiv' => true, + 'studiengang_kz' => $studiengang_kz, + 'p.orgform_kurzbz !=' => 'DDP' + ]); + + + return getData($result) ?: []; + } + + public function studiensemester($path_template, $studiengang_kz) + { + $number_displayed_past_studiensemester = null; + + $this->_ci->load->model('system/Variable_model', 'VariableModel'); + + $result = $this->_ci->VariableModel->getVariables(getAuthUID(), ['number_displayed_past_studiensemester']); + if (isError($result)) + return []; + + $data = getData($result); + if ($data && isset($data['number_displayed_past_studiensemester'])) { + $number_displayed_past_studiensemester = $data['number_displayed_past_studiensemester']; + } else { + $this->_ci->load->config('stv'); + $number_displayed_past_studiensemester = $this->_ci->config->item('number_displayed_past_studiensemester_default'); + } + + $this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); + + $this->_ci->StudiensemesterModel->addPlusMinus(null, $number_displayed_past_studiensemester); + + $this->_ci->StudiensemesterModel->addSelect("studiensemester_kurzbz AS name"); + $this->_ci->StudiensemesterModel->addSelect("FORMAT( + " . $this->_ci->StudiensemesterModel->escape($path_template) . ", + LOWER(studiensemester_kurzbz) + ) AS path", false); + $this->_ci->StudiensemesterModel->addSelect($this->_ci->StudiensemesterModel->escape($studiengang_kz) . " AS stg_kz", false); + + $this->_ci->StudiensemesterModel->addOrder('ende'); + + $result = $this->_ci->StudiensemesterModel->load(); + + return getData($result) ?: []; + } +} diff --git a/application/models/codex/Bisio_model.php b/application/models/codex/Bisio_model.php index 1cff1dc54..972661c8b 100644 --- a/application/models/codex/Bisio_model.php +++ b/application/models/codex/Bisio_model.php @@ -44,4 +44,110 @@ class Bisio_model extends DB_Model else return success("Bisio not found"); } + + /** + * Gets outgoing students of certain Semester + * @param String studiensemester_kurzbz + * @return array of prestudent_ids + */ + public function getOutgoingsOfSemester($studiensemester_kurzbz) + { + $query = " + SELECT DISTINCT ps.prestudent_id, tbl_bisio.von, tbl_bisio.bis + FROM bis.tbl_bisio + JOIN public.tbl_student USING (student_uid) + JOIN public.tbl_prestudent ps USING (prestudent_id) + JOIN public.tbl_prestudentstatus pss ON (ps.prestudent_id = pss.prestudent_id) + JOIN public.tbl_studiensemester ss ON (pss.studiensemester_kurzbz = ss.studiensemester_kurzbz) + WHERE ss.studiensemester_kurzbz = ? + AND nation_code <> 'A' + AND ( + tbl_bisio.von <= ss.ende + AND ( + tbl_bisio.bis >= ss.start + OR tbl_bisio.bis IS NULL + ) + ) + "; + + return $this->execQuery($query, array($studiensemester_kurzbz)); + } + + /** + * Gets incoming students of certain Semester + * @param String studiensemester_kurzbz + * @return array of prestudent_ids + */ + public function getIncomingsOfSemester($studiensemester_kurzbz) + { + $query = " + SELECT DISTINCT ps.prestudent_id, tbl_bisio.von, tbl_bisio.bis + FROM bis.tbl_bisio + JOIN public.tbl_student USING (student_uid) + JOIN public.tbl_prestudent ps USING (prestudent_id) + JOIN public.tbl_prestudentstatus pss ON (ps.prestudent_id = pss.prestudent_id) + JOIN public.tbl_studiensemester ss ON (pss.studiensemester_kurzbz = ss.studiensemester_kurzbz) + WHERE ss.studiensemester_kurzbz = ? + AND nation_code = 'A' + AND ( + tbl_bisio.von <= ss.ende + AND ( + tbl_bisio.bis >= ss.start + OR tbl_bisio.bis IS NULL + ) + ) + "; + + return $this->execQuery($query, array($studiensemester_kurzbz)); + } + + public function isPrestudentOutgoing($studiensemester_kurzbz, $prestudent_id) + { + $query = " + SELECT + ps.prestudent_id, tbl_bisio.von, tbl_bisio.bis + FROM bis.tbl_bisio + JOIN public.tbl_student USING (student_uid) + JOIN public.tbl_prestudent ps USING (prestudent_id) + JOIN public.tbl_prestudentstatus pss ON (ps.prestudent_id = pss.prestudent_id) + JOIN public.tbl_studiensemester ss ON (pss.studiensemester_kurzbz = ss.studiensemester_kurzbz) + WHERE ss.studiensemester_kurzbz = ? + AND nation_code <> 'A' + AND ( + tbl_bisio.von <= ss.ende + AND ( + tbl_bisio.bis >= ss.start + OR tbl_bisio.bis IS NULL + ) + ) + AND ps.prestudent_id = ? + "; + + return $this->execQuery($query, array($studiensemester_kurzbz, $prestudent_id)); + } + + public function isPrestudentIncoming($studiensemester_kurzbz, $prestudent_id) + { + $query = " + SELECT + ps.prestudent_id, tbl_bisio.von, tbl_bisio.bis + FROM bis.tbl_bisio + JOIN public.tbl_student USING (student_uid) + JOIN public.tbl_prestudent ps USING (prestudent_id) + JOIN public.tbl_prestudentstatus pss ON (ps.prestudent_id = pss.prestudent_id) + JOIN public.tbl_studiensemester ss ON (pss.studiensemester_kurzbz = ss.studiensemester_kurzbz) + WHERE ss.studiensemester_kurzbz = ? + AND nation_code = 'A' + AND ( + tbl_bisio.von <= ss.ende + AND ( + tbl_bisio.bis >= ss.start + OR tbl_bisio.bis IS NULL + ) + ) + AND ps.prestudent_id = ? + "; + + return $this->execQuery($query, array($studiensemester_kurzbz, $prestudent_id)); + } } diff --git a/application/models/crm/Dokument_model.php b/application/models/crm/Dokument_model.php index 8008202c1..d292068d8 100644 --- a/application/models/crm/Dokument_model.php +++ b/application/models/crm/Dokument_model.php @@ -57,7 +57,7 @@ class Dokument_model extends DB_Model $qry = " SELECT a.akte_id, - a.bezeichnung, + dok.bezeichnung, a.dokument_kurzbz, a.titel_intern, a.anmerkung_intern, @@ -83,6 +83,7 @@ class Dokument_model extends DB_Model inhalt FROM public.tbl_akte a + JOIN public.tbl_dokument dok ON (dok.dokument_kurzbz = a.dokument_kurzbz) WHERE a.person_id = ? AND a.dokument_kurzbz NOT IN ( diff --git a/application/models/education/Lehreinheit_model.php b/application/models/education/Lehreinheit_model.php index fed5b9d23..8a242cf2a 100644 --- a/application/models/education/Lehreinheit_model.php +++ b/application/models/education/Lehreinheit_model.php @@ -719,7 +719,7 @@ EOSQL; return "tag_data_agg AS ( SELECT lehreinheit_id, - COALESCE(json_agg(tag ORDER BY done), '[]'::json) AS tags + COALESCE(json_agg(tag ORDER BY done, prioritaet), '[]'::json) AS tags FROM ( SELECT DISTINCT ON (public.tbl_notiz.notiz_id) tbl_notiz.notiz_id AS id, @@ -728,7 +728,9 @@ EOSQL; text AS notiz, style, erledigt AS done, - lehreinheit_id + lehreinheit_id, + prioritaet, + automatisiert FROM public.tbl_notizzuordnung JOIN public.tbl_notiz ON tbl_notizzuordnung.notiz_id = tbl_notiz.notiz_id JOIN public.tbl_notiz_typ ON tbl_notiz.typ = tbl_notiz_typ.typ_kurzbz diff --git a/application/models/education/Lehreinheitgruppe_model.php b/application/models/education/Lehreinheitgruppe_model.php index dee8bbfe1..139d639cb 100644 --- a/application/models/education/Lehreinheitgruppe_model.php +++ b/application/models/education/Lehreinheitgruppe_model.php @@ -341,7 +341,7 @@ class Lehreinheitgruppe_model extends DB_Model $this->db->where('lehreinheit_id', $lehreinheit_id); $this->db->where('studiengang_kz', $gruppen_array->studiengang_kz); - if (!isEmptyString($gruppen_array->semester)) + if (!isEmptyString((string)$gruppen_array->semester)) { $this->db->where('semester', $gruppen_array->semester); } @@ -444,30 +444,37 @@ class Lehreinheitgruppe_model extends DB_Model ) ELSE tbl_gruppe.beschreibung END AS beschreibung"); - $this->addSelect("CASE - WHEN tbl_lehreinheitgruppe.gruppe_kurzbz IS NULL THEN - ( - SELECT EXISTS ( - SELECT 1 - FROM lehre.tbl_stundenplandev - WHERE lehreinheit_id = tbl_lehreinheitgruppe.lehreinheit_id - AND studiengang_kz = tbl_lehreinheitgruppe.studiengang_kz - AND semester = tbl_lehreinheitgruppe.semester - AND TRIM(COALESCE(verband, '')) = TRIM(tbl_lehreinheitgruppe.verband) - AND TRIM(COALESCE(gruppe, '')) = TRIM(tbl_lehreinheitgruppe.gruppe) - AND (gruppe_kurzbz IS NULL OR gruppe_kurzbz = '') - ) - ) - ELSE + $this->addSelect(" + CASE + WHEN trim(COALESCE(tbl_lehreinheitgruppe.gruppe_kurzbz, '')) = '' THEN ( SELECT EXISTS ( SELECT 1 - FROM lehre.tbl_stundenplandev - WHERE lehreinheit_id = tbl_lehreinheitgruppe.lehreinheit_id - AND gruppe_kurzbz = tbl_lehreinheitgruppe.gruppe_kurzbz + FROM lehre.tbl_stundenplandev sp + WHERE sp.lehreinheit_id = tbl_lehreinheitgruppe.lehreinheit_id + AND sp.studiengang_kz = tbl_lehreinheitgruppe.studiengang_kz + AND sp.semester = tbl_lehreinheitgruppe.semester + AND trim(COALESCE(sp.verband, '')) = trim(COALESCE(tbl_lehreinheitgruppe.verband, '')) + AND trim(COALESCE(sp.gruppe, '')) = trim(COALESCE(tbl_lehreinheitgruppe.gruppe, '')) + AND trim(COALESCE(sp.gruppe_kurzbz, '')) = '' ) ) - END AS verplant"); + ELSE + ( + SELECT EXISTS ( + SELECT 1 + FROM lehre.tbl_stundenplandev sp + WHERE sp.lehreinheit_id = tbl_lehreinheitgruppe.lehreinheit_id + AND sp.studiengang_kz = tbl_lehreinheitgruppe.studiengang_kz + AND sp.semester = tbl_lehreinheitgruppe.semester + AND trim(COALESCE(sp.verband, '')) = trim(COALESCE(tbl_lehreinheitgruppe.verband, '')) + AND trim(COALESCE(sp.gruppe, '')) = trim(COALESCE(tbl_lehreinheitgruppe.gruppe, '')) + AND trim(COALESCE(sp.gruppe_kurzbz, '')) = trim(COALESCE(tbl_lehreinheitgruppe.gruppe_kurzbz, '')) + ) + ) + END AS verplant + "); + $this->addJoin('tbl_studiengang', 'studiengang_kz', 'LEFT'); $this->addJoin('public.tbl_gruppe', 'gruppe_kurzbz', 'LEFT'); diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 9e08e0d56..3b94ab3bd 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -1245,7 +1245,7 @@ class Lehrveranstaltung_model extends DB_Model { return " SELECT - lehrveranstaltung_id, tbl_lehrveranstaltung.kurzbz as lv_kurzbz, tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung, bezeichnung_english as lv_bezeichnung_english, studiengang_kz, + distinct on (lehrveranstaltung_id) lehrveranstaltung_id, tbl_lehrveranstaltung.kurzbz as lv_kurzbz, tbl_lehrveranstaltung.bezeichnung as lv_bezeichnung, bezeichnung_english as lv_bezeichnung_english, studiengang_kz, tbl_studienplan_lehrveranstaltung.semester, tbl_lehrveranstaltung.sprache, ects as lv_ects, semesterstunden, anmerkung, lehre, lehreverzeichnis as lv_lehreverzeichnis, tbl_lehrveranstaltung.aktiv, planfaktor as lv_planfaktor, planlektoren as lv_planlektoren, planpersonalkosten as lv_planpersonalkosten, diff --git a/application/models/education/Lvgesamtnote_model.php b/application/models/education/Lvgesamtnote_model.php index 44645e75b..c7d9ea3df 100644 --- a/application/models/education/Lvgesamtnote_model.php +++ b/application/models/education/Lvgesamtnote_model.php @@ -29,10 +29,14 @@ class Lvgesamtnote_model extends DB_Model $this->addSelect("lv.bezeichnung AS lehrveranstaltung_bezeichnung"); $this->addSelect("lv.studiengang_kz"); $this->addSelect("UPPER(stg.typ || stg.kurzbz) AS studiengang"); + $this->addSelect("person.vorname"); + $this->addSelect("person.nachname"); $this->addJoin("lehre.tbl_note n", "note"); $this->addJoin("lehre.tbl_lehrveranstaltung lv", "lehrveranstaltung_id"); $this->addJoin("public.tbl_studiengang stg", "studiengang_kz"); + $this->addJoin("public.tbl_benutzer benutzer", "uid = student_uid", "LEFT"); + $this->addJoin("public.tbl_person person", "person_id", "LEFT"); $this->db->where($this->dbTable . ".freigabedatum <", "NOW()", false); diff --git a/application/models/education/Studierendenantrag_model.php b/application/models/education/Studierendenantrag_model.php index 40224ffc9..c18d18053 100644 --- a/application/models/education/Studierendenantrag_model.php +++ b/application/models/education/Studierendenantrag_model.php @@ -188,6 +188,7 @@ class Studierendenantrag_model extends DB_Model * Get the studiengang and ausbildungssemester the student was in * for the studiensemester the antrag was committed for * + * * @param integer $antrag_id * * @return stdClass @@ -242,6 +243,28 @@ class Studierendenantrag_model extends DB_Model ); } + /** + * Get the studiengang and the studienplan the student is in + * + * @param integer $antrag_id + * + * @return stdClass + */ + public function getStgAndDetails($antrag_id) + { + $this->addSelect('p.studiengang_kz'); + $this->addSelect('stg.orgform_kurzbz'); + $this->addSelect('stg.bezeichnung'); + $this->addJoin('public.tbl_prestudent p', 'prestudent_id'); + $this->addJoin('public.tbl_studiengang stg', 'studiengang_kz'); + + $this->addLimit(1); + + return $this->load( + $antrag_id + ); + } + public function getStgEmail($antrag_id) { $this->addJoin('public.tbl_prestudent p', 'prestudent_id'); diff --git a/application/models/education/Zeugnisnote_model.php b/application/models/education/Zeugnisnote_model.php index f32713dec..694c5d5c0 100644 --- a/application/models/education/Zeugnisnote_model.php +++ b/application/models/education/Zeugnisnote_model.php @@ -148,7 +148,7 @@ class Zeugnisnote_model extends DB_Model * * @return object */ - public function getZeugnisnoten($student_uid, $studiensemester_kurzbz) + public function getZeugnisnoten($student_uid, $studiensemester_kurzbz, $lehrveranstaltung_id = null) { $params = array(); $where=''; @@ -163,6 +163,11 @@ class Zeugnisnote_model extends DB_Model $where.=" AND vw_student_lehrveranstaltung.studiensemester_kurzbz= ?"; $params[] = $studiensemester_kurzbz; } + if($lehrveranstaltung_id != null) + { + $where .= " AND vw_student_lehrveranstaltung.lehrveranstaltung_id = ?"; + $params[] = $lehrveranstaltung_id; + } $where2=''; @@ -176,6 +181,11 @@ class Zeugnisnote_model extends DB_Model $where2 .= " AND studiensemester_kurzbz= ?"; $params[] = $studiensemester_kurzbz; } + if($lehrveranstaltung_id != null) + { + $where2 .=" AND lehrveranstaltung_id = ?"; + $params[] = $lehrveranstaltung_id; + } $qry = "SELECT a.*, @@ -188,7 +198,10 @@ class Zeugnisnote_model extends DB_Model lv.semester AS semester_lv, lv.ects AS ects_lv, lv.zeugnis, - lv.bezeichnung_english AS lehrveranstaltung_bezeichnung_english + lv.bezeichnung_english AS lehrveranstaltung_bezeichnung_english, + s.verband, + person.vorname, + person.nachname FROM ( SELECT vw_student_lehrveranstaltung.lehrveranstaltung_id, uid, vw_student_lehrveranstaltung.studiensemester_kurzbz, note, punkte, uebernahmedatum, benotungsdatum, @@ -231,6 +244,8 @@ class Zeugnisnote_model extends DB_Model ORDER BY sort ) a LEFT JOIN public.tbl_student s ON (a.uid = s.student_uid) + LEFT JOIN public.tbl_benutzer benutzer ON benutzer.uid = s.student_uid + LEFT JOIN public.tbl_person person ON benutzer.person_id = person.person_id LEFT JOIN public.tbl_studiengang stg1 ON (s.studiengang_kz = stg1.studiengang_kz) LEFT JOIN lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id) LEFT JOIN public.tbl_studiengang stg2 ON (lv.studiengang_kz = stg2.studiengang_kz)"; diff --git a/application/models/organisation/Studiensemester_model.php b/application/models/organisation/Studiensemester_model.php index e9c7f06d3..8aee8f65e 100644 --- a/application/models/organisation/Studiensemester_model.php +++ b/application/models/organisation/Studiensemester_model.php @@ -229,6 +229,82 @@ class Studiensemester_model extends DB_Model return $this->execQuery($query, array($date,$date,$date,$date)); } + /** + * Gets all Studiensemester where the range dates are contained + * or the nearest if date is not contained + * @param $from + * @param $to + * @return array|null + */ + public function getContainingOrNearestByDateRange($from, $to) + { + if (date_format(date_create($from), 'Y-m-d') > (date_format(date_create($to), 'Y-m-d'))) + return success(array()); + + $query = <<db->escape($from)}::date BETWEEN s.start AND s.ende + ORDER BY + s.start DESC + LIMIT 1 + ), + semester_interval_enddate AS ( + SELECT + s.* + FROM + public.tbl_studiensemester s + WHERE + {$this->db->escape($to)}::date BETWEEN s.start AND s.ende + ORDER BY + s.start DESC + LIMIT 1 + ), + nearest_semester_interval_startdate AS ( + SELECT + LEAST(ABS(s.start - {$this->db->escape($from)}::date), ABS(s.ende - {$this->db->escape($from)}::date)) AS mindiff, + s.* + FROM + public.tbl_studiensemester s + WHERE + s.ende < {$this->db->escape($from)}::date + ORDER BY + 1 ASC + LIMIT 1 + ), + nearest_semester_interval_enddate AS ( + SELECT + LEAST(ABS(s.start - {$this->db->escape($to)}::date), ABS(s.ende - {$this->db->escape($to)}::date)) AS mindiff, + s.* + FROM + public.tbl_studiensemester s + WHERE + s.start > {$this->db->escape($to)}::date + ORDER BY + 1 ASC + LIMIT 1 + ) + SELECT + COALESCE( + (SELECT studiensemester_kurzbz FROM semester_interval_startdate), + (SELECT studiensemester_kurzbz FROM nearest_semester_interval_startdate) + ) AS studiensemester_kurzbz + UNION + SELECT + COALESCE( + (SELECT studiensemester_kurzbz FROM semester_interval_enddate), + (SELECT studiensemester_kurzbz FROM nearest_semester_interval_enddate) + ) AS studiensemester_kurzbz + +EOSQL; + + return $this->execReadonlyQuery($query); + } + /** * Gets all Studiensemester between two dates * @param $from diff --git a/application/models/person/Benutzerfunktion_model.php b/application/models/person/Benutzerfunktion_model.php index dfed7c332..52236ba7c 100644 --- a/application/models/person/Benutzerfunktion_model.php +++ b/application/models/person/Benutzerfunktion_model.php @@ -348,5 +348,64 @@ class Benutzerfunktion_model extends DB_Model return success($funktionJson); } + /** + * Gets all Prestudents with details for a given Benutzerfunktion and optionally semester + * + * @param String $studiensemester_kurzbz + * @return object |null + */ + public function getPrestudentsOfJgv($semester) + { + $query = " + SELECT DISTINCT ps.prestudent_id, bf.datum_von, bf.datum_bis + FROM public.tbl_benutzerfunktion bf + JOIN public.tbl_benutzer bn USING (uid) + JOIN public.tbl_prestudent ps USING (person_id) + JOIN public.tbl_prestudentstatus pss ON (ps.prestudent_id = pss.prestudent_id) + JOIN public.tbl_studiensemester ss ON (pss.studiensemester_kurzbz = ss.studiensemester_kurzbz) + WHERE ss.studiensemester_kurzbz = ? + AND bf.funktion_kurzbz = 'jgv' + AND ( + bf.datum_von <= ss.ende + AND ( + bf.datum_bis >= ss.start + OR bf.datum_bis IS NULL + ) + ) + "; + + return $this->execQuery($query, array($semester)); + } + + /** + * Checks if a certain prestudent has the Benutzerfunktion jgv for a certain semester + * + * @param String $studiensemester_kurzbz + * @param $prestudent_id + * @return object |null + */ + public function isJgv($semester, $prestudent_id) + { + $query = " + SELECT ps.prestudent_id, ss.start as von, ss.ende as bis + FROM public.tbl_benutzerfunktion bf + JOIN public.tbl_benutzer bn USING (uid) + JOIN public.tbl_prestudent ps USING (person_id) + JOIN public.tbl_prestudentstatus pss ON (ps.prestudent_id = pss.prestudent_id) + JOIN public.tbl_studiensemester ss ON (pss.studiensemester_kurzbz = ss.studiensemester_kurzbz) + WHERE ss.studiensemester_kurzbz = ? + AND bf.funktion_kurzbz = 'jgv' + AND ( + bf.datum_von <= ss.ende + AND ( + bf.datum_bis >= ss.start + OR bf.datum_bis IS NULL + ) + ) + AND ps.prestudent_id = ? + "; + + return $this->execQuery($query, array($semester, $prestudent_id)); + } } diff --git a/application/models/person/Notiz_model.php b/application/models/person/Notiz_model.php index 64fce8944..1dc09caaf 100644 --- a/application/models/person/Notiz_model.php +++ b/application/models/person/Notiz_model.php @@ -296,4 +296,59 @@ class Notiz_model extends DB_Model return $this->loadWhere(array('anrechnung_id' => $anrechnung_id)); } + + /** + * check if a given Tag for a certain notizzuordnung id is valid + * + * @param $tag typ_kurzbz to check + * @param $typeId typeId to check + * @param $id id to check + * @param $von start of time period or NULL + * @param $bis end of time period or NULL + * + * @return array + */ + public function checkIfExistingTag($tag, $typeId, $id, $von=null, $bis=null) + { + $query = " + SELECT * + FROM public.tbl_notiz + JOIN public.tbl_notizzuordnung nz USING (notiz_id) + WHERE typ = ? + AND {$typeId} = ? + AND ( + start IS NULL + OR ende IS NULL + OR (start <= ? AND ende >= ?) + ) + "; + + return $this->execQuery($query, [$tag, $id, $bis, $von]); + } + + /** + * returns all existing tags of a certain tag within a time period + * + * @param $tag typ_kurzbz of tag + * @param $von start of time period or NULL + * @param $bis end of time period or NULL + * + * @return array + */ + public function getAllTags($tag, $von=null, $bis=null) + { + $query = " + SELECT * + FROM public.tbl_notiz + JOIN public.tbl_notizzuordnung nz USING (notiz_id) + WHERE typ = ? + AND ( + start IS NULL + OR ende IS NULL + OR (start <= ? AND ende >= ?) + ); + "; + + return $this->execQuery($query, array($tag, $bis, $von)); + } } diff --git a/application/models/person/Person_model.php b/application/models/person/Person_model.php index e72b24de4..c893c51de 100644 --- a/application/models/person/Person_model.php +++ b/application/models/person/Person_model.php @@ -149,7 +149,7 @@ class Person_model extends DB_Model * @param $filter Term to search for. * @return DB-result */ - public function searchPerson($filter) + public function searchPerson($filter, $mode=null) { $this->addSelect('vorname, nachname, gebdatum, person_id, titelpre, titelpost'); $this->addSelect("CASE @@ -161,6 +161,26 @@ class Person_model extends DB_Model THEN 'Student' ELSE 'Person' END AS status"); + + if($mode == 'mitMaUid') + { + $this->addSelect("( + SELECT m.mitarbeiter_uid + FROM public.tbl_benutzer b + JOIN public.tbl_mitarbeiter m + ON b.uid = m.mitarbeiter_uid + WHERE b.person_id = tbl_person.person_id + LIMIT 1 + ) + AS uid"); + $this->addOrder('uid, lower(nachname), lower(vorname)'); + } + else + { + $this->addOrder('lower(nachname), lower(vorname)'); + } + + $result = $this->loadWhere( 'lower(nachname) like '.$this->db->escape('%'.mb_strtolower($filter).'%')." OR lower(vorname) like ".$this->db->escape('%'.$filter.'%')." @@ -433,4 +453,33 @@ class Person_model extends DB_Model return $this->execReadOnlyQuery($qry, [$person_id]); } + + //just a test function for a person_id tag + //alle personen die innerhalb dieses Zeitraumens 55 werden + public function getFiftyFivers($von, $bis) + { + $qry = " + SELECT + p.person_id + FROM public.tbl_person p + WHERE p.gebdatum >= DATE ? - INTERVAL '55 years' + AND p.gebdatum <= DATE ? - INTERVAL '55 years'; + "; + return $this->execReadOnlyQuery($qry, [$von, $bis]); + } + + //just a test function for a person_id tag + //check if Person gets 55 in this time + public function isFiftyFive($von, $bis, $person_id) + { + $qry = " + SELECT + p.person_id + FROM public.tbl_person p + WHERE p.gebdatum >= DATE ? - INTERVAL '55 years' + AND p.gebdatum <= DATE ? - INTERVAL '55 years' + AND p.persond_id = ?; + "; + return $this->execReadOnlyQuery($qry, [$von, $bis, $person_id]); + } } diff --git a/application/models/ressource/Reservierung_model.php b/application/models/ressource/Reservierung_model.php index 461a009b0..d1c4e683b 100644 --- a/application/models/ressource/Reservierung_model.php +++ b/application/models/ressource/Reservierung_model.php @@ -137,4 +137,15 @@ class Reservierung_model extends DB_Model return $this->execQuery($query, [$uid, $uid]); } + public function lektorHasReservierung($uid, $datum, $stunde) + { + $qry = "SELECT reservierung_id, uid, stg_kurzbz, ort_kurzbz, semester, verband, gruppe, gruppe_kurzbz, datum, stunde + FROM lehre.vw_reservierung + WHERE uid = ? + AND datum = ? + AND stunde = ?"; + + return $this->execReadOnlyQuery($qry, [$uid, $datum, $stunde]); + } + } diff --git a/application/models/ressource/Stundenplan_model.php b/application/models/ressource/Stundenplan_model.php index 997451243..1eddd2e2f 100644 --- a/application/models/ressource/Stundenplan_model.php +++ b/application/models/ressource/Stundenplan_model.php @@ -333,6 +333,68 @@ class Stundenplan_model extends DB_Model ", [$start_date, $end_date, $lv_id]); } + public function getStundenplanLE($lehreinheit, $start_date, $end_date, $stundenplan) + { + $qry = " + WITH lehreinheiten AS ( + SELECT lehreinheit_id FROM lehre.tbl_lehreinheit WHERE lehreinheit_id = ? + ), " . $this->getStundenplanCTE($stundenplan) . " + SELECT * + FROM stundenplanentries + "; + + return $this->execReadOnlyQuery($qry, array($lehreinheit, $start_date, $end_date)); + } + + public function getStundenplanLV($lehrveranstaltung_id, $start_date, $end_date, $stundenplan) + { + $qry = " + WITH lehreinheiten AS ( + SELECT lehreinheit_id + FROM lehre.tbl_lehreinheit + JOIN tbl_studiensemester USING(studiensemester_kurzbz) + WHERE lehrveranstaltung_id = ? + AND tbl_studiensemester.start >= ? AND tbl_studiensemester.ende <= ? + ), " . $this->getStundenplanCTE($stundenplan) . " + SELECT * + FROM stundenplanentries + "; + + return $this->execReadOnlyQuery($qry, array($lehrveranstaltung_id, $start_date, $end_date, $start_date, $end_date)); + } + + private function getStundenplanCTE($stundenplan) + { + return "entries AS ( + SELECT + datum, min(stunde) as stunde_beginn, max(stunde) as stunde_ende, + array_agg(DISTINCT( + CASE WHEN gruppe_kurzbz is not null THEN gruppe_kurzbz + ELSE (UPPER(stg_typ || stg_kurzbz) || COALESCE(semester,'0') || COALESCE(verband,'') || COALESCE(gruppe,'')) + END)) as gruppen_kuerzel, + array_agg(DISTINCT CONCAT(vorname || ' ' || nachname)) as lektorname, + array_agg(DISTINCT stundenplan.ort_kurzbz) as ort_kurzbz, + array_agg(DISTINCT titel) as titel, + lehrfach_bez, stundenplan.lehreinheit_id, lehrveranstaltung_id + FROM lehre.vw_$stundenplan as stundenplan + JOIN public.tbl_mitarbeiter ON stundenplan.uid = tbl_mitarbeiter.mitarbeiter_uid + JOIN tbl_benutzer ON tbl_mitarbeiter.mitarbeiter_uid = tbl_benutzer.uid + JOIN tbl_person USING(person_id) + JOIN lehreinheiten ON stundenplan.lehreinheit_id = lehreinheiten.lehreinheit_id + WHERE datum >= ? AND datum <= ? + GROUP BY datum, unr, stundenplan.lehreinheit_id, lehrveranstaltung_id, lehrfach_bez, lehrfach_bez + ORDER BY datum, min(stunde), unr, lehreinheit_id + ), + stundenplanentries AS ( + SELECT + entries.*, + stundeb.beginn AS beginn, + stundee.ende AS ende + FROM entries + JOIN lehre.tbl_stunde stundeb ON stundeb.stunde = entries.stunde_beginn + JOIN lehre.tbl_stunde stundee ON stundee.stunde = entries.stunde_ende + )"; + } /** * queries Stundenplan and filters by assigned ma_kurzbz, very similar to get by LVA * diff --git a/application/models/ressource/Stundenplandev_model.php b/application/models/ressource/Stundenplandev_model.php index e718ba073..355283b90 100644 --- a/application/models/ressource/Stundenplandev_model.php +++ b/application/models/ressource/Stundenplandev_model.php @@ -241,4 +241,15 @@ class Stundenplandev_model extends DB_Model return $this->delete(array('lehreinheit_id' => $lehreinheit_id, 'mitarbeiter_uid' => $mitarbeiter_uid)); } + + public function lektorHasStundenplandevEintrag($uid, $datum, $stunde) + { + $qry = "SELECT stundenplandev_id, lektor, stg_kurzbz, ort_kurzbz, semester, verband, gruppe, gruppe_kurzbz, datum, stunde + FROM lehre.vw_stundenplandev + WHERE uid = ? + AND datum = ? + AND stunde = ?"; + + return $this->execReadOnlyQuery($qry, [$uid, $datum, $stunde]); + } } diff --git a/application/models/ressource/Zeitsperre_model.php b/application/models/ressource/Zeitsperre_model.php index c7dc9dfff..d67ab361c 100644 --- a/application/models/ressource/Zeitsperre_model.php +++ b/application/models/ressource/Zeitsperre_model.php @@ -187,4 +187,39 @@ class Zeitsperre_model extends DB_Model return $result; } + + public function checkIfZeitsperreExists($uid, $datum, $stunde) + { + $this->db->select("*"); + + $this->db->where('mitarbeiter_uid', $uid); + $this->db->where('zeitsperretyp_kurzbz !=', 'ZVerfueg'); + + $this->db->group_start(); + + $this->db->where('vondatum <', $datum); + $this->db->or_group_start(); + $this->db->where('vondatum', $datum); + $this->db->group_start(); + $this->db->where('vonstunde <=', $stunde); + $this->db->or_where('vonstunde IS NULL', null, false); + $this->db->group_end(); + $this->db->group_end(); + $this->db->group_end(); + + + $this->db->group_start(); + $this->db->where('bisdatum >', $datum); + $this->db->or_group_start(); + $this->db->where('bisdatum', $datum); + $this->db->group_start(); + $this->db->where('bisstunde >=', $stunde); + $this->db->or_where('bisstunde IS NULL', null, false); + $this->db->group_end(); + + $this->db->group_end(); + $this->db->group_end(); + + return $this->load(); + } } diff --git a/application/models/tabulator/Tabulator_preset_model.php b/application/models/tabulator/Tabulator_preset_model.php new file mode 100644 index 000000000..a3c45739d --- /dev/null +++ b/application/models/tabulator/Tabulator_preset_model.php @@ -0,0 +1,83 @@ +dbTable = 'public.tbl_tabulator_presets'; + $this->pk = ['preset_id']; + $this->hasSequence = true; + + + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + } + + public function getTabulatorPreset($presetId) + { + $query = " + SELECT * + FROM public.tbl_tabulator_presets + WHERE preset_id = ? + LIMIT 1 + "; + return $this->execQuery($query, array($presetId)); + } + + public function getTabulatorPresetsByUserAndTable($uid, $tableName) + { + $query = ' + SELECT * + FROM public.tbl_tabulator_presets + WHERE benutzer_uid = ? + AND table_name = ? + ORDER BY preset_id ASC + '; + return $this->execQuery($query, array($uid, $tableName)); + } + + public function createTabulatorPreset($uid, $tableName, $presetName, $preset) + { + $presetCreationResult = $this->insert([ + "benutzer_uid" => $uid, + "table_name" => $tableName, + "preset_name" => $presetName, + "preset_json" => json_encode($preset), + ]); + + if (isError($presetCreationResult)) + { + return error('Something went wrong during preset creation!'); + } + + return success($presetCreationResult->retval); + } + + public function updateTabulatorPreset($presetId, $preset) + { + $presetUpdateResult = $this->update(["preset_id" => $presetId], ["preset_json" => json_encode($preset)]); + + if (isError($presetUpdateResult)) + { + return error('Something went wrong during preset update!'); + } + + return success($presetUpdateResult->retval); + } + + public function deleteTabulatorPreset($presetId) + { + $presetDeletionReseult = $this->delete(["preset_id" => $presetId]); + + if (isError($presetDeletionReseult)) + { + return error('Something went wrong during preset deletion!'); + } + + return success($presetDeletionReseult->retval); + } + +} diff --git a/application/views/LVVerwaltung.php b/application/views/LVVerwaltung.php index 4cebae839..42d4631f1 100644 --- a/application/views/LVVerwaltung.php +++ b/application/views/LVVerwaltung.php @@ -31,6 +31,8 @@ lv-root="" :permissions="" :config="" + avatar-url="" + logout-url="" > diff --git a/application/views/Studentenverwaltung.php b/application/views/Studentenverwaltung.php index 8dd2dd93d..5b6138ff8 100644 --- a/application/views/Studentenverwaltung.php +++ b/application/views/Studentenverwaltung.php @@ -56,12 +56,13 @@ $configArray = [ diff --git a/application/views/jssnippets/tabulatorcolumns.js b/application/views/jssnippets/tabulatorcolumns.js new file mode 100644 index 000000000..44667e45b --- /dev/null +++ b/application/views/jssnippets/tabulatorcolumns.js @@ -0,0 +1,19 @@ +let result = []; +let current; + + + +current = (tabulatorcolumns => { +load->view($path); ?> +})(result); + +if (current) { + if (Array.isArray(current)) + result = result.concat(current); + else + result.push(current); +} + + + +export default result; \ No newline at end of file diff --git a/application/views/jssnippets/tabulatorcolumns/lvverwaltung.js b/application/views/jssnippets/tabulatorcolumns/lvverwaltung.js new file mode 100644 index 000000000..9b7c44212 --- /dev/null +++ b/application/views/jssnippets/tabulatorcolumns/lvverwaltung.js @@ -0,0 +1,264 @@ +return [ + { + title: 'kurzbz', + titlePhrase: 'lehre/kurzbz', + field: "lv_kurzbz", + headerFilterFuncParams: { field: 'lv_kurzbz' }, + headerFilter: true, + formatter(cell, formatterParams) { + const rowData = cell.getRow().getData(); + const iconKey = (rowData.lehrtyp_kurzbz || '').toLowerCase(); + const lvkurzbz = (cell.getValue()).toUpperCase(); + + const parentspan = document.createElement('span'); + const span = document.createElement('span'); + + span.classList.add('lv_table_icon', `icon-${iconKey}`); + span.title = iconKey || 'LV-Teil'; + + parentspan.appendChild(span); + parentspan.appendChild(document.createTextNode(` ${lvkurzbz}`)); + + return parentspan; + }, + cellClick(e, cell) { + cell.getRow().treeToggle(); + } + }, + { + title: 'Tags', + field: 'tags', + tooltip: false, + headerFilter: "input", + headerFilterFunc: "tagHeaderFilter", + headerFilterFuncParams: { field: 'tags' }, + formatter: "tagFormatter", + width: 150, + }, + { + title: 'lehrveranstaltung_id', + titlePhrase: 'lehre/lehrveranstaltung_id', + field: "lehrveranstaltung_id", + headerFilterFuncParams: { field: 'lehrveranstaltung_id' }, + headerFilter: true, + visible: false + }, + { + title: 'bezeichnung', + titlePhrase: 'ui/bezeichnung', + field: "lv_bezeichnung", + headerFilter: true, + headerFilterFuncParams: { field: 'lv_bezeichnung' } + }, + { + title: 'bezeichnungeng', + titlePhrase: 'lehre/bezeichnungeng', + field: "lv_bezeichnung_english", + headerFilter: true, + headerFilterFuncParams: { field: 'lv_bezeichnung_english' }, + visible: false + }, + { + title: 'studiengangskennzahlLehre', + titlePhrase: 'lehre/studiengangskennzahlLehre', + field: "lv_studiengang_kz", + headerFilter: true, + headerFilterFuncParams: { field: 'lv_studiengang_kz' }, + visible: false + }, + { + title: 'studiengang', + titlePhrase: 'lehre/studiengang', + field: "studiengang", + headerFilter: true, + headerFilterFuncParams: { field: 'studiengang' }, + visible: false + }, + { + title: 'semester', + titlePhrase: 'lehre/semester', + field: "semester", + headerFilter: true, + headerFilterFuncParams: { field: 'semester' } + }, + { + title: 'sprache', + titlePhrase: 'global/sprache', + field: "sprache", + headerFilter: true, + headerFilterFuncParams: { field: 'sprache' }, + visible: false + }, + { + title: 'ects', + titlePhrase: 'lehre/ects', + field: "lv_ects", + headerFilter: true, + headerFilterFuncParams: { field: 'lv_ects' }, + visible: false + }, + { + title: 'semesterstunden', + titlePhrase: 'lehre/semesterstunden', + field: "semesterstunden", + headerFilter: true, + headerFilterFuncParams: { field: 'semesterstunden' }, + visible: false + }, + { + title: 'anmerkung', + titlePhrase: 'global/anmerkung', + field: "anmerkung", + headerFilter: true, + headerFilterFuncParams: { field: 'anmerkung' }, + visible: false + }, + { + title: 'lehre', + titlePhrase: 'lehre/lehre', + field: "lehre", + headerFilter: true, + headerFilterFuncParams: { field: 'lehre' }, + formatter: 'ja_nein', + visible: false + }, + { + title: 'aktiv', + titlePhrase: 'person/aktiv', + field: "aktiv", + headerFilter: true, + headerFilterFuncParams: { field: 'aktiv' }, + formatter: 'ja_nein', + visible: false + }, + { + title: 'organisationsform', + titlePhrase: 'lehre/organisationsform', + field: "orgform_kurzbz", + headerFilter: true, + headerFilterFuncParams: { field: 'orgform_kurzbz' } + }, + { + title: 'studienplan_id', + titlePhrase: 'ui/studienplan_id', + field: "studienplan_id", + headerFilter: true, + headerFilterFuncParams: { field: 'studienplan_id' }, + visible: false + }, + { + title: 'studienplan', + titlePhrase: 'lehre/studienplan', + field: "studienplan_bezeichnung", + headerFilter: true, + headerFilterFuncParams: { field: 'studienplan_bezeichnung' }, + visible: false + }, + { + title: 'lehrtyp', + titlePhrase: 'lehre/lehrtyp', + field: "lehrtyp_kurzbz", + headerFilter: true, + headerFilterFuncParams: { field: 'lehrtyp_kurzbz' }, + visible: false + }, + { + title: 'lehrform', + titlePhrase: 'lehre/lehrform', + field: "lehrform_kurzbz", + headerFilter: true, + headerFilterFuncParams: { field: 'lehrform_kurzbz'} }, + { + title: 'leplanstunden', + titlePhrase: 'lehre/leplanstunden', + field: "le_planstunden", + headerFilter: true, + headerFilterFuncParams: { field: 'le_planstunden' }, + visible: false + }, + { + title: 'lehreinheit_id', + titlePhrase: 'lehre/lehreinheit_id', + field: "lehreinheit_id", + headerFilter: true, + headerFilterFuncParams: { field: 'lehreinheit_id' }, + visible: false + }, + { + title: 'studiensemester', + titlePhrase: 'lehre/studiensemester', + field: "studiensemester_kurzbz", + headerFilter: true, + headerFilterFuncParams: { field: 'studiensemester_kurzbz' }, + visible: false + }, + { + title: 'unr', + titlePhrase: 'lehre/unr', + field: "unr", + headerFilter: true, + headerFilterFuncParams: { field: 'unr' }, + visible: false + }, + { + title: 'organisationseinheit', + titlePhrase: 'lehre/organisationseinheit', + field: "fachbereich", + headerFilter: true, + headerFilterFuncParams: { field: 'fachbereich' }, + visible: false + }, + { + title: 'stundenblockung', + titlePhrase: 'lehre/stundenblockung', + field: "stundenblockung", + headerFilter: true, + headerFilterFuncParams: { field: 'stundenblockung' }, + visible: false + }, + { + title: 'wochenrhythmus', + titlePhrase: 'lehre/wochenrhythmus', + field: "wochenrythmus", + headerFilter: true, + headerFilterFuncParams: { field: 'wochenrythmus' }, + visible: false + }, + { + title: 'startkw', + titlePhrase: 'lehre/startkw', + field: "start_kw", + headerFilter: true, + headerFilterFuncParams: { field: 'startkw' }, + visible: false + }, + { + title: 'raumtyp', + titlePhrase: 'lehre/raumtyp', + field: "raumtyp", + headerFilter: true, + headerFilterFuncParams: { field: 'raumtyp' }, + visible: false + }, + { + title: 'raumtypalternativ', + titlePhrase: 'lehre/raumtypalternativ', + field: "raumtypalternativ", + headerFilter: true, + headerFilterFuncParams: { field: 'raumtypalternativ' }, + visible: false + }, + { + title: 'gruppen', + titlePhrase: 'lehre/gruppen', + field: "gruppen", + headerFilter: true, + headerFilterFuncParams: { field: 'gruppen'} }, + { + title: 'lehrende', + titlePhrase: 'lehre/lehrende', + field: "lektoren", + headerFilter: true, + headerFilterFuncParams: { field: ['lektoren', 'vorname', 'nachname'] } + }, +]; diff --git a/application/views/jssnippets/tabulatorcolumns/stv.js b/application/views/jssnippets/tabulatorcolumns/stv.js new file mode 100644 index 000000000..8270defe1 --- /dev/null +++ b/application/views/jssnippets/tabulatorcolumns/stv.js @@ -0,0 +1,343 @@ +return [ + { + title: "UID", + titlePhrase: 'person/uid', + field: "uid", + headerFilter: true + }, + { + title: "TitelPre", + titlePhrase: 'person/titelpre', + field: "titelpre", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "Nachname", + titlePhrase: 'person/nachname', + field: "nachname", + headerFilter: true + }, + { + title: "Vorname", + titlePhrase: 'person/vorname', + field: "vorname", + headerFilter: true + }, + { + title: "Wahlname", + titlePhrase: 'person/wahlname', + field: "wahlname", + visible: false, + headerFilter: true + }, + { + title: "Vornamen", + titlePhrase: 'person/vornamen', + field: "vornamen", + visible: false, + headerFilter: true + }, + { + title: "TitelPost", + titlePhrase: 'person/titelpost', + field: "titelpost", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "Ersatzkennzeichen", + titlePhrase: 'person/ersatzkennzeichen', + field: "ersatzkennzeichen", + headerFilter: true + }, + { + title: "Geburtsdatum", + titlePhrase: 'person/geburtsdatum', + field: "gebdatum", + formatter: 'datetime', + formatterParams: { + inputFormat: "iso", + outputFormat: "dd.MM.yyyy", + invalidPlaceholder: " ", + timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone + }, + headerFilter: 'input', + headerFilterFunc: 'dateinput' + }, + { + title: "Geschlecht", + titlePhrase: 'person/geschlecht', + field: "geschlecht", + headerFilter: "list", + headerFilterParams: { + values: { + 'm': 'männlich', + 'w': 'weiblich', + 'x': 'divers', + 'u': 'unbekannt' + }, + listOnEmpty: true, + autocomplete: true + } + }, + { + title: "Sem.", + titlePhrase: 'lehre/sem', + field: "semester_berechnet", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "Verb.", + titlePhrase: 'lehre/verb', + field: "verband", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "Grp.", + titlePhrase: 'lehre/grp', + field: "gruppe", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "Studiengang", + titlePhrase: 'lehre/studiengang', + field: "studiengang", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "Studiengang_kz", + titlePhrase: 'lehre/studiengang_kz', + field: "studiengang_kz", + visible: false, + headerFilter: true + }, + { + title: "Personenkennzeichen", + titlePhrase: 'person/personenkennzeichen', + field: "matrikelnr", + headerFilter: true + }, + { + title: "PersonID", + titlePhrase: 'person/person_id', + field: "person_id", + headerFilter: true + }, + { + title: "Status", + titlePhrase: 'global/status', + field: "status", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "Status Datum", + titlePhrase: 'profilUpdate/statusDate', + field: "status_datum", + visible: false, + formatter: 'datetime', + formatterParams: { + inputFormat: "iso", + outputFormat: "dd.MM.yyyy", + invalidPlaceholder: " ", + timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone + } + }, + { + title: "Status Bestaetigung", + titlePhrase: 'global/status_bestaetigung', + field: "status_bestaetigung", + visible: false, + formatter: 'datetime', + formatterParams: { + inputFormat: "iso", + outputFormat: "dd.MM.yyyy", + invalidPlaceholder: " ", + timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone + }, + headerFilter: 'input', + headerFilterFunc: 'dateinput' + }, + { + title: "EMail (Privat)", + titlePhrase: 'person/email_private', + field: "mail_privat", + visible: false, + headerFilter: true + }, + { + title: "EMail (Intern)", + titlePhrase: 'person/email_intern', + field: "mail_intern", + visible: false, + headerFilter: true + }, + { + title: "Anmerkungen", + titlePhrase: 'stv/notes_person', + field: "anmerkungen", + visible: false, + headerFilter: true + }, + { + title: "AnmerkungPre", + titlePhrase: 'stv/notes_prestudent', + field: "anmerkung", + visible: false, + headerFilter: true + }, + { + title: "OrgForm", + titlePhrase: 'lehre/orgform', + field: "orgform_kurzbz", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "Aufmerksamdurch", + titlePhrase: 'person/aufmerksamDurch', + field: "aufmerksamdurch_kurzbz", + visible: false + }, + { + title: "Gesamtpunkte", + titlePhrase: 'admission/gesamtpunkte', + field: "punkte", + visible: false + }, + { + title: "Aufnahmegruppe", + titlePhrase: 'stv/aufnahmegruppe_kurzbz', + field: "aufnahmegruppe_kurzbz", + visible: false + }, + { + title: "Dual", + titlePhrase: 'lehre/dual_short', + field: "dual", + visible: false, + formatter: 'tickCross', + formatterParams: { + tickElement: '', + crossElement: '' + }, + headerFilter: "tickCross", + headerFilterParams: { + tristate: true, + elementAttributes: { + value: "true" + } + }, + headerFilterEmptyCheck(value) { + return value === null + } + }, + { + title: "Matrikelnummer", + titlePhrase: 'person/matrikelnummer', + field: "matr_nr", + visible: false, + headerFilter: true + }, + { + title: "Studienplan", + titlePhrase: 'lehre/studienplan', + field: "studienplan_bezeichnung", + headerFilter: "list", + headerFilterParams: { + valuesLookup: true, + listOnEmpty: true, + autocomplete: true, + sort: "asc" + } + }, + { + title: "PreStudentInnenID", + titlePhrase: 'ui/prestudent_id', + field: "prestudent_id", + headerFilter: true + }, + { + title: "Priorität", + titlePhrase: 'lehre/prioritaet', + field: "priorisierung_relativ" + }, + { + title: "Mentor", + titlePhrase: 'stv/mentor', + field: "mentor", + visible: false + }, + { + title: "Aktiv", + titlePhrase: 'person/aktiv', + field: "bnaktiv", + visible: false, + formatter: 'tickCross', + formatterParams: { + allowEmpty: true, + tickElement: '', + crossElement: '' + }, + headerFilter: "tickCross", + headerFilterParams: { + tristate: true, + elementAttributes: { + value: "true" + } + }, + headerFilterEmptyCheck(value) { + return value === null + } + }, + { + title: "Unruly", + field: "unruly", + visible: false + } +]; diff --git a/application/views/lehre/Antrag/Student/List.php b/application/views/lehre/Antrag/Student/List.php index 6d769dafe..884d1f391 100644 --- a/application/views/lehre/Antrag/Student/List.php +++ b/application/views/lehre/Antrag/Student/List.php @@ -30,6 +30,12 @@ if(defined('CIS4')){ } ?> +load->config('studierendenantrag'); + $showInfoModalAbmeldung = $this->config->item('showInfoModalAbmeldung'); +?> +
@@ -43,17 +49,75 @@ if(defined('CIS4')){

()

+
-

p->t('studierendenantrag', 'calltoaction_' . $type); ?>

-
- - p->t('studierendenantrag', 'antrag_typ_' . $type); ?> - +

p->t('studierendenantrag', 'calltoaction_' . $type); ?>

+
+ + + + data-bs-toggle="modal" + + > + p->t('studierendenantrag', 'antrag_typ_' . $type); ?> + + + +
- + + + + @@ -205,7 +269,7 @@ if(defined('CIS4')){ p->t('studierendenantrag', 'my_lvs'); ?> diff --git a/application/views/templates/FHC-Footer.php b/application/views/templates/FHC-Footer.php index d2eb229f1..66ed4bd51 100644 --- a/application/views/templates/FHC-Footer.php +++ b/application/views/templates/FHC-Footer.php @@ -154,6 +154,7 @@ generateJSsInclude('vendor/npm-asset/primevue/confirmdialog/confirmdialog.min.js'); generateJSsInclude('vendor/npm-asset/primevue/confirmationservice/confirmationservice.min.js'); generateJSsInclude('vendor/npm-asset/primevue/tieredmenu/tieredmenu.min.js'); + generateJSsInclude('vendor/npm-asset/primevue/progressbar/progressbar.min.js'); } if($vuedatepicker11) generateJSsInclude('vendor/vuejs/vuedatepicker_js11/vue-datepicker.iife.js'); diff --git a/application/views/templates/FHC-Header.php b/application/views/templates/FHC-Header.php index 7b53cbf5d..4203aa571 100644 --- a/application/views/templates/FHC-Header.php +++ b/application/views/templates/FHC-Header.php @@ -132,7 +132,11 @@ if ($cis === true) generateCSSsInclude(defined('CIS4') ? 'public/css/cis4.css' : 'public/css/cis_bs5.css'); //Tags - if ($tags === true) generateCSSsInclude('public/css/tags.css'); + if ($tags === true) + { + generateCSSsInclude('public/css/tags.css'); + generateCSSsIncludeIfExtensionCssExists('tags.css'); + } $extapphelper = ExtendableAppsHelper::getInstance(); $extapphelper->init($customCSSs, $customJSs, $customJSModules); diff --git a/composer.json b/composer.json index 4d37abdbb..0feee3c63 100644 --- a/composer.json +++ b/composer.json @@ -109,9 +109,9 @@ "type": "package", "package": { "name": "fortawesome/font-awesome6", - "version": "6.1.1", + "version": "6.7.2", "dist": { - "url": "https://github.com/FortAwesome/Font-Awesome/archive/refs/tags/6.1.1.zip", + "url": "https://github.com/FortAwesome/Font-Awesome/archive/refs/tags/6.7.2.zip", "type": "zip" } } @@ -468,7 +468,7 @@ "fgelinas/timepicker": "0.3.3", "fortawesome/font-awesome4": "4.7.*", - "fortawesome/font-awesome6": "6.1.*", + "fortawesome/font-awesome6": "6.7.*", "fzaninotto/faker": "1.*", "firebase/php-jwt": "^6.0", @@ -496,7 +496,7 @@ "netcarver/textile": "3.7.*", "nicolaskruchten/pivottable": "2.23.0", "npm-asset/primevue": "3.29.1", - "npm-asset/primeicons": "5.0.0", + "npm-asset/primeicons": "6.0.1", "olifolkerd/tabulator4": "4.9.*", "olifolkerd/tabulator5": "5.5.*", diff --git a/composer.lock b/composer.lock index 212eea3be..39042460f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "869cbc35bd1ba90ab90934fcb41b0f51", + "content-hash": "bc21de2a48b960781c1ed028c4b8d7b9", "packages": [ { "name": "afarkas/html5shiv", @@ -960,10 +960,10 @@ }, { "name": "fortawesome/font-awesome6", - "version": "6.1.1", + "version": "6.7.2", "dist": { "type": "zip", - "url": "https://github.com/FortAwesome/Font-Awesome/archive/refs/tags/6.1.1.zip" + "url": "https://github.com/FortAwesome/Font-Awesome/archive/refs/tags/6.7.2.zip" }, "type": "library" }, @@ -1565,10 +1565,10 @@ }, { "name": "npm-asset/primeicons", - "version": "5.0.0", + "version": "6.0.1", "dist": { "type": "tar", - "url": "https://registry.npmjs.org/primeicons/-/primeicons-5.0.0.tgz" + "url": "https://registry.npmjs.org/primeicons/-/primeicons-6.0.1.tgz" }, "type": "npm-asset", "license": [ diff --git a/content/statistik/lehrauftragsliste_gst.xls.php b/content/statistik/lehrauftragsliste_gst.xls.php index 990216c09..d44c91e63 100644 --- a/content/statistik/lehrauftragsliste_gst.xls.php +++ b/content/statistik/lehrauftragsliste_gst.xls.php @@ -41,6 +41,12 @@ else $user = get_uid(); loadVariables($user); +if (isset($_GET['studiensemester_kurzbz'])) +{ + if (check_stsem($_GET['studiensemester_kurzbz'])) + $semester_aktuell = $_GET['studiensemester_kurzbz']; +} + //Studiengang laden $studiengang = new studiengang($studiengang_kz); diff --git a/content/statistik/lvplanung.php b/content/statistik/lvplanung.php index b0169dade..fe69e4160 100644 --- a/content/statistik/lvplanung.php +++ b/content/statistik/lvplanung.php @@ -50,6 +50,12 @@ else $user = get_uid(); loadVariables($user); +if (isset($_GET['studiensemester_kurzbz'])) +{ + if (check_stsem($_GET['studiensemester_kurzbz'])) + $semester_aktuell = $_GET['studiensemester_kurzbz']; +} + $rechte = new benutzerberechtigung(); $rechte->getBerechtigungen($user); diff --git a/public/css/Lvverwaltung.css b/public/css/Lvverwaltung.css index 6e0f4c760..b8152d39a 100644 --- a/public/css/Lvverwaltung.css +++ b/public/css/Lvverwaltung.css @@ -38,3 +38,9 @@ textarea[name="anmerkung"] { { border-color: black; } + +.node-verband { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; +} diff --git a/public/css/Studentenverwaltung.css b/public/css/Studentenverwaltung.css index 86e3d6be3..870a58bdf 100644 --- a/public/css/Studentenverwaltung.css +++ b/public/css/Studentenverwaltung.css @@ -2,6 +2,7 @@ @import './SvgIcons.css'; @import './components/searchbar/searchbar.css'; @import './components/verticalsplit.css'; +@import './components/horizontalsplit.css'; @import './components/FilterComponent.css'; @import './components/Tabs.css'; @import './components/Notiz.css'; @@ -95,6 +96,13 @@ html.fs_huge { overflow: visible !important; } +/* to enable scrolling for many student entries*/ +.stv-details-abschlusspruefung-student-names { + max-height: 80px; + overflow-y: auto; + overflow-x: hidden; +} + .p-dropdown-item{ z-index: 1201 !important; } diff --git a/public/css/components/FilterComponent.css b/public/css/components/FilterComponent.css index 3edf989a6..7779f78fc 100644 --- a/public/css/components/FilterComponent.css +++ b/public/css/components/FilterComponent.css @@ -70,6 +70,10 @@ margin-top: 20px; } +.filter-table-dataset .tabulator-tableholder { + padding-bottom: 16px; +} + .tabulator { font-size: 1rem; } diff --git a/public/css/components/horizontalsplit.css b/public/css/components/horizontalsplit.css new file mode 100644 index 000000000..baf8632c0 --- /dev/null +++ b/public/css/components/horizontalsplit.css @@ -0,0 +1,75 @@ +:root { + --fhc-horizontalsplit-hsplitter-bg-color: var(--fhc-background, #eee); + --fhc-horizontalsplit-hsplitter-border-color: var(--fhc-border, #eee); + --fhc-horizontalsplit-hsplitter-splitactions-color: var(--fhc-dark, #000); +} + +.horizontalsplit-container { + display: flex; + flex-direction: row; + overflow: hidden; + max-height: 100%; + padding: 0px; +} + +.horizontalsplitted { + overflow: auto; + flex-shrink: 0; +} + +.horizontalsplitter { + flex-shrink: 0; + width: 16px; + cursor: col-resize; + user-select: none; + display: flex; + align-items: center; + justify-content: center; +} + +.horizontalsplitter.left { + border-left: solid 3px var(--fhc-horizontalsplit-hsplitter-border-color); + margin-right: 3px; +} + +.horizontalsplitter.right { + border-right: solid 3px var(--fhc-horizontalsplit-hsplitter-border-color); + margin-left: 3px; +} + +.splitactions.horizontal { + background-color: var(--fhc-horizontalsplit-hsplitter-bg-color); + color: var(--fhc-horizontalsplit-hsplitter-splitactions-color); + display: flex; + flex-direction: column; + padding: 5px 0 5px 0; +} + +.splitactions.horizontal.left { + border-radius: 0 40% 40% 0; +} + +.splitactions.horizontal.right { + border-radius: 40% 0 0 40%; +} + +.splitactions.horizontal .splitaction { + width: 14px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; +} + +.splitactions.horizontal .splitaction.resize { + cursor: col-resize; +} + +#content { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +#content > div:first-child { + margin-top: 30px; +} \ No newline at end of file diff --git a/public/css/tags.css b/public/css/tags.css index e92f415b2..0634c5ce3 100644 --- a/public/css/tags.css +++ b/public/css/tags.css @@ -46,7 +46,6 @@ background-color: #ffA500ff; } - .tag_braun { background-color: #6d4c41; } @@ -69,12 +68,46 @@ .tag_limette { background-color: #D3FFCE; + color: black; +} + +.tag_turquoise { + background-color: #89cccf; + color: black; +} + +.tag_bordeaux { + background-color: #7B1B38; + color: white; +} + +.tag_olive { + background-color: #868d07; + color: white; +} + +.tag_beige { + background-color: #E4D5B4; + color: black; +} + +.tag_skyblue { + background-color: #90D5FF; + color: black; +} +.tag_blush { + background-color: #A83E3E; + color: white; } .tag_done { text-decoration: line-through; } +.tag_auto { + border: 1px dashed white; +} + .display_all { background-color: darkgrey !important; border: none; @@ -111,4 +144,4 @@ .copy-btn { float: right; margin-top: 3px; -} \ No newline at end of file +} diff --git a/public/js/api/factory/detailHeader.js b/public/js/api/factory/detailHeader.js index 3c7d95bb1..02a371f15 100644 --- a/public/js/api/factory/detailHeader.js +++ b/public/js/api/factory/detailHeader.js @@ -34,4 +34,10 @@ export default { url: 'api/frontend/v1/detailheader/detailheader/getLeitungOrg/' + oekurzbz, }; }, + getSemesterStati(prestudent_id){ + return { + method: 'get', + url: 'api/frontend/v1/detailheader/detailheader/getSemesterStati/' + prestudent_id, + }; + }, } \ No newline at end of file diff --git a/public/js/api/factory/studiensemester.js b/public/js/api/factory/studiensemester.js index 9cf4e82d0..5e45b113f 100644 --- a/public/js/api/factory/studiensemester.js +++ b/public/js/api/factory/studiensemester.js @@ -36,4 +36,19 @@ export default { url: '/api/frontend/v1/Studiensemester/getStudiensemester' }; }, -}; \ No newline at end of file + current() { + return { + method: 'get', + url: 'api/frontend/v1/organisation/studiensemester/current' + }; + }, + set(studiensemester_kurzbz) { + return { + method: 'post', + url: 'api/frontend/v1/organisation/studiensemester/set', + params: { + studiensemester_kurzbz + } + }; + } +}; diff --git a/public/js/api/factory/stv.js b/public/js/api/factory/stv.js index 95bc227f5..15ae15649 100644 --- a/public/js/api/factory/stv.js +++ b/public/js/api/factory/stv.js @@ -17,7 +17,6 @@ import app from './stv/app.js'; import lists from './stv/lists.js'; -import verband from './stv/verband.js'; import students from './stv/students.js'; import filter from './stv/filter.js'; import konto from './stv/konto.js'; @@ -34,7 +33,6 @@ import admissionDates from './stv/admissionDates.js'; export default { app, lists, - verband, students, filter, konto, diff --git a/public/js/api/factory/stv/coursedates.js b/public/js/api/factory/stv/coursedates.js index 71ded7285..dc047f815 100644 --- a/public/js/api/factory/stv/coursedates.js +++ b/public/js/api/factory/stv/coursedates.js @@ -16,16 +16,20 @@ */ export default { - getCourselist(params) { + getCourselist(student_uid, start_date, end_date, stundenplan) { return { method: 'get', - url: 'api/frontend/v1/stv/LvTermine/getStundenplan/' + params.student_uid + '/' - + params.start_date + '/' - + params.end_date + '/' - + params.group_consecutiveHours + '/' - + params.dbStundenplanTable + url: 'api/frontend/v1/stv/LvTermine/getStundenplan/' + encodeURIComponent(student_uid) + '/' + + encodeURIComponent(start_date) + '/' + + encodeURIComponent(end_date) + '/' + + encodeURIComponent(stundenplan) + '/' + + encodeURIComponent(true) }; }, + exportCalendar(student_uid, stundenplan) + { + return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'content/statistik/termine.xls.php?student_uid=' + encodeURIComponent(student_uid) + '&db_stpl_table='+encodeURIComponent(stundenplan); + }, getStudiensemester(){ return { method: 'get', diff --git a/public/js/api/factory/stv/students.js b/public/js/api/factory/stv/students.js index c17e32ff9..98360ea13 100644 --- a/public/js/api/factory/stv/students.js +++ b/public/js/api/factory/stv/students.js @@ -17,9 +17,9 @@ export default { uid(uid, studiensemester_kurzbz) { - let url = 'api/frontend/v1/stv/students/' + let url = 'api/frontend/v1/treemenudata/stv/stdsem/' + encodeURIComponent(studiensemester_kurzbz) - + '/uid/' + + '/student/' + encodeURIComponent(uid); return { method: 'get', @@ -27,7 +27,7 @@ export default { }; }, prestudent(prestudent_id, studiensemester_kurzbz) { - let url = 'api/frontend/v1/stv/students/' + let url = 'api/frontend/v1/treemenudata/stv/stdsem/' + encodeURIComponent(studiensemester_kurzbz) + '/prestudent/' + encodeURIComponent(prestudent_id); @@ -37,7 +37,7 @@ export default { }; }, person(person_id, studiensemester_kurzbz) { - let url = 'api/frontend/v1/stv/students/' + let url = 'api/frontend/v1/treemenudata/stv/stdsem/' + encodeURIComponent(studiensemester_kurzbz) + '/person/' + encodeURIComponent(person_id); diff --git a/public/js/api/factory/stv/tag.js b/public/js/api/factory/stv/tag.js index 29675f6d4..4a22a009e 100644 --- a/public/js/api/factory/stv/tag.js +++ b/public/js/api/factory/stv/tag.js @@ -51,4 +51,20 @@ export default { params: data }; }, + + getAllTagsPrestudent(prestudent_id){ + return { + method: 'get', + url: 'api/frontend/v1/stv/Tags/getAllTags', + params: prestudent_id + }; + }, + + rebuildTagsforTypeId(data){ + return { + method: 'post', + url: 'api/frontend/v1/stv/Tags/rebuildTagsForTypeId/', + params: data + }; + } }; \ No newline at end of file diff --git a/public/js/api/factory/tabulatorPresets.js b/public/js/api/factory/tabulatorPresets.js new file mode 100644 index 000000000..1653ccb40 --- /dev/null +++ b/public/js/api/factory/tabulatorPresets.js @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2025 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export default { + getTabulatorPresets(tableName) { + return { + method: "get", + url: + "/api/frontend/v1/TabulatorPresets/getTabulatorPresets?tableName=" + + tableName, + }; + }, + createTabulatorPreset(tableName, presetName, preset) { + return { + method: "post", + url: "/api/frontend/v1/TabulatorPresets/createTabulatorPreset", + params: { + tableName, + presetName, + preset, + }, + }; + }, + updateTabulatorPreset(presetId, preset) { + return { + method: "post", + url: "/api/frontend/v1/TabulatorPresets/updateTabulatorPreset", + params: { + presetId, + preset, + }, + }; + }, + deleteTabulatorPreset(tabulatorPresetId) { + return { + method: "post", + url: + "/api/frontend/v1/TabulatorPresets/deleteTabulatorPreset", + params: { + presetId: tabulatorPresetId, + }, + }; + }, +}; diff --git a/public/js/api/lehrveranstaltung/studiengangtree.js b/public/js/api/factory/treemenu.js similarity index 68% rename from public/js/api/lehrveranstaltung/studiengangtree.js rename to public/js/api/factory/treemenu.js index fb63db729..662297d22 100644 --- a/public/js/api/lehrveranstaltung/studiengangtree.js +++ b/public/js/api/factory/treemenu.js @@ -1,5 +1,5 @@ /** - * Copyright (C) 2025 fhcomplete.org + * Copyright (C) 2026 fhcomplete.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,26 +16,29 @@ */ export default { - get(path) { - let url = 'api/frontend/v1/lv/StgTree'; - if (path) - url += '/' + path; + get(config, path = '') { return { method: 'get', - url + url: '/api/frontend/v1/treemenu/' + config + '/' + path + }; + }, + data(config, path = '') { + return { + method: 'get', + url: '/api/frontend/v1/treemenudata/' + config + '/' + path }; }, favorites: { - get() { + get(config) { return { method: 'get', - url: 'api/frontend/v1/lv/favorites' + url: 'api/frontend/v1/favorites/get/' + config }; }, - set(favorites) { + set(config, favorites) { return { method: 'post', - url: 'api/frontend/v1/lv/favorites/set', + url: 'api/frontend/v1/favorites/set/' + config, params: { favorites } }; } diff --git a/public/js/api/lehrveranstaltung/config.js b/public/js/api/lehrveranstaltung/config.js new file mode 100644 index 000000000..40d275145 --- /dev/null +++ b/public/js/api/lehrveranstaltung/config.js @@ -0,0 +1,15 @@ +export default { + get() { + return { + method: 'get', + url: '/api/frontend/v1/lv/config/get' + }; + }, + set(params) { + return { + method: 'post', + url: 'api/frontend/v1/lv/config/set', + params + }; + } +}; diff --git a/public/js/api/lehrveranstaltung/gruppe.js b/public/js/api/lehrveranstaltung/gruppe.js index 44340bec7..23db4c069 100644 --- a/public/js/api/lehrveranstaltung/gruppe.js +++ b/public/js/api/lehrveranstaltung/gruppe.js @@ -18,6 +18,14 @@ export default { params: newData }; }, + getGruppe(gruppe) + { + return { + method: 'post', + url: '/api/frontend/v1/lv/gruppe/getGruppe/', + params: gruppe + }; + }, getByLehreinheit(lehreinheit_id) { return { diff --git a/public/js/api/lehrveranstaltung/letermine.js b/public/js/api/lehrveranstaltung/letermine.js new file mode 100644 index 000000000..e5b3093f5 --- /dev/null +++ b/public/js/api/lehrveranstaltung/letermine.js @@ -0,0 +1,19 @@ +export default { + getCourselist(le_id, start_date, end_date, stundenplan) + { + return { + method: 'get', + url: '/api/frontend/v1/lvPlan/getLeEvents/' + encodeURIComponent(le_id) + "/" + encodeURIComponent(start_date) + "/" + encodeURIComponent(end_date) + "/" + encodeURIComponent(stundenplan), + }; + }, + exportCalendar(le_id, stundenplan) + { + return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'content/statistik/termine.xls.php?lehreinheit_id=' + encodeURIComponent(le_id) + '&db_stpl_table='+encodeURIComponent(stundenplan); + }, + getStudiensemester(){ + return { + method: 'get', + url: '/api/frontend/v1/lv/setup/getStudiensemester/' + }; + }, +}; diff --git a/public/js/api/lehrveranstaltung/lvtermine.js b/public/js/api/lehrveranstaltung/lvtermine.js new file mode 100644 index 000000000..35fdc5ca8 --- /dev/null +++ b/public/js/api/lehrveranstaltung/lvtermine.js @@ -0,0 +1,19 @@ +export default { + getCourselist(lv_id, start_date, end_date, stundenplan) + { + return { + method: 'get', + url: '/api/frontend/v1/lvPlan/getLvEvents/' + encodeURIComponent(lv_id) + "/" + encodeURIComponent(start_date) + "/" + encodeURIComponent(end_date) + "/" + encodeURIComponent(stundenplan), + }; + }, + exportCalendar(lv_id, stundenplan) + { + return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'content/statistik/termine.xls.php?lehrveranstaltung_id=' + encodeURIComponent(lv_id) + '&db_stpl_table='+encodeURIComponent(stundenplan); + }, + getStudiensemester(){ + return { + method: 'get', + url: '/api/frontend/v1/lv/setup/getStudiensemester/' + }; + }, +}; diff --git a/public/js/api/lehrveranstaltung/noten.js b/public/js/api/lehrveranstaltung/noten.js new file mode 100644 index 000000000..fdcbb6f1e --- /dev/null +++ b/public/js/api/lehrveranstaltung/noten.js @@ -0,0 +1,25 @@ +import Grades from '../factory/stv/grades.js'; + +export default { + ...Grades, + getCertificate(lv_id, studiensemester_kurzbz) { + let url = 'api/frontend/v1/lv/noten/getCertificate/' + encodeURIComponent(lv_id); + if (!!studiensemester_kurzbz) { + url = url + '/' + encodeURIComponent(studiensemester_kurzbz); + } + return { + method: 'get', + url: url + }; + }, + getTeacherProposal(lv_id, studiensemester_kurzbz) { + let url = 'api/frontend/v1/lv/noten/getTeacherProposal/' + encodeURIComponent(lv_id); + if (!!studiensemester_kurzbz) { + url = url + '/' + encodeURIComponent(studiensemester_kurzbz); + } + return { + method: 'get', + url: url + }; + }, +}; \ No newline at end of file diff --git a/public/js/api/lehrveranstaltung/setup.js b/public/js/api/lehrveranstaltung/setup.js index 78f78b05b..dab1062a4 100644 --- a/public/js/api/lehrveranstaltung/setup.js +++ b/public/js/api/lehrveranstaltung/setup.js @@ -1,9 +1,16 @@ export default { - getTabs() + getLETabs() { return { method: 'get', - url: '/api/frontend/v1/lv/setup/getTabs/' + url: '/api/frontend/v1/lv/setup/getLETabs/' + }; + }, + getLVTabs() + { + return { + method: 'get', + url: '/api/frontend/v1/lv/setup/getLVTabs/' }; }, } diff --git a/public/js/api/stv.js b/public/js/api/stv.js index 8a4e568ea..19125b1bf 100644 --- a/public/js/api/stv.js +++ b/public/js/api/stv.js @@ -1,4 +1,3 @@ -import verband from './stv/verband.js'; import students from './stv/students.js'; import filter from './stv/filter.js'; import konto from './stv/konto.js'; @@ -18,7 +17,6 @@ import jointstudies from "./stv/jointstudies.js"; import courselist from './stv/courselist.js'; export default { - verband, students, filter, konto, diff --git a/public/js/api/stv/abschlusspruefung.js b/public/js/api/stv/abschlusspruefung.js index dccc10085..26d9c17ab 100644 --- a/public/js/api/stv/abschlusspruefung.js +++ b/public/js/api/stv/abschlusspruefung.js @@ -35,9 +35,6 @@ export default { getMitarbeiter(searchString){ return this.$fhcApi.get('api/frontend/v1/stv/abschlusspruefung/getMitarbeiter/' + searchString); }, - getPruefer(searchString){ - return this.$fhcApi.get('api/frontend/v1/stv/abschlusspruefung/getPruefer/' + searchString); - }, getNoten(){ return this.$fhcApi.get('api/frontend/v1/stv/abschlusspruefung/getNoten/'); }, diff --git a/public/js/api/stv/verband.js b/public/js/api/stv/verband.js deleted file mode 100644 index f3479c81b..000000000 --- a/public/js/api/stv/verband.js +++ /dev/null @@ -1,15 +0,0 @@ -export default { - get() { - return this.$fhcApi.get('api/frontend/v1/stv/verband'); - }, - favorites: { - get() { - return this.$fhcApi.get('api/frontend/v1/stv/favorites'); - }, - set(favorites) { - return this.$fhcApi.post('api/frontend/v1/stv/favorites/set', { - favorites - }); - } - } -} \ No newline at end of file diff --git a/public/js/apps/LVVerwaltung.js b/public/js/apps/LVVerwaltung.js index 18f8a072f..b9ab52ef6 100644 --- a/public/js/apps/LVVerwaltung.js +++ b/public/js/apps/LVVerwaltung.js @@ -12,74 +12,13 @@ const router = VueRouter.createRouter({ component: LVVerwaltung }, { - name: `byEmp`, - path: `/emp/:studiensemester_kurzbz/:emp/:stg?/:semester?`, + name: 'stdsem', + path: '/stdsem/:stdsem', component: LVVerwaltung, - props: route => { - let {emp, stg, semester, studiensemester_kurzbz} = route.params; - - if (emp === '') - emp = undefined; - - if (stg === '') - stg = undefined; - - if (studiensemester_kurzbz === '') - studiensemester_kurzbz = undefined; - - return { - studiensemester_kurzbz: studiensemester_kurzbz, - emp: emp, - stg: stg, - semester: semester - }; - }, - beforeEnter: (to, from, next) => { - const { studiensemester_kurzbz } = to.params; - const isSemester = /^(SS|WS)\d{4}$/.test(studiensemester_kurzbz); - - if (!isSemester) - return next({ path: '/' }); - else - next(); - } - }, - /*{ - name: `byFachbereich`, - path: `/fachbereich/:fachbereich/:emp?`, - component: LVVerwaltung - },*/ - { - name: `byStg`, - path: '/stg/:studiensemester_kurzbz/:stg?/:semester?/', - component: LVVerwaltung, - props: route => { - let { studiensemester_kurzbz, stg, semester } = route.params; - - if (semester === '') - semester = undefined; - - if (studiensemester_kurzbz === '') - studiensemester_kurzbz = undefined; - - if (stg === '') - stg = undefined; - - return { - studiensemester_kurzbz: studiensemester_kurzbz, - stg: stg, - semester: semester, - }; - }, - beforeEnter: (to, from, next) => { - const studiensemester_kurzbz = to.params?.studiensemester_kurzbz - const isSemester = /^(SS|WS)\d{4}$/.test(studiensemester_kurzbz); - - if (!isSemester) - return next({ path: '/' }); - else - next(); - } + children: [ + { name: 'emp', path: 'emp/:emp/:treemenu(.*)*', component: LVVerwaltung }, + { name: 'treemenu', path: ':treemenu(.*)*', component: LVVerwaltung } + ] }, { path: '/:pathMatch(.*)*', @@ -91,7 +30,9 @@ const router = VueRouter.createRouter({ FhcApps.router.makeExtendable(router); -const app = Vue.createApp(); +const app = Vue.createApp({ + name: 'LvVwApp' +}); FhcApps.makeExtendable(app); diff --git a/public/js/apps/Studentenverwaltung.js b/public/js/apps/Studentenverwaltung.js index 47490bfdd..7687eaf90 100644 --- a/public/js/apps/Studentenverwaltung.js +++ b/public/js/apps/Studentenverwaltung.js @@ -24,167 +24,27 @@ import PluginsPhrasen from "../plugins/Phrasen.js"; const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router; const router = VueRouter.createRouter({ - history: VueRouter.createWebHistory(), + history: VueRouter.createWebHistory('/' + ciPath), routes: [ { name: 'index', - path: `/${ciPath}/studentenverwaltung`, - component: FhcStudentenverwaltung - }, - { - name: 'studiensemester', - path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz`, + path: '/studvw', component: FhcStudentenverwaltung, - props: (route) => { - return { - url_studiensemester_kurzbz: route.params.studiensemester_kurzbz - }; - }, - beforeEnter: (to, from, next) => { - const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); - if (!isSemester) { - return next({name: 'index'}); + children: [ + { + name: 'stdsem', + path: 'stdsem/:stdsem', + component: FhcStudentenverwaltung, + children: [ + { name: 'search', path: 'search/:query', component: FhcStudentenverwaltung }, + { name: 'searchtypes', path: 'search/:types/:query', component: FhcStudentenverwaltung }, + { name: 'prestudent', path: 'prestudent/:prestudent_id', component: FhcStudentenverwaltung }, + { name: 'student', path: 'student/:student_uid', component: FhcStudentenverwaltung }, + { name: 'person', path: 'person/:person_id', component: FhcStudentenverwaltung }, + { name: 'treemenu', path: ':treemenu(.*)*', component: FhcStudentenverwaltung }, + ] } - next(); - } - }, - { - name: 'studiengang', - path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/:studiengang`, - component: FhcStudentenverwaltung, - props: (route) => { - return { - url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, - url_studiengang: route.params.studiengang, - }; - }, - beforeEnter: (to, from, next) => { - const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); - if (!isSemester) { - return next({name: 'index'}); - } - const isStudiengang = /^[A-Z]{3}/.test(to.params.studiengang); - if (!isStudiengang) { - return next({ - name: 'studiensemester', - params: { - studiensemester_kurzbz: to.params.studiensemester_kurzbz - } - }); - } - next(); - } - }, - { - path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/prestudent/:prestudent_id`, - component: FhcStudentenverwaltung, - props: (route) => { - return { - url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, - url_mode: 'prestudent', - url_prestudent_id: route.params.prestudent_id - }; - }, - beforeEnter: (to, from, next) => { - const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); - if (!isSemester) { - return next({name: 'index'}); - } - next(); - } - }, - { - path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/prestudent/:prestudent_id/:tab`, - component: FhcStudentenverwaltung, - props: (route) => { - return { - url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, - url_mode: 'prestudent', - url_prestudent_id: route.params.prestudent_id, - url_tab: route.params.tab - }; - }, - beforeEnter: (to, from, next) => { - const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); - if (!isSemester) { - return next({name: 'index'}); - } - next(); - } - }, - { - path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/student/:id`, - component: FhcStudentenverwaltung, - props: (route) => { - return { - url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, - url_mode: 'student', - url_student_id: route.params.id - }; - }, - beforeEnter: (to, from, next) => { - const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); - if (!isSemester) { - return next({name: 'index'}); - } - next(); - } - }, - { - path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/person/:person_id`, - component: FhcStudentenverwaltung, - props: (route) => { - return { - url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, - url_mode: 'person', - url_prestudent_id: route.params.person_id - }; - }, - beforeEnter: (to, from, next) => { - const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); - if (!isSemester) { - return next({name: 'index'}); - } - next(); - } - }, - { - name: 'search', - path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/search/:searchstr`, - component: FhcStudentenverwaltung, - props(route) { - return { - url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, - url_mode: 'search', - url_prestudent_id: route.params.searchstr - }; - }, - beforeEnter(to, from, next) { - const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); - if (!isSemester) { - return next({name: 'index'}); - } - next(); - } - }, - { - name: 'search_w_types', - path: `/${ciPath}/studentenverwaltung/:studiensemester_kurzbz/search/:types/:searchstr`, - component: FhcStudentenverwaltung, - props(route) { - return { - url_studiensemester_kurzbz: route.params.studiensemester_kurzbz, - url_mode: 'search', - url_prestudent_id: route.params.type + '/' + route.params.searchstr - }; - }, - beforeEnter(to, from, next) { - const isSemester = /^[WS]S\d{4}$/.test(to.params.studiensemester_kurzbz); - if (!isSemester) { - return next({name: 'index'}); - } - next(); - } + ] }, { path: '/:pathMatch(.*)*', @@ -197,11 +57,16 @@ const router = VueRouter.createRouter({ router.afterEach((to, from, failure) => { let title = 'Studierendenverwaltung FH-Complete'; - if (to.params.studiengang) { - title = to.params.studiengang + ' ' + title; + if (to.params.treemenu) { + const index = to.params.treemenu.findIndex( + (e, i) => i%2 == 0 && e == 'stg' + ); + if (index >= 0) { + title = to.params.treemenu[index + 1].toUpperCase() + ' ' + title; + } } - if (to.params.studiensemester_kurzbz) { - title = to.params.studiensemester_kurzbz + ' ' + title; + if (to.params.stdsem) { + title = to.params.stdsem.toUpperCase() + ' ' + title; } document.title = title; }); @@ -209,7 +74,7 @@ router.afterEach((to, from, failure) => { FhcApps.router.makeExtendable(router); const app = Vue.createApp({ - name: 'StudentenverwaltungApp' + name: 'StudVwApp' }); FhcApps.makeExtendable(app); diff --git a/public/js/components/Base/Treemenu.js b/public/js/components/Base/Treemenu.js new file mode 100644 index 000000000..5fe46606c --- /dev/null +++ b/public/js/components/Base/Treemenu.js @@ -0,0 +1,363 @@ +import TreemenuEntry from './Treemenu/Entry.js'; + +import dragClick from '../../directives/dragClick.js'; + +import ApiTreemenu from '../../api/factory/treemenu.js'; + +export default { + components: { + PvTreetable: primevue.treetable, + PvColumn: primevue.column, + TreemenuEntry + }, + directives: { + dragClick + }, + emits: [ + 'selectEntry', + 'drop' + ], + props: { + config: { + type: String, + required: true, + }, + preselectedKey: { + type: Array, + default: null + } + }, + data() { + return { + loading: true, + nodes: [], + selectedKey: [], + expandedKeys: {}, + filters: {}, // TODO(chris): filter only 1st level? + favorites: {on: false, list: []} + } + }, + computed: { + filteredNodes() { + if (this.favorites.on) + return this.nodes.filter(node => this.favorites.list.includes(node.data.path)); + + return this.nodes; + } + }, + watch: { + preselectedKey(newVal, oldVal) { + if (newVal !== oldVal) { + this.setPreselection(); + } + } + }, + methods: { + reloadNodesWithProp(prop, nodes = undefined) { + if (!nodes) + nodes = this.nodes; + + nodes.forEach(node => { + if (node.data[prop]) { + // reload + delete node.children; + this.onExpandTreeNode(node); + } else if (node.children) { + this.reloadNodesWithProp(prop, node.children); + } + }); + }, + findNodeByKey(key, arr) { + if (!arr) + arr = this.nodes; + let res = arr.filter(n => n.key == key); + if (res.length) + return res.pop(); + res = arr.map(n => n.children ? this.findNodeByKey(key, n.children) : null).filter(a => a); + if (res.length) + return res.pop(); + return null; + }, + async onExpandTreeNode(node) { + if (!node.children) { + if (node.data.path) { + /** + * NOTE(chris): activeEl is for keyboard navigation to + * prevent the focus jumping down to the next parent + * instead of the current submenu entry (which is not yet + * loaded) + */ + let activeEl = null; + this.$nextTick(() => { + this.$nextTick(() => { + activeEl = document.activeElement; + }); + }); + this.loading = true; + + return this.$api + .call(ApiTreemenu.get(this.config, node.data.path)) + .then(result => { + const subNodes = result.data.map(this.mapResultToTreeData); + const realNode = this.findNodeByKey(node.key); + if (realNode) + realNode.children = subNodes; + else + node.children = subNodes; // NOTE(chris): fallback should never be the case + + this.$nextTick(() => { + if (activeEl != document.activeElement) + return; + + let treeitem = this.$refs.tree.$el.querySelector('[data-tree-item-key="' + node.key + '"]'); + if (!treeitem) + return; + + treeitem = treeitem.closest('[role="row"]'); + + if (!treeitem) + return; + + treeitem.dispatchEvent(new KeyboardEvent('keydown', { + code: 'ArrowDown', + key: 'ArrowDown' + })); + }); + + this.loading = false; + }) + .catch(this.$fhcAlert.handleSystemError); + } + } + }, + onSelectTreeNode(node) { + this.$emit('selectEntry', node.data); + }, + mapNodesToNoSemReloadNodes(result, node) { + if (node.data.no_sem_reload) + result.push(node); + if (node.children) + result = node.children.reduce(this.mapNodesToNoSemReloadNodes, result); + return result; + }, + mapResultToTreeData(el) { + const cp = { + key: ("" + el.path).replace(/\//g, '-'), + data: el, + label: el.name // TODO(chris): phrase + }; + + if (el.children) + cp.children = el.children.map(this.mapResultToTreeData); + else + cp.leaf = el.leaf || false; + + return cp; + }, + async setPreselection() + { + if (!this.preselectedKey || !this.preselectedKey.length) + { + this.selectedKey = null; + return; + } + + if (!this.preselectedKey || !Array.isArray(this.preselectedKey)) + return; + + // [1, 2, 3, 4, ...] => ['1-2', '3-4', ...] + const parts = this.preselectedKey.reduce((r, c, i, a) => (i%2 && r.push(a[i-1] + '-' + c), r), []); + let currentKey = parts[0]; + let currentNode = this.findNodeByKey(currentKey); + + if (!currentNode) + return; + + if(this.selectedKey) + { + const currentSelectedKey = Object.keys(this.selectedKey).find(Boolean); + if (currentSelectedKey) { + if (currentSelectedKey == currentKey) + return; + /** + * Do not select a new entry if the current is a child of the new one. + * This happens if a child entry of a new stg is selected and the router + * tries to select the stg root entry (because subtrees do not have + * routes yet) + */ + const isChild = this.findNodeByKey( + currentSelectedKey, + currentNode.children || [] + ); + if (isChild) + return; + } + } + + for (let i = 1; i < parts.length; i++) + { + this.expandedKeys[currentNode.key] = true; + + await this.onExpandTreeNode(currentNode); + + currentKey += '-' + parts[i]; + currentNode = this.findNodeByKey(currentKey); + + if (!currentNode) + { + return; + } + } + + this.selectedKey = {[currentNode.key]: true}; + this.onSelectTreeNode(currentNode); + }, + async toggleTreeNode(node) { + if (this.expandedKeys[node.key]) { + delete this.expandedKeys[node.key]; + } else if (!node.leaf) { + await this.onExpandTreeNode(node); + this.expandedKeys[node.key] = true; + } + }, + filterFav() { + this.favorites.on = !this.favorites.on; + this.$api + .call(ApiTreemenu.favorites.set( + this.config, + JSON.stringify(this.favorites) + )); + }, + markFav(key) { + let index = this.favorites.list.indexOf(key.data.path + ''); + + if (index != -1) { + this.favorites.list.splice(index, 1); + } else { + this.favorites.list.push(key.data.path + ''); + } + + this.$api + .call(ApiTreemenu.favorites.set( + this.config, + JSON.stringify(this.favorites) + )); + }, + unsetFavFocus(e) { + if (e.target.dataset?.linkFavAdd !== undefined) { + e.target.tabIndex = -1; + } else { + let items = e.target.querySelectorAll('[data-link-fav-add]:not([tabindex="-1"])'); + items.forEach(el => el.tabIndex = document.activeElement == el ? 0 : -1); + } + }, + setFavFocus(e) { + if (e.target.dataset?.linkFavAdd !== undefined) { + e.target.tabIndex = 0; + } else { + let items = e.target.querySelectorAll('[data-link-fav-add][tabindex="-1"]'); + items.forEach(el => el.tabIndex = 0); + } + } + }, + mounted() { + this.$api + .call(ApiTreemenu.get(this.config)) + .then(result => { + this.nodes = result.data.map(el => { + el.root = true; + return this.mapResultToTreeData(el); + }); + this.setPreselection(); + this.loading = false; + }) + .catch(this.$fhcAlert.handleSystemError); + + this.$api + .call(ApiTreemenu.favorites.get(this.config)) + .then(result => { + if (result.data) { + this.favorites = JSON.parse(result.data); + } + }) + .catch(this.$fhcAlert.handleSystemError); + }, + template: /* html */` + + + + + + + + + + + ` +}; diff --git a/public/js/components/Base/Treemenu/Entry.js b/public/js/components/Base/Treemenu/Entry.js new file mode 100644 index 000000000..c09f413ff --- /dev/null +++ b/public/js/components/Base/Treemenu/Entry.js @@ -0,0 +1,63 @@ +import drop from '../../../directives/drop.js'; +import draggable from '../../../directives/draggable.js'; + +export default { + directives: { + drop, + draggable + }, + emits: [ + 'drop' + ], + props: { + node: { + type: Object, + required: true + } + }, + computed: { + name() { + if (Array.isArray(this.node.data.name)) + return this.$p.t(this.node.data.name); + + return this.node.data.name; + }, + title() { + if (!this.node.data.title) + return this.name; + + if (Array.isArray(this.node.data.title)) + return this.$p.t(this.node.data.title); + + return this.node.data.title; + }, + dropConfig() { + if (!this.node.data?.droplink) + return null; + + const allowed = [ ...this.node.data.droplink ]; + const effect = allowed.shift(); + + return { effect, allowed }; + }, + dragConfig() { + if (!this.node.data?.draggable) + return null; + + let config = [ ...this.node.data.draggable ]; + const effect = config.shift(); + const value = JSON.parse(config.shift()); + + return { effect, value }; + } + }, + template: /* html */` + + {{ name }} + ` +}; diff --git a/public/js/components/Betriebsmittel/Betriebsmittel.js b/public/js/components/Betriebsmittel/Betriebsmittel.js index 08d6a8947..9f8290222 100644 --- a/public/js/components/Betriebsmittel/Betriebsmittel.js +++ b/public/js/components/Betriebsmittel/Betriebsmittel.js @@ -433,6 +433,7 @@ export default { :label="$p.t('wawi/ausgegebenam')" name="ausgegebenam" v-model="formData.ausgegebenam" + model-type="yyyy-MM-dd" auto-apply :enable-time-picker="false" text-input @@ -449,6 +450,7 @@ export default { :label="$p.t('wawi/retouram')" name="retouram" v-model="formData.retouram" + model-type="yyyy-MM-dd" auto-apply :enable-time-picker="false" text-input diff --git a/public/js/components/Cis/Profil/MitarbeiterProfil.js b/public/js/components/Cis/Profil/MitarbeiterProfil.js index a768da0e7..2d20d2e26 100644 --- a/public/js/components/Cis/Profil/MitarbeiterProfil.js +++ b/public/js/components/Cis/Profil/MitarbeiterProfil.js @@ -469,6 +469,7 @@ export default { :tabulator-options="betriebsmittel_table_options" tableOnly :sideMenu="false" + :isUsingPresets="true" /> diff --git a/public/js/components/DetailHeader/DetailHeader.js b/public/js/components/DetailHeader/DetailHeader.js index 0b846df9c..5bf49731b 100644 --- a/public/js/components/DetailHeader/DetailHeader.js +++ b/public/js/components/DetailHeader/DetailHeader.js @@ -1,11 +1,17 @@ import ApiDetailHeader from "../../api/factory/detailHeader.js"; import ApiHandleFoto from "../../api/factory/fotoHandling.js"; import ModalUploadFoto from "./Modal/UploadFoto.js"; +import PvSkeleton from "../../../../index.ci.php/public/js/components/primevue/skeleton/skeleton.esm.min.js"; +import CoreTag from "../Tag/Tag.js"; +import ApiTag from "../../api/factory/stv/tag.js"; +import { idTagFormatter } from "../Tag/tagFormatter.js"; export default { name: 'DetailHeader', components: { - ModalUploadFoto + ModalUploadFoto, + PvSkeleton, + CoreTag }, props: { headerData: { @@ -38,8 +44,26 @@ export default { 'mitarbeiter', ].includes(value) } + }, + currentSemester: { + type: String, + default: '' + }, + isLoading: { //if true, then parent isLoading + type: Boolean, + default: false } }, + inject: { + tagsEnabled: { + from: 'configStvTagsEnabled', + default: false + }, + lists: { + from: 'lists', + required: true + }, + }, computed: { appRoot() { return FHC_JS_DATA_STORAGE_OBJECT.app_root; @@ -58,20 +82,36 @@ export default { hasTileGammaSlot() { return !!this.$slots.titleGammaTile }, + hasTileDeltaSlot() { + return !!this.$slots.titleDeltaTile + }, hasTileUIDSlot() { return !!this.$slots.uid }, - + prestudentIds() { + if (this.headerData[0].prestudent_id) + { + return [this.headerData[0].prestudent_id]; + } + }, + semesterDates(){ + return this.lists.studiensemester?.find(item => item.studiensemester_kurzbz === this.currentSemester) || {}; + } }, created(){ if (this.typeHeader === 'student') { if (!this.headerData) { throw new Error('[DetailHeader] "headerData" is required.') } + + if(this.tagsEnabled) { + this.loadTagsAndRender(this.headerData[0].prestudent_id); + } + } else if (this.typeHeader === 'mitarbeiter') { if (!this.person_id || !this.mitarbeiter_uid || !this.domain) { throw new Error( - '[DetailHeader] "person_id", "mitarbeiter_uid", and "domain" are requried.' + '[DetailHeader] "person_id", "mitarbeiter_uid", and "domain" are required.' ) } this.loadHeaderData(this.person_id, this.mitarbeiter_uid); @@ -86,13 +126,34 @@ export default { }, deep: true, }, + headerData: { + handler(newVal) { + if (this.typeHeader === 'student' && newVal?.length) { + this.getSemesterStati(newVal[0].prestudent_id); + } + + if(this.tagsEnabled) { + this.loadTagsAndRender(this.headerData[0].prestudent_id); + } + }, + deep: true, + immediate: true + }, }, data(){ return{ headerDataMa: {}, departmentData: {}, leitungData: {}, - isFetchingIssues: false + isFetchingIssues: false, + noCurrentStatus: false, + semesterStatiLoading: false, + leitungOrgLoading: false, + departmentDataLoading: false, + headerDataMaLoading: false, + tagEndpoint: ApiTag, + tagData: null, + rebuildData: null, }; }, methods: { @@ -108,29 +169,40 @@ export default { }); }, getHeader(person_id) { + this.headerDataMaLoading = true; return this.$api .call(ApiDetailHeader.getHeader(person_id)) .then(result => { this.headerDataMa = result.data; - }) - .catch(this.$fhcAlert.handleSystemError); + .catch(this.$fhcAlert.handleSystemError) + .finally(() => { + this.headerDataMaLoading = false; + }); }, loadDepartmentData(mitarbeiter_uid) { + this.departmentDataLoading = true; return this.$api .call(ApiDetailHeader.getPersonAbteilung(mitarbeiter_uid)) .then(result => { this.departmentData = result.data; }) - .catch(this.$fhcAlert.handleSystemError); + .catch(this.$fhcAlert.handleSystemError) + .finally(() => { + this.departmentDataLoading = false; + }); }, getLeitungOrg(oekurzbz){ + this.leitungOrgLoading = true; return this.$api .call(ApiDetailHeader.getLeitungOrg(oekurzbz)) .then(result => { this.leitungData = result.data; }) - .catch(this.$fhcAlert.handleSystemError); + .catch(this.$fhcAlert.handleSystemError) + .finally(() => { + this.leitungOrgLoading = false; + }); }, async goToLeitung() { this.loadHeaderData(this.leitungData.person_id, this.leitungData.uid); @@ -179,7 +251,98 @@ export default { } else { return 'data:image/jpeg;base64,' + foto; } - } + }, + getSemesterStati(prestudent_id){ + this.semesterStatiLoading = true; + this.$api + .call(ApiDetailHeader.getSemesterStati(prestudent_id)) + .then(result => { + this.semesterStati = result.data; + this.setNoCurrentStatus(); + }) + .catch(this.$fhcAlert.handleSystemError) + .finally(() => { + this.semesterStatiLoading = false; + }); + }, + setNoCurrentStatus() { + if(!Array.isArray(this.semesterStati)) + { + this.noCurrentStatus = false; + } + + if(!this.semesterStati.some(item => item.studiensemester_kurzbz === this.currentSemester)) { + this.noCurrentStatus = true; + } + else + { + this.noCurrentStatus = false; + } + }, + //methods tags + async loadTagsAndRender(prestudent_id) { + await this.getAllTags(prestudent_id); + + const container = idTagFormatter( + prestudent_id, + this.tagData, + this.$refs.tagComponent, + 'prestudent_id', + this.semesterDates.start, + this.semesterDates.ende + ); + + if(this.headerData.length == 1) { + this.$refs.tagWrapper.innerHTML = ''; + this.$refs.tagWrapper.appendChild(container); + } + }, + getAllTags(prestudent_id){ + return this.$api + .call(ApiTag.getAllTagsPrestudent({prestudent_id})) + .then(result => { + this.tagData = result.data.sort((a, b) => { + let adone = a.done ? 1 : 0; + let bdone = b.done ? 1 : 0; + + if (adone !== bdone) { + return adone - bdone; + } + return a.prioritaet - b.prioritaet; + }); + }) + .catch(this.$fhcAlert.handleSystemError); + }, + addedTag(addedTag) + { + this.reload(); + }, + deletedTag(id) + { + this.reload(); + }, + updatedTag(updatedTag) + { + this.reload(); + }, + rebuildPrestudentTags(){ + const id = [this.headerData[0].prestudent_id]; + + const params = { + ids: id, + typeId: 'prestudent_id', + sem: this.currentSemester + }; + + return this.$api + .call(ApiTag.rebuildTagsforTypeId(params)) + .then(result => { + this.rebuildData = result.data; + console.log("Rebuild manually triggered"); + this.reload(); + }) + .catch(this.$fhcAlert.handleSystemError); + }, }, template: `
@@ -200,7 +363,6 @@ export default { @@ -330,27 +570,51 @@ export default {
-

{{headerDataMa.titelpre}} {{headerDataMa.vorname}} {{headerDataMa.nachname}}, {{headerDataMa.titelpost}}

- {{departmentData.organisationseinheittyp_kurzbz}} - {{departmentData.bezeichnung}} - | - Vorgesetzte*r - - {{leitungData.titelpre}} {{leitungData.vorname}} {{leitungData.nachname}} - -

- Email - - - {{ mitarbeiter_uid }}@{{ domain }} +

{{headerDataMa.titelpre}} {{headerDataMa.vorname}} {{headerDataMa.nachname}}, {{headerDataMa.titelpost}}

+

+
+ +
+
+ Email + + +
+ +
+ | + DW + + +
+
+ +
@@ -358,22 +622,42 @@ export default {
+
+

+
+ + +
+

-
+
+ + +

-
+
+ + +

-
+
+ + +

UID

-
+
+ + +
+
diff --git a/public/js/components/Form/Input.js b/public/js/components/Form/Input.js index 4ad555ab7..62c6217e6 100644 --- a/public/js/components/Form/Input.js +++ b/public/js/components/Form/Input.js @@ -29,7 +29,8 @@ export default { label: String, // NOTE(chris): remove these from $attrs array to prevent doubled event listeners onInput: [Array, Function], - 'onUpdate:modelValue': [Array, Function] + 'onUpdate:modelValue': [Array, Function], + titleActionButton: String }, data() { return { @@ -317,6 +318,7 @@ export default { :id="idCmp" :name="name" :class="validationClass" + :titleActionButton="titleActionButton" @update:model-value="clearValidationForThisName" > diff --git a/public/js/components/Form/Upload/Image.js b/public/js/components/Form/Upload/Image.js index b0b8d5b78..4b96d4c85 100644 --- a/public/js/components/Form/Upload/Image.js +++ b/public/js/components/Form/Upload/Image.js @@ -1,9 +1,14 @@ export default { emits: [ - 'update:modelValue' + 'update:modelValue', + 'actionbutton-clicked' ], props: { - modelValue: String + modelValue: String, + titleActionButton: { + type: String, + default: "" + } }, computed: { valueAsBase64DataString() { @@ -28,6 +33,9 @@ export default { }, deleteImage() { this.$emit('update:modelValue', ''); + }, + emitAction(){ + this.$emit('actionbutton-clicked', this.modelValue); } }, template: ` @@ -42,6 +50,7 @@ export default { + diff --git a/public/js/components/Funktionen/Funktionen.js b/public/js/components/Funktionen/Funktionen.js index 2c1bce445..bf02995a1 100644 --- a/public/js/components/Funktionen/Funktionen.js +++ b/public/js/components/Funktionen/Funktionen.js @@ -551,6 +551,7 @@ export default { container-class="mb-3 col-3" type="DatePicker" v-model="formData.datum_von" + model-type="yyyy-MM-dd" name="datum_von" :label="$p.t('ui/from') + ' *'" auto-apply @@ -566,6 +567,7 @@ export default { container-class="mb-3 col-3" type="DatePicker" v-model="formData.datum_bis" + model-type="yyyy-MM-dd" name="datum_bis" :label="$p.t('global/bis')" auto-apply diff --git a/public/js/components/LVVerwaltung/Details/Gruppen.js b/public/js/components/LVVerwaltung/Details/Gruppen.js index 024bf6468..5244136cc 100644 --- a/public/js/components/LVVerwaltung/Details/Gruppen.js +++ b/public/js/components/LVVerwaltung/Details/Gruppen.js @@ -2,6 +2,7 @@ import {CoreFilterCmpt} from "../../filter/Filter.js"; import FormForm from '../../Form/Form.js'; import FormInput from '../../Form/Input.js'; import ApiGruppe from "../../../api/lehrveranstaltung/gruppe.js"; +import drop from '../../../directives/drop.js'; export default{ name: "LVGruppen", components: { @@ -9,6 +10,9 @@ export default{ FormForm, FormInput }, + directives: { + drop + }, props: { lehreinheit_id: Number }, @@ -187,28 +191,48 @@ export default{ this.reload(); }) }, + onDropVerband(node, gruppe) { + this.$api.call(ApiGruppe.getGruppe(gruppe)) + .then(result => result.data) + .then(result => { + + let newData = { + 'gid': result, + 'lehreinheit_id': this.lehreinheit_id, + 'lehrverband': gruppe.lehrverband, + } + this.$api.call(ApiGruppe.add(newData)) + .then(result => { + this.reload() + }) + }); + + }, + }, template: ` - - - - +
+ + + + +
` }; \ No newline at end of file diff --git a/public/js/components/LVVerwaltung/LVVerwaltung.js b/public/js/components/LVVerwaltung/LVVerwaltung.js index 42c50bdfa..6d9331d38 100644 --- a/public/js/components/LVVerwaltung/LVVerwaltung.js +++ b/public/js/components/LVVerwaltung/LVVerwaltung.js @@ -1,16 +1,18 @@ import CoreSearchbar from "../searchbar/searchbar.js"; import VerticalSplit from "../verticalsplit/verticalsplit.js"; +import HorizontalSplit from "../horizontalsplit/horizontalsplit.js"; import AppMenu from "../AppMenu.js"; -import StvVerband from "../Stv/Studentenverwaltung/Verband.js"; +import BaseTreemenu from '../Base/Treemenu.js'; import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js"; import LvTable from "./Setup/Table.js"; import LvTabs from "./Setup/Tabs.js"; +import NavLanguage from "../navigation/Language.js"; import ApiDetails from "../../api/lehrveranstaltung/details.js"; import ApiLektor from "../../api/lehrveranstaltung/lektor.js"; -import ApiGruppe from "../../api/lehrveranstaltung/gruppe.js"; -import ApiStudiengangTree from "../../api/lehrveranstaltung/studiengangtree.js"; import ApiSearchbar from "../../api/factory/searchbar.js"; +import AppConfig from "../AppConfig.js"; +import ApiLvConfig from "../../api/lehrveranstaltung/config.js"; export default { @@ -18,23 +20,23 @@ export default { components: { CoreSearchbar, VerticalSplit, + HorizontalSplit, AppMenu, - StvVerband, + BaseTreemenu, StvStudiensemester, LvTable, LvTabs, + AppConfig, + NavLanguage }, props: { + avatarUrl: String, + logoutUrl: String, defaultSemester: String, lvRoot: String, permissions: Object, config: Object, - stg: { type: String, required: false }, - semester: { type: [Number, String], required: false, default: null }, - studiensemester_kurzbz: { type: String, required: false, default: null }, - emp: { type: String, required: false, default: null } }, - provide() { return { currentSemester: Vue.computed(() => this.selectedStudiensemester), @@ -44,46 +46,27 @@ export default { lehreinheitAnmerkungDefault: (this.config.lehreinheitAnmerkungDefault || '').replace(/\\n/g, '\n'), lehreinheitRaumtypDefault: this.config.lehreinheitRaumtypDefault, lehreinheitRaumtypAlternativeDefault: this.config.lehreinheitRaumtypAlternativeDefault, - permissionLehrveranstaltung: this.permissions['lehre/lehrveranstaltung'], permissionGruppenEntfernen: this.permissions['lv-plan/gruppenentfernen'], permissionLektorEntfernen: this.permissions['lv-plan/lektorentfernen'], - } - }, - mounted() { - this.updateFilter(); - }, - watch: { - stg() { - this.updateFilter(); - }, - semester() { - this.updateFilter(); - }, - selectedStudiensemester() { - this.updateFilter(); - }, - emp() { - this.updateFilter(); - }, - studiensemester_kurzbz(newVal) { - this.selectedStudiensemester = newVal ?? this.defaultSemester; + language: Vue.computed(() => this.$p.user_language), + isMobile: false, } }, data() { return { + sidebarCollapsed: false, + appconfig:{}, + configEndpoints: ApiLvConfig, selected: [], - studiengang: "", - filter: {}, - selectedStudiensemester: this.studiensemester_kurzbz ?? this.defaultSemester, - endpoint: ApiStudiengangTree, + studiengang_kz: null, + selectedStudiensemester: this.defaultSemester, dropdowns: { studiensemester_array: [], sprachen_array: [], lehrform_array: [], raumtyp_array: [], }, - selectedStudiengang: '', searchbaroptions: { origin: 'lvverwaltung', cssclass: "position-relative", @@ -107,28 +90,112 @@ export default { }, } }, + computed: { + filter() { + let filter = { + emp: this.$route.params.emp, + studiensemester_kurzbz: this.selectedStudiensemester + }; + let index; + if (this.$route.params.treemenu) { + index = this.$route.params.treemenu.indexOf('stg'); + if (index > -1) + filter.stg = this.$route.params.treemenu[index+1]; + index = this.$route.params.treemenu.indexOf('semester'); + if (index > -1) + filter.semester = this.$route.params.treemenu[index+1]; + } + filter.activeFilter = filter.emp ? 'employee' : filter.stg ? 'verband' : null; + return filter; + }, + emp() { + return this.filter.emp; + }, + stg() { + return this.filter.stg; + }, + semester() { + return this.filter.semester; + }, + appMenuExtraItems() + { + let extraItems = []; + + const studiengang_kz = this.studiengang_kz || ''; + const studiensemester = this.selectedStudiensemester; + const semester = this.semester || ''; + const uid = this.emp || ''; + + + extraItems.push( + { + description: 'lehre/berichte', + requires: ['studiengang_kz'], + children: [ + { + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/lvplanung.xls.php' + + '?studiengang_kz=' + studiengang_kz + + '&studiensemester_kurzbz=' + studiensemester + + '&semester=' + semester, + description: 'lehre/lvplanung', + requires: ['studiengang_kz'] + }, + { + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/lehrauftragsliste_gst.xls.php' + + '?studiengang_kz=' + studiengang_kz + + '&studiensemester_kurzbz=' + studiensemester + + '&semester=' + semester, + description: 'lehre/lehrauftragsliste', + requires: ['studiengang_kz'] + }, + { + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/pdfExport.php?xml=lehrauftrag.xml.php' + + '&xsl=Lehrauftrag' + + '&stg_kz=' + studiengang_kz + + '&ss=' + studiensemester, + description: 'lehre/lehrauftraege', + requires: ['studiengang_kz'] + }, + { + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/pdfExport.php?xml=lehrauftrag.xml.php' + + '&xsl=Lehrauftrag' + + '&stg_kz=' + studiengang_kz + + '&ss=' + studiensemester + + '&uid=' + uid, + description: 'lehre/lehrauftragslisteemp', + requires: ['emp'] + } + ] + }, + { + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'vilesci/lehre/lehrveranstaltung.php' + + '?stg_kz=' + studiengang_kz, + description: 'lehre/extrakvverwaltung', + requires: ['studiengang_kz'] + } + ); + + return extraItems; + } + }, + + watch: { + sidebarCollapsed(newVal) { + if(newVal) + this.$refs.hSplit.collapseLeft() + else + this.$refs.hSplit.showBoth() + } + }, methods: { updateFilter() { - const filter = { - stg: this.stg, - emp: this.emp, - semester: this.semester, - studiensemester_kurzbz: this.selectedStudiensemester, - activeFilter: this.emp ? 'employee' : this.stg ? 'verband' : null - } - - if (this.stg !== undefined) - { - this.selectedStudiengang = this.semester !== '' && this.semester - ? `${this.stg}/${this.semester}` - : this.stg; - } - else - { - this.selectedStudiengang = ''; - } - this.filter = filter; + // deprecated }, handleRowClicked(data) { @@ -136,77 +203,107 @@ export default { }, onSelectEmployee(emp) { - const { stg, semester } = this.filter; - - let studiensemester_kurzbz = this.selectedStudiensemester; - const params = { emp }; - - if (stg) - params.stg = stg; - if (semester !== null) - params.semester = semester; - if (studiensemester_kurzbz) - params.studiensemester_kurzbz = studiensemester_kurzbz; - - this.$router.push({ name: 'byEmp', params }) + this.$router.push({ + name: 'emp', + params: { + ...this.$route.params, + emp + } + }); }, - - onSelectVerband({link}) + onSelectVerband({ path: link, stg_kz }) { - let stg = null; - let semester = null; - let studiensemester_kurzbz = this.selectedStudiensemester; - - if (typeof link === 'number') - stg = link; - else if (typeof link === 'string') - { - [stg, semester] = link.split('/'); - } - - const routeName = this.filter.emp ? 'byEmp' : 'byStg'; - const params = { stg }; - - params.semester = ''; - if (semester !== null) - params.semester = semester; - if (studiensemester_kurzbz) - params.studiensemester_kurzbz = studiensemester_kurzbz; - if (this.filter.emp) - params.emp = this.filter.emp; - this.$router.push({ name: routeName, params }); + this.$router.push({ + name: this.$route.name == 'emp' ? 'emp' : 'treemenu', + params: { + ...this.$route.params, + treemenu: link.split('/') + } + }); this.selected = []; + this.studiengang_kz = stg_kz; }, resetEmployeeFilter() { - const newParams = { ...this.filter, activeFilter: 'verband' }; - if (newParams.stg === '') - this.$router.replace({ name: 'index' }); - else - { - delete newParams.emp; - - this.$router.replace({ name: 'byStg', params: newParams }); - } + let params = { ...this.$route.params }; + + delete params.emp; + + this.$router.replace({ + name: params.treemenu ? 'treemenu' : 'stdsem', + params + }); }, resetStgFilter() { - const newParams = { ...this.filter, activeFilter: 'emp' }; - delete newParams.stg; - this.selectedStudiengang = ''; - this.$router.replace({ name: 'byEmp', params: newParams }); + let params = { ...this.$route.params }; + let index = params.treemenu.indexOf('stg'); + if (index > -1) + params.treemenu = params.treemenu.slice(0, index); + if (params.treemenu.length) { + this.$router.replace({ + params: { + treemenu: params.treemenu + } + }); + } else { + delete params.treemenu; + this.$router.replace({ + name: params.emp ? 'emp' : 'stdsem', + params + }); + } }, - searchfunction(params) { - return this.$api.call(ApiSearchbar.search(params)); + searchfunction(params, config) { + return this.$api.call(ApiSearchbar.search(params), config); }, - studiensemesterChanged(newValue) { - const routeName = this.filter.activeFilter === 'employee' ? 'byEmp' : 'byStg'; - const newParams = {...this.filter, studiensemester_kurzbz: newValue}; - this.$router.push({ name: routeName, params: newParams }); + studiensemesterChanged(stdsem) { + if (!stdsem) { + // no valid studiensemester in url + this.$router.replace({ + params: { + stdsem: this.defaultSemester.toLowerCase() + } + }); + return; + } + if (stdsem.toLowerCase() != this.selectedStudiensemester?.toLowerCase()) { + this.$router.push({ + params: { + stdsem: stdsem.toLowerCase() + } + }); + } + this.selectedStudiensemester = stdsem; this.selected = []; }, + isDisabled(item) + { + if (!item?.requires?.length) + return false; + + const values = { + studiengang_kz: this.studiengang_kz, + stg: this.stg, + emp: this.emp, + studiensemester: this.selectedStudiensemester, + semester: this.semester + }; + + return item.requires.some(req => !values[req]); + + } }, created() { + if (!this.$route.params.stdsem) { + this.$router.replace({ + name: 'stdsem', + params: { + stdsem: this.defaultSemester.toLowerCase() + } + }); + } + this.$p.loadCategory(['lehre', 'person', 'global']) this.$api.call(ApiDetails.getStudiensemester()) @@ -239,9 +336,8 @@ export default { }) .catch(this.$fhcAlert.handleSystemError); }, - template: /* html */` -
+
@@ -278,54 +419,99 @@ export default {
- + + +
- - -
- - diff --git a/public/js/components/Messages/Details/NewMessage/NewDiv.js b/public/js/components/Messages/Details/NewMessage/NewDiv.js index 00b4e92a8..6a0dfa222 100644 --- a/public/js/components/Messages/Details/NewMessage/NewDiv.js +++ b/public/js/components/Messages/Details/NewMessage/NewDiv.js @@ -55,6 +55,7 @@ export default { previewBody: "", replyData: null, messageSent: false, + isSending: false } }, methods: { @@ -104,16 +105,21 @@ export default { data.append('data', JSON.stringify(this.formData)); data.append('ids', JSON.stringify(this.id)); + //for disabling sendButton and show Spinner + this.isSending = true; + return this.$api .call(ApiMessages.sendMessage(this.typeId, data)) .then(response => { - if(this.openMode == "inSamePage") + if(this.openMode == "inSamePage") { this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSent')); + } this.hideTemplate(); this.resetForm(); this.messageSent = true; }).catch(this.$fhcAlert.handleSystemError) .finally(() => { + this.isSending = false; if(this.openMode == "inSamePage" && this.id.length == 1 ){ this.$emit('reloadTable'); } @@ -413,7 +419,12 @@ export default { - +
diff --git a/public/js/components/Stv/Studentenverwaltung.js b/public/js/components/Stv/Studentenverwaltung.js index 66ff7f891..b9ee54526 100644 --- a/public/js/components/Stv/Studentenverwaltung.js +++ b/public/js/components/Stv/Studentenverwaltung.js @@ -18,6 +18,7 @@ import CoreSearchbar from "../searchbar/searchbar.js"; import NavLanguage from "../navigation/Language.js"; import VerticalSplit from "../verticalsplit/verticalsplit.js"; +import HorizontalSplit from "../horizontalsplit/horizontalsplit.js"; import AppMenu from "../AppMenu.js"; import AppConfig from "../AppConfig.js"; import StvVerband from "./Studentenverwaltung/Verband.js"; @@ -26,8 +27,8 @@ import StvDetails from "./Studentenverwaltung/Details.js"; import StvStudiensemester from "./Studentenverwaltung/Studiensemester.js"; import ApiSearchbar from "../../api/factory/searchbar.js"; +import ApiTreemenu from "../../api/factory/treemenu.js"; import ApiStv from "../../api/factory/stv.js"; -import ApiStvVerband from '../../api/factory/stv/verband.js'; import ApiStvConfig from '../../api/factory/stv/config.js'; @@ -37,6 +38,7 @@ export default { CoreSearchbar, NavLanguage, VerticalSplit, + HorizontalSplit, AppMenu, AppConfig, StvVerband, @@ -53,11 +55,7 @@ export default { avatarUrl: String, logoutUrl: String, activeAddons: String, // semicolon separated list of active addons - url_studiensemester_kurzbz: String, - url_mode: String, - url_prestudent_id: String, - url_tab: String, - url_studiengang: String + authUid: String }, provide() { return { @@ -91,7 +89,9 @@ export default { hasZGVMasterPermission: this.permissions['student/editMakkZgv'], hasZGVDoctorPermission: this.permissions['student/editDokZgv'], hasBismeldenPermission: this.permissions['student/editBismelden'], - + authUid: this.authUid, + language: Vue.computed(() => this.$p.user_language), + isMobile: false, } }, data() { @@ -140,7 +140,6 @@ export default { mergeResults: 'person' }, studiengangKz: undefined, - studiengangKuerzel: '', studiensemesterKurzbz: this.defaultSemester, selected_semester: undefined, selected_orgform: undefined, @@ -149,7 +148,6 @@ export default { sprachen: [], geschlechter: [] }, - verbandEndpoint: ApiStvVerband, filter: [] } }, @@ -192,34 +190,111 @@ export default { } return extraItems; + }, + appMenuLvPlanungItems() { + const extraItems = []; + + if (this.studiengangKz !== undefined && this.selected_semester !== undefined) { + const studiengang_kz = String(this.studiengangKz); + const semester = String(this.selected_semester); + const orgform = this.selected_orgform || ''; + + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/lvplanung.xls.php?' + + '&studiengang_kz=' + studiengang_kz + + '&semester=' + semester + + '&studiensemester_kurzbz=' + this.studiensemesterKurzbz + + '&orgform_kurzbz=' + orgform, + description: 'stv/lvplanung_xls' + }); + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/lvplanung.php?' + + '&studiengang_kz=' + studiengang_kz + + '&semester=' + semester, + description: 'stv/lvplanung_html' + }); + } + + return extraItems; + }, + appMenuBisItems() { + const extraItems = []; + + if (this.studiengangKz !== undefined) { + const studiengang_kz = String(this.studiengangKz); + + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'vilesci/bis/studentenmeldung.php?' + + 'stg_kz=' + studiengang_kz, + description: 'stv/bis_stud_meldung' + }); + extraItems.push({ + link: FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'system/checkStudenten.php?' + + 'stg_kz=' + studiengang_kz, + description: 'stv/bis_stud_plausicheck' + }); + } + + return extraItems; + }, + linkRt(){ + return FHC_JS_DATA_STORAGE_OBJECT.app_root + '/vilesci/stammdaten/reihungstestverwaltung.php' + }, + selected_uid(){ + return this.selected?.[this.selected.length - 1]?.uid ?? null; + }, + linkGradeList(){ + return FHC_JS_DATA_STORAGE_OBJECT.app_root + 'index.ci.php/person/gradelist/index/' + this.selected_uid + }, + listEndpoint() { + if (['index', 'stdsem'].includes(this.$route.name)) + return null; + + if (this.$route.params.query) { + return ApiStv.students.search({ + searchstr: this.$route.params.query, + types: this.$route.params.types?.split('+') || [] + }, this.$route.params.stdsem); + } + + let url = 'stdsem/' + this.$route.params.stdsem + '/'; + + if (this.$route.params.treemenu) { + url += this.$route.params.treemenu.join('/'); + return ApiTreemenu.data('stv', url); + } + + const paramName = Object.keys(this.$route.params).find(k => k != 'stdsem'); + if (paramName) { + url += this.$route.name + '/' + this.$route.params[paramName]; + return ApiTreemenu.data('stv', url); + } + + return null; + }, + studiengangKuerzel() { + if (!this.lists?.stgs) + return ''; + + const stg = this.lists.stgs + .find(stg => stg.studiengang_kz == this.studiengangKz); + + if (stg) + return (stg.typ + stg.kurzbz).toUpperCase(); + + return ''; } }, watch: { - 'url_studiensemester_kurzbz': function (newVal, oldVal) { - if (newVal !== oldVal) { - this.studiensemesterKurzbz = newVal; - if(this.$route.name === 'search') - { - this.handleSearchUrl(); - } - else - { - this.$refs.stvList.updateUrl(); - this.$refs.details.reload(); - } + listEndpoint(n, o) { + if (n != o) { + this.$refs.stvList.updateUrl(n || []); } }, - 'url_studiengang': function (newVal, oldVal) { - if (newVal !== oldVal) { - this.checkUrlStudiengang(); - } - }, - 'url_mode': function () { - this.handlePersonUrl(); - }, - url_prestudent_id() { - this.handlePersonUrl(); - }, 'appconfig.font_size'() { // add to html class const classList = Object.keys(this.$refs.config.setup.font_size.options); @@ -235,6 +310,15 @@ export default { } } } + }, + sidebarCollapsed(newVal) { + if(newVal) this.$refs.hSplit.collapseLeft() + else this.$refs.hSplit.showBoth() + }, + studiengangKz(newVal, oldVal) { + if (newVal !== oldVal) { + this.$refs.stvList.clearSelection(); + } } }, methods: { @@ -272,177 +356,99 @@ export default { ); }, onSelectVerband({ link, studiengang_kz, semester, orgform_kurzbz }) { - let urlpath = String(link); - if (!urlpath.match(/\/prestudent/)) - { - urlpath = 'CURRENT_SEMESTER' + '/' + urlpath; - } - this.$refs.stvList.updateUrl(ApiStv.students.verband(urlpath)); - this.studiengangKz = studiengang_kz; this.selected_semester = semester; this.selected_orgform = orgform_kurzbz; - const stg = this.lists.stgs.find((element) => { - return (element.studiengang_kz === this.studiengangKz); - }); - if (stg) - { - this.studiengangKuerzel = (stg.typ + stg.kurzbz).toUpperCase() - this.$router.push({ - name: 'studiengang', - params: { - studiensemester_kurzbz: this.studiensemesterKurzbz, - studiengang: this.studiengangKuerzel - } - }); - } else - { - this.studiengangKuerzel = ''; - this.$router.push({ - name: 'studiensemester', - params: { - studiensemester_kurzbz: this.studiensemesterKurzbz - } - }); - } - }, - studiensemesterChanged(v) { - this.studiensemesterKurzbz = v; this.$router.push({ + name: 'treemenu', params: { - studiensemester_kurzbz: v + treemenu: link.split('/') } }); }, + studiensemesterChanged(stdsem) { + if (!stdsem) { + // no valid studiensemester in url + this.$router.replace({ + params: { + stdsem: this.defaultSemester.toLowerCase() + } + }); + return; + } + if (stdsem.toLowerCase() != this.studiensemesterKurzbz?.toLowerCase()) { + this.$router.push({ + params: { + stdsem: stdsem.toLowerCase() + } + }); + } + this.studiensemesterKurzbz = stdsem; + }, reloadList() { this.$refs.stvList.reload(); }, searchfunction(params, config) { return this.$api.call(ApiSearchbar.searchStv(params), config); }, - handlePersonUrl() { - if (this.$route.params.id) { - this.$refs.stvList.updateUrl( - ApiStv.students.uid(this.$route.params.id, 'CURRENT_SEMESTER'), - true - ); - } else if (this.$route.params.prestudent_id) { - this.$refs.stvList.updateUrl( - ApiStv.students.prestudent(this.$route.params.prestudent_id, 'CURRENT_SEMESTER'), - true - ); - } else if (this.$route.params.person_id) { - this.$refs.stvList.updateUrl( - ApiStv.students.person(this.$route.params.person_id, 'CURRENT_SEMESTER'), - true - ); - } else if (this.$route.params.searchstr) { - this.handleSearchUrl(); - } - else - { - this.clearTabulator(); - } - }, - handleSearchUrl() { - const searchsettings = { - searchstr: this.$route.params.searchstr, - types: this.$route.params.types?.split('+') || [] - }; - - // init into student list - this.$refs.stvList.updateUrl( - ApiStv.students.search(searchsettings, this.studiensemesterKurzbz) - ); - - // init into searchbar - this.$refs.searchbar.searchsettings.searchstr = searchsettings.searchstr; - this.$refs.searchbar.searchsettings.types = searchsettings.types; - this.$nextTick(this.blurSearchbar); - }, - clearTabulator() { - if(['index', 'studiensemester'].includes(this.$route.name)) - { - if(this.$refs?.stvList?.$refs?.table?.tabulator) - { - this.$refs.stvList.$refs.table.tabulator.setData([]); - } - } - }, - checkUrlStudiengang() { - if (this.url_studiengang) { - const stg = this.lists.stgs.find((element) => { - const kuerzel = (element.typ + element.kurzbz).toUpperCase(); - return (this.url_studiengang === kuerzel); - }); - if (stg) { - this.studiengangKz = stg.studiengang_kz; - this.studiengangKuerzel = (stg.typ + stg.kurzbz).toUpperCase(); - } else { - this.$router.replace({ - name: 'studiensemester', - params: { - studiensemester_kurzbz: this.studiensemesterKurzbz - } - }); - } - } - else - { - this.studiengangKz = undefined; - this.studiengangKuerzel = ''; - this.clearTabulator(); - } - }, onSearch(e) { - this.deleteCustomFilter(); const searchsettings = { ...this.$refs.searchbar.searchsettings }; + if (searchsettings.searchstr.length >= 2) { - this.blurSearchbar(); + this.deleteCustomFilter(); if (!searchsettings.types.length || searchsettings.types.length == this.$refs.searchbar.types.length) { this.$router.push({ name: 'search', params: { - studiensemester_kurzbz: this.studiensemesterKurzbz, - searchstr: searchsettings.searchstr + query: searchsettings.searchstr } }); } else { this.$router.push({ - name: 'search_w_types', + name: 'searchtypes', params: { - studiensemester_kurzbz: this.studiensemesterKurzbz, - searchstr: searchsettings.searchstr, + query: searchsettings.searchstr, types: searchsettings.types.join('+') } }); } } }, - blurSearchbar() { - this.$refs.searchbar.$refs.input.blur(); - this.$refs.searchbar.abort(); - this.$refs.searchbar.hideresult(); - }, handleCustomFilter(filter){ this.filter = filter; }, deleteCustomFilter(){ this.$refs.stvList.resetFilter(); + }, + showAlertNoSelectedStudent(){ + this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseStudent')); + }, + showAlertMultipleStudents() { + this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseOnlyOneStudent')); + }, + showAlertNoGroupChosen(){ + this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseGroupSem')); + }, + showAlertNoDegreeProgramChosen(){ + this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseDegreeProgram')); + }, + showMenu(refName) { + bootstrap.Dropdown.getOrCreateInstance(this.$refs[refName]).show(); + }, + hideMenu(refName) { + bootstrap.Dropdown.getOrCreateInstance(this.$refs[refName]).hide(); } }, created() { - if (!this.url_studiensemester_kurzbz) { + if (!this.$route.params.stdsem) { this.$router.replace({ - name: 'studiensemester', + name: 'stdsem', params: { - studiensemester_kurzbz: this.defaultSemester + stdsem: this.defaultSemester.toLowerCase() } }); - } else { - this.studiensemesterKurzbz = this.url_studiensemester_kurzbz; } this.$api @@ -478,7 +484,6 @@ export default { .then(result => { this.lists.stgs = result.data; this.lists.active_stgs = this.lists.stgs.filter(stg => stg.aktiv); - this.checkUrlStudiengang(); }) .catch(this.$fhcAlert.handleSystemError); @@ -505,7 +510,8 @@ export default { .catch(this.$fhcAlert.handleSystemError); }, mounted() { - this.handlePersonUrl(); + if (this.listEndpoint) + this.$refs.stvList.updateUrl(this.listEndpoint); }, template: /* html */`
@@ -606,19 +612,30 @@ export default {
- -
- - - - -
+ + + + + +
diff --git a/public/js/components/Stv/Studentenverwaltung/Details.js b/public/js/components/Stv/Studentenverwaltung/Details.js index e59492cf5..8cae83e38 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details.js +++ b/public/js/components/Stv/Studentenverwaltung/Details.js @@ -2,12 +2,18 @@ import FhcTabs from "../../Tabs.js"; import FhcHeader from "../../DetailHeader/DetailHeader.js"; import ApiStvApp from '../../../api/factory/stv/app.js'; +import ApiStudent from '../../../api/factory/stv/students.js'; // TODO(chris): alt & title // TODO(chris): phrasen export default { name: "DetailsPrestudent", + inject: { + currentSemester: { + from: 'currentSemester', + }, + }, components: { FhcTabs, FhcHeader @@ -15,7 +21,9 @@ export default { data() { return { configStudent: {}, - configStudents: {} + configStudents: {}, + activeTab: null, + localStudent: null }; }, props: { @@ -40,6 +48,9 @@ export default { } return Object.fromEntries(Object.entries(this.configStudents).filter(([ , value ]) => !value.showOnlyWithUid && !value.showOnlyWithUid)); }, + isLoading() { + return this.students === null; //null-> loading, [] -> empty, [...] -> data, necessary for skeleton in child + }, tile_PersId(){ let tile = this.students[0].person_id != null ? this.students[0].person_id : '-'; return tile; @@ -52,11 +63,34 @@ export default { let tile = this.students[0].matrikelnr != null ? this.students[0].matrikelnr : '-'; return tile; }, + tile_PrestdId(){ + let tile = this.students[0].prestudent_id != null ? this.students[0].prestudent_id : '-'; + return tile; + }, + tile_UID(){ + let tile = this.students[0].uid != null ? this.students[0].uid : '-'; + return tile; + }, }, watch: { '$p.user_language.value'(n, o) { if (n !== o && o !== undefined) this.loadConfig(); + }, + currentSemester(newVal) { + if ( + Array.isArray(this.students) && + this.students.length === 1 && + newVal !== this.students[0].query_studiensemester_kurzbz + ) { + this.reloadDataStudent(); + } + else { + this.localStudent = null; + } + }, + students() { + this.localStudent = null; } }, methods: { @@ -74,10 +108,28 @@ export default { }) .catch(this.$fhcAlert.handleSystemError); }, + handleTabChanged(key) { + this.activeTab = key + this.reload() + }, reload() { if (this.$refs.tabs?.$refs?.current?.reload) this.$refs.tabs.$refs.current.reload(); }, + reloadDataStudent(){ + this.localStudent = null; + const studentArr = this.students; + + if (!studentArr || !studentArr.length) { + return; + } + + this.$api + .call(ApiStudent.uid(studentArr[0].uid, this.currentSemester)) + .then(result => { + this.localStudent = result.data; + }); + }, reloadList() { this.$emit('reload'); }, @@ -92,31 +144,35 @@ export default {
- - - - - - - + + + + + + + + + - +
Loading... diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Abschlusspruefung/Abschlusspruefung.js b/public/js/components/Stv/Studentenverwaltung/Details/Abschlusspruefung/Abschlusspruefung.js index 1c2c53dd7..e9f6f7492 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Abschlusspruefung/Abschlusspruefung.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Abschlusspruefung/Abschlusspruefung.js @@ -78,7 +78,7 @@ export default { layout: 'fitDataStretchFrozen', layoutColumnsOnNewData: false, height: 'auto', - minHeight: '200', + minHeight: '200' } }, computed: { @@ -238,6 +238,16 @@ export default { } return this.student.map(e => e.uid); }, + studentNames() { + if (this.student.uid) + { + return [this.student.vorname + ' ' + this.student.nachname]; + } + + const array = this.student.map(e => ' ' + e.vorname + ' ' + e.nachname + '(' + e.uid +')'); + + return array.toString(); + }, studentKzs(){ if (this.student.uid) { @@ -287,10 +297,51 @@ export default { .catch(this.$fhcAlert.handleSystemError); }, actionNewAbschlusspruefung() { - this.resetForm(); - this.statusNew = true; - this.$refs.finalexamModal.show(); this.setDefaultFormData(); + this.statusNew = true; + + //prepare local Storage + let STORAGE_KEY = 'finalExamDefaultData'; + const id = '20260625_01'; + const stored = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; + + if (stored[id]) { + const data = stored[id]; + + this.formData.pruefungstyp_kurzbz = data.pruefungstyp_kurzbz; + this.formData.datum = data.datum; + this.formData.sponsion = data.sponsion; + this.formData.akadgrad_id = data.akadgrad_id; + this.formData.pruefungsantritt_kurzbz = data.pruefungsantritt_kurzbz; + + if (data.vorsitz_uid) { + this.selectedVorsitz = { + mitarbeiter_uid: data.vorsitz_uid, + person_id: data.vorsitz_person_id, + label: data.vorsitz_label + }; + } + + if (data.pruefer1_person_id) { + this.selectedPruefer1 = { + person_id: data.pruefer1_person_id, + label: data.pruefer1_label + }; + } + if (data.pruefer2_person_id) { + this.selectedPruefer2 = { + person_id: data.pruefer2_person_id, + label: data.pruefer2_label + }; + } + if (data.pruefer3_person_id) { + this.selectedPruefer3 = { + person_id: data.pruefer3_person_id, + label: data.pruefer3_label + }; + } + } + this.$refs.finalexamModal.show(); }, actionEditAbschlusspruefung(abschlusspruefung_id) { this.resetForm(); @@ -305,20 +356,23 @@ export default { }; if (data.p1_person_id) { this.selectedPruefer1 = { - label: this.getPersonLabel(data.p1_titelpre, data.p1_nachname, data.p1_vorname, data.p1_titelpost), - person_id: data.p1_person_id + label: this.getPersonLabel(data.p1_titelpre, data.p1_nachname, data.p1_vorname, data.p1_titelpost, data.p1_uid), + person_id: data.p1_person_id, + mitarbeiter_uid: data.p1_uid }; } if (data.p2_person_id) { this.selectedPruefer2 = { - label: this.getPersonLabel(data.p2_titelpre, data.p2_nachname, data.p2_vorname, data.p2_titelpost), - person_id: data.p2_person_id + label: this.getPersonLabel(data.p2_titelpre, data.p2_nachname, data.p2_vorname, data.p2_titelpost, data.p2_uid), + person_id: data.p2_person_id, + mitarbeiter_uid: data.p2_uid } }; if (data.p3_person_id) { this.selectedPruefer3= { - label: this.getPersonLabel(data.p3_titelpre, data.p3_nachname, data.p3_vorname, data.p3_titelpost), - person_id: data.p3_person_id + label: this.getPersonLabel(data.p3_titelpre, data.p3_nachname, data.p3_vorname, data.p3_titelpost, data.p3_uid), + person_id: data.p3_person_id, + mitarbeiter_uid: data.p3_uid }; } }); @@ -337,6 +391,30 @@ export default { .then(this.deleteAbschlusspruefung) .catch(this.$fhcAlert.handleSystemError); }, + saveOrUpdateLocalStorage(){ + let STORAGE_KEY = 'finalExamDefaultData'; + + const id = '20260625_01'; + const stored = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {}; + + stored[id] = { + pruefungstyp_kurzbz: this.formData.pruefungstyp_kurzbz, + pruefungsantritt_kurzbz: this.formData.pruefungsantritt_kurzbz, + vorsitz_uid: this.selectedVorsitz?.mitarbeiter_uid || null, + vorsitz_person_id: this.selectedVorsitz?.person_id || null, + vorsitz_label: this.selectedVorsitz?.label || null, + pruefer1_person_id: this.selectedPruefer1?.person_id || null, + pruefer1_label: this.selectedPruefer1?.label || null, + pruefer2_person_id: this.selectedPruefer2?.person_id || null, + pruefer2_label: this.selectedPruefer2?.label || null, + pruefer3_person_id: this.selectedPruefer3?.person_id || null, + pruefer3_label: this.selectedPruefer3?.label || null, + akadgrad_id: this.formData.akadgrad_id, + datum: this.formData.datum, + sponsion: this.formData.sponsion + }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(stored)); + }, addNewAbschlusspruefung() { const dataToSend = { uid: this.student.uid, @@ -347,6 +425,8 @@ export default { .call(ApiStvAbschlusspruefung.addNewAbschlusspruefung(dataToSend)) .then(response => { this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); + //save formData fields in LocalStorage + this.saveOrUpdateLocalStorage(); this.hideModal('finalexamModal'); this.resetForm(); }) @@ -355,6 +435,43 @@ export default { this.reload(); }); }, + async addNewAbschlusspruefungMulti(){ + try { + const arraySuccessfullySent = []; + const arrayError = []; + + for (const student of this.studentUids) { + try { + await this.$refs.formFinalExam.call( + ApiStvAbschlusspruefung.addNewAbschlusspruefung({ + uid: student, + formData: this.formData + }) + ); + arraySuccessfullySent.push(student); + } catch (error) { + arrayError.push(student); + } + } + if (arraySuccessfullySent.length) { + this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); + //TODO(Manu) check if really needed in case of success + //this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave') + ': Uids: ' + arraySuccessfullySent.join(", ")); + } + + if (arrayError.length) { + this.$fhcAlert.alertError(this.$p.t('ui', 'errorSavingData') + ': Uids: ' + arrayError.join(", ")); + } + + if (arraySuccessfullySent.length) { + this.saveOrUpdateLocalStorage(); + this.hideModal("finalexamModal"); + this.resetForm(); + } + } catch (error) { + this.$fhcAlert.handleSystemError(error); + } + }, hideModal(modalRef){ this.$refs[modalRef].hide(); }, @@ -376,6 +493,9 @@ export default { id: abschlusspruefung_id, formData: this.formData }; + //uncomment if also save data in local storage for update + //this.saveOrUpdateLocalStorage(); + return this.$refs.formFinalExam .call(ApiStvAbschlusspruefung.updateAbschlusspruefung(dataToSend)) .then(response => { @@ -417,7 +537,6 @@ export default { this.selectedPruefer1 = null; this.selectedPruefer2 = null; this.selectedPruefer3 = null; - }, setDefaultFormData() { @@ -434,7 +553,10 @@ export default { } if (!this.formData.akadgrad_id && this.arrAkadGrad.length > 0) { - this.formData.akadgrad_id = this.arrAkadGrad[0].akadgrad_id; + if(this.student.geschlecht == 'w') + this.formData.akadgrad_id = this.arrAkadGrad[1].akadgrad_id; + else + this.formData.akadgrad_id = this.arrAkadGrad[0].akadgrad_id; } }, printDocument(link) { @@ -471,29 +593,29 @@ export default { searchPerson(event) { if (this.abortController.persons) { this.abortController.persons.abort(); - } - + } this.abortController.persons = new AbortController(); return this.$api .call(ApiStvAbschlusspruefung.getPruefer(event.query)) .then(result => { - this.filteredPersons = []; - for (let person of result.data.retval) { - this.filteredPersons.push( - { - label: this.getPersonLabel( - person.titelpre, - person.nachname, - person.vorname, - person.titelpost, - person.person_uid - ), - person_id: person.person_id - } - ); - } - }); + this.filteredPersons = []; + for (let person of result.data.retval) { + this.filteredPersons.push( + { + label: this.getPersonLabel( + person.titelpre, + person.nachname, + person.vorname, + person.titelpost, + person.uid + ), + person_id: person.person_id, + mitarbeiter_uid: person.uid + } + ); + } + }); }, }, created() { @@ -526,7 +648,7 @@ export default { .catch(this.$fhcAlert.handleSystemError); this.$api - .call(ApiStvAbschlusspruefung.getAkadGrade(this.student.studiengang_kz)) + .call(ApiStvAbschlusspruefung.getAkadGrade(this.stg_kz)) .then(result => { this.arrAkadGrad = result.data; }) @@ -547,7 +669,8 @@ export default {

{{this.$p.t('stv','tab_finalexam')}}

-
+
+

{{$p.t('abschlusspruefung', 'abschluessPruefungAnlegen')}}

{{$p.t('abschlusspruefung', 'abschluessPruefungBearbeiten')}}

+
+ {{studentNames}} +
- + {{this.$p.t('global','details')}}

[{{$p.t('ui', 'neu')}}]

+
- +
+ + @@ -718,10 +866,11 @@ export default { :key="grad.akadgrad_id" :value="grad.akadgrad_id" > - {{grad.titel}} + {{ grad.titel }} ({{ grad.geschlecht }}) @@ -779,6 +928,7 @@ export default { >
-
+

{{$p.t('abschlusspruefung', 'zurBeurteilung')}}

@@ -807,8 +957,9 @@ export default { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Aufnahmetermine/Aufnahmetermine.js b/public/js/components/Stv/Studentenverwaltung/Details/Aufnahmetermine/Aufnahmetermine.js index 7f39496e9..a0e8c9465 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Aufnahmetermine/Aufnahmetermine.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Aufnahmetermine/Aufnahmetermine.js @@ -470,6 +470,7 @@ export default { :label="$p.t('admission', 'anmeldundRtAm')" type="DatePicker" v-model="formData.anmeldedatum" + model-type="yyyy-MM-dd" auto-apply :enable-time-picker="false" text-input diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Details.js b/public/js/components/Stv/Studentenverwaltung/Details/Details.js index 3cd314fc1..0b5eec44d 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Details.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Details.js @@ -158,6 +158,16 @@ export default { }, reload(){ this.updateStudent(this.modelValue); + }, + sendInfomail(){ + const subject = this.$p.t('person', 'betreffProfilfoto'); + const subjectEncoded = encodeURIComponent(subject); + + const body = this.$p.t('person', 'mailText_profilfoto'); + const bodyWithNewLines = body.replace(/\\n/g, '\n'); + const bodyEncoded = encodeURIComponent(bodyWithNewLines); + + window.location.href = "mailto:" + this.modelValue.mail_intern + "?subject=" + subjectEncoded + "&body=" + bodyEncoded; } }, created() { @@ -293,6 +303,7 @@ export default { :label="$p.t('person', 'geburtsdatum')" type="DatePicker" v-model="data.gebdatum" + model-type="yyyy-MM-dd" name="gebdatum" :clearable="false" no-today @@ -386,10 +397,12 @@ export default { container-class="col stv-details-details-foto" :label="$p.t('person', 'foto')" type="UploadImage" + titleActionButton="Infomail" v-model="data.foto" name="foto" + @actionbutton-clicked="sendInfomail" > - No Image + No Image ' : ''; - // if nachgereicht_am show datum + // if nachgereicht_am show datum nachgereicht_am oder hochgeladenamum let pill = dueDate - ? `${dateFormatted}` + ? `${date}` : ''; return `${tickCrossIcon} ${pill}`; @@ -206,7 +220,7 @@ export default { setHeader('anmerkung_intern', this.$p.t('global', 'anmerkung')); setHeader('akte_id', this.$p.t('global', 'akte_id')); setHeader('pflicht', this.$p.t('ampeln', 'mandatory')); - setHeader('nachgereicht_am', this.$p.t('global', 'dokument')); + setHeader('nachgereicht_am', this.$p.t('dokumente', 'nachreichungAm')); } }, { diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Dokumente/Modal/Edit.js b/public/js/components/Stv/Studentenverwaltung/Details/Dokumente/Modal/Edit.js index c96dcfd78..f536c016e 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Dokumente/Modal/Edit.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Dokumente/Modal/Edit.js @@ -103,6 +103,7 @@ export default { -
-
-
- +
+
+ + + +
+
+
+ +
+
+
+
+ +
- -
-
- + +
+ +
+
+ +
+
+
+ +
+
+ +
+
+

{{$p.t('stv', 'zuvieleEMails')}}

+ + +
+ +
+
-
` -}; \ No newline at end of file +
+ ` +}; diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Konto.js b/public/js/components/Stv/Studentenverwaltung/Details/Konto.js index 36e5bbbd3..5b6e1b6bb 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Konto.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Konto.js @@ -27,7 +27,7 @@ export default { return { filter: false, studiengang_kz: false, - counterdate: new Date() + counterdate: luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate() }; }, computed: { @@ -259,6 +259,7 @@ export default { - +
` }; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Lehrveranstaltungstermine/ListLehrveranstaltungstermine.js b/public/js/components/Stv/Studentenverwaltung/Details/Lehrveranstaltungstermine/ListLehrveranstaltungstermine.js index b664964f7..855adc26b 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Lehrveranstaltungstermine/ListLehrveranstaltungstermine.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Lehrveranstaltungstermine/ListLehrveranstaltungstermine.js @@ -2,8 +2,6 @@ import {CoreFilterCmpt} from "../../../../filter/Filter.js"; import FormInput from "../../../../Form/Input.js"; import FormForm from '../../../../Form/Form.js'; -import ApiStvCoursedates from "../../../../../api/factory/stv/coursedates.js"; - export default { name: "TblCourseList", components: { @@ -17,7 +15,14 @@ export default { }, }, props: { - student: Object + id: { + type: [Number, String], + required: true + }, + endpoint: { + type: Object, + required: true + }, }, data(){ return { @@ -48,14 +53,7 @@ export default { initTabulatorOptions(){ this.tabulatorOptions = { ajaxURL: 'dummy', - ajaxRequestFunc: () => this.$api.call( - ApiStvCoursedates.getCourselist({ - student_uid: this.student.uid, - start_date: this.dataSem.start, - end_date: this.dataSem.ende, - group_consecutiveHours: true, - dbStundenplanTable: this.dbStundenplanTable}) - ), + ajaxRequestFunc: () => this.$api.call(this.endpoint.getCourselist(this.id, this.dataSem.start, this.dataSem.ende, this.dbStundenplanTable)), ajaxResponse: (url, params, response) => { return response.data; }, @@ -131,7 +129,7 @@ export default { this.dataSem = this.listStudiensemester.find(item => item.studiensemester_kurzbz === studiensemester_kurzbz); }, exportToExcel(){ - window.open(this.downloadLink, '_blank'); + window.open(this.endpoint.exportCalendar(this.id, this.dbStundenplanTable), '_blank'); }, reload() { this.$refs.table.reloadTable(); @@ -145,10 +143,13 @@ export default { currentSemester(newVal, oldVal) { this.getDatesOfSemester(newVal); }, + id() { + this.reload(); + } }, created(){ this.$api - .call(ApiStvCoursedates.getStudiensemester()) + .call(this.endpoint.getStudiensemester()) .then(result => { this.listStudiensemester = result.data; this.getDatesOfSemester(this.currentSemester); diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Mobility/Mobility.js b/public/js/components/Stv/Studentenverwaltung/Details/Mobility/Mobility.js index e71c0dfb8..dd2899bf1 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Mobility/Mobility.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Mobility/Mobility.js @@ -467,6 +467,7 @@ export default { :label="$p.t('ui', 'von')" type="DatePicker" v-model="formData.von" + model-type="yyyy-MM-dd" auto-apply :enable-time-picker="false" text-input @@ -501,6 +502,7 @@ export default { :label="$p.t('global', 'bis')" type="DatePicker" v-model="formData.bis" + model-type="yyyy-MM-dd" auto-apply :enable-time-picker="false" text-input diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten.js index 71b8096ec..972326f26 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Noten.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten.js @@ -2,6 +2,9 @@ import NotenZeugnis from './Noten/Zeugnis.js'; import NotenTeacher from './Noten/Teacher.js'; import NotenRepeater from './Noten/Repeater.js'; +import ApiStvGrades from '../../../../api/factory/stv/grades.js'; +import { highlightGesamtnote } from "../../../../helpers/DocumentHelper.js"; + const LOCAL_STORAGE_ID = 'stv_details_noten_2024-11-25_stdsem_all'; export default { @@ -22,9 +25,17 @@ export default { }, data() { return { - stdsem: '' + stdsem: '', + endpoint: ApiStvGrades, + zeugnisLoaded: false, + teacherLoaded: false, }; }, + computed: { + gradeListLink() { + return this.$api.getUri('/person/gradelist/index/' + this.modelValue.uid); + } + }, methods: { reload() { this.$refs.zeugnis.$refs.table.reloadTable(); @@ -33,26 +44,58 @@ export default { }, saveStdsem(event) { window.localStorage.setItem(LOCAL_STORAGE_ID, event.target.value); + }, + onZeugnisLoaded() { + this.zeugnisLoaded = true; + this.checkHighlight(); + }, + onTeacherLoaded() { + this.teacherLoaded = true; + this.checkHighlight(); + }, + checkHighlight() + { + if (!this.zeugnisLoaded || !this.teacherLoaded) + return; + + if (!this.$refs.zeugnis || !this.$refs.teacher) + return; + + let zeugnisTable = this.$refs.zeugnis.$refs.table.tabulator; + let teacherTable = this.$refs.teacher.$refs.table.tabulator; + + if (!zeugnisTable || !teacherTable) + return; + + highlightGesamtnote(zeugnisTable, teacherTable); } }, created() { const savedPath = window.localStorage.getItem(LOCAL_STORAGE_ID); this.stdsem = savedPath || ''; }, - template: ` + template: /* html */`
-
- +
+
+ +
+
- +
- +
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Repeater.js b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Repeater.js index eaa34c1c7..ab7dedb3e 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Noten/Repeater.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Noten/Repeater.js @@ -21,6 +21,7 @@ export default { }, data() { return { + renderTabulator: false, tabulatorEvents: [] }; }, @@ -118,13 +119,13 @@ export default { created() { this.$p.loadCategory(['global', 'stv', 'lehre', 'profil']) .then(() => { - if (this.$refs.table.tableBuilt) - this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns); + this.renderTabulator = true }); }, template: `
this.$emit("loaded"), + }, + { + event: "tableBuilt", + handler: () => { + if(this.optionalTabulatorOptions?.visibleColumns + && this.setOptinalTabulatorOptionsVisibility) + { + for(let key in this.optionalTabulatorOptions.visibleColumns) { + if(this.optionalTabulatorOptions.visibleColumns[key]) + this.$refs.table.tabulator.showColumn(key); + else + this.$refs.table.tabulator.hideColumn(key); + } + } + } + } + ] }; }, computed: { tabulatorOptions() { return { ajaxURL: 'dummy', - ajaxRequestFunc: () => this.$api.call(ApiStvGrades.getTeacherProposal( - this.student.prestudent_id, + ajaxRequestFunc: () => this.$api.call(this.endpoint.getTeacherProposal( + this.id, (!this.allSemester ? this.currentSemester : null) )), ajaxResponse: (url, params, response) => { @@ -74,19 +103,24 @@ export default { { field: 'studiensemester_kurzbz', title: this.$p.t('lehre/studiensemester'), visible: false }, { field: 'note', title: this.$p.t('stv/grades_numericgrade'), visible: false }, { field: 'student_uid', title: this.$p.t('profil/studentIn'), visible: false }, + { field: 'vorname', title: this.$p.t('person/vorname'), visible: false }, + { field: 'nachname', title: this.$p.t('person/nachname'), visible: false }, { field: 'lehrveranstaltung_id', title: this.$p.t('lehre/lehrveranstaltung_id'), visible: false }, { field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false } ], + columnDefaults: { + headerFilter: this.optionalTabulatorOptions?.headerFilter ?? false, + }, layout: 'fitDataStretch', height: '100%', selectableRows: true, selectableRowsRangeMode: 'click', - persistenceID: 'stv-details-noten-teacher-20260217' + persistenceID: this.optionalTabulatorOptions?.persistenceTeacherID ?? 'stv-details-noten-teacher-2025120401', }; } }, watch: { - student(n) { + id() { this.$refs.table.reloadTable(); }, allSemester(n) { @@ -97,7 +131,7 @@ export default { copyGrades(selected) { const promises = selected.map( grade => this.$api - .call(ApiStvGrades.copyTeacherProposalToCertificate(grade), { + .call(this.endpoint.copyTeacherProposalToCertificate(grade), { errorHeader: grade.lehrveranstaltung_bezeichnung }) .then(() => { @@ -117,13 +151,19 @@ export default { created() { this.$p.loadCategory(['stv', 'lehre', 'profil']) .then(() => { - if (this.$refs.table.tableBuilt) - this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns); + this.renderTabulator = true; }); + + if(this?.optionalTabulatorOptions) + { + const localStorageKey = 'tabulator-' + this.optionalTabulatorOptions.persistenceTeacherID + '-columns'; + this.setOptinalTabulatorOptionsVisibility = (window.localStorage.getItem(localStorageKey) === null) ? true : false; + } }, template: `
this.$emit("loaded") + }, { event: "rowSelected", handler: row => row.getElement().style.zIndex = 12 @@ -42,6 +55,21 @@ export default { { event: "rowDeselected", handler: row => row.getElement().style.zIndex = '' + }, + { + event: "tableBuilt", + handler: () => { + if(this.optionalTabulatorOptions?.visibleColumns + && this.setOptinalTabulatorOptionsVisibility) + { + for(let key in this.optionalTabulatorOptions.visibleColumns) { + if(this.optionalTabulatorOptions.visibleColumns[key]) + this.$refs.table.tabulator.showColumn(key); + else + this.$refs.table.tabulator.hideColumn(key); + } + } + } } ], stdsem: '', @@ -53,7 +81,7 @@ export default { computed: { tabulatorOptions() { const listPromise = this.$api - .call(ApiStvGrades.list()) + .call(this.endpoint.list()) .then(res => res.data.map(({bezeichnung: label, note: value}) => ({label, value}))); let gradeField = { @@ -86,7 +114,7 @@ export default { note_bezeichnung })) // send to backend - .then(data => this.$api.call(ApiStvGrades.updateCertificate(data))) + .then(data => this.$api.call(this.endpoint.updateCertificate(data))) // get bezeichnung again .then(() => listPromise) .then(list => list.find(el => el.value == note)) @@ -109,7 +137,7 @@ export default { if (filterTerm) { return this.$api .call( - ApiStvGrades.getGradeFromPoints( + this.endpoint.getGradeFromPoints( filterTerm, cell.getData().lehrveranstaltung_id, this.currentSemester @@ -142,6 +170,8 @@ export default { .then(() => node.innerText = this.$p.t('ui/loading')) .catch(this.$fhcAlert.handleSystemError); gradeField.editorParams.placeholderLoading = node; + gradeField.visible = this.optionalTabulatorOptions?.visibleColumns?.note ?? true + gradeField.headerFiler = this.optionalTabulatorOptions?.headerFilter?.note || this.optionalTabulatorOptions?.note || false } const columns = [ @@ -192,7 +222,10 @@ export default { { field: 'lehrform', title: this.$p.t('lehre/lehrform'), visible: false }, { field: 'kurzbz', title: this.$p.t('lehre/kurzbz'), visible: false }, { field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false }, - { field: 'lehrveranstaltung_bezeichnung_english', title: this.$p.t('stv/grades_lehrveranstaltung_bezeichnung_english'), visible: false } + { field: 'lehrveranstaltung_bezeichnung_english', title: this.$p.t('stv/grades_lehrveranstaltung_bezeichnung_english'), visible: false }, + { field: 'uid', title: this.$p.t('person/uid'), visible: false }, + { field: 'vorname', title: this.$p.t('person/vorname'), visible: false }, + { field: 'nachname', title: this.$p.t('person/nachname'), visible: false } ]; const hasDocuments = ['both', 'inline'].includes(this.config.documents); @@ -202,6 +235,7 @@ export default { columns.push({ field: 'actions', title: this.$p.t('global/actions'), + minWidth: 120, cssClass: "overflow-visible", headerSort: false, formatter: cell => { @@ -238,8 +272,8 @@ export default { return { ajaxURL: 'dummy', - ajaxRequestFunc: () => this.$api.call(ApiStvGrades.getCertificate( - this.student.prestudent_id, + ajaxRequestFunc: () => this.$api.call(this.endpoint.getCertificate( + this.id, (!this.allSemester ? this.currentSemester : null) )), ajaxResponse: (url, params, response) => { @@ -248,9 +282,10 @@ export default { columns, height: '100%', layout: 'fitDataStretchFrozen', - selectableRows: 1, + selectable: true, selectableRowsRangeMode: 'click', - persistenceID: 'stv-details-noten-zeugnis-20260217', + selectableRows: true, + persistenceID: this.optionalTabulatorOptions?.persistenceZeugnisID ?? 'stv-details-noten-zeugnis-20260217', persistence:{ columns: ["width", "visible", "frozen"] } @@ -258,7 +293,8 @@ export default { } }, watch: { - student(n) { + id() + { this.$refs.table.reloadTable(); }, allSemester(n) { @@ -269,7 +305,7 @@ export default { setGrade(data) { this.$api .call( - ApiStvGrades.updateCertificate(data), + this.endpoint.updateCertificate(data), { errorHeader: data.lehrveranstaltung_bezeichnung } ) .then(this.$refs.table.reloadTable) @@ -282,7 +318,7 @@ export default { .confirmDelete() .then(result => result ? data : Promise.reject({handled:true})) .then(data => this.$api.call( - ApiStvGrades.deleteCertificate(data), + this.endpoint.deleteCertificate(data), { errorHeader: data.lehrveranstaltung_bezeichnung } )) .then(this.$refs.table.reloadTable) @@ -291,15 +327,21 @@ export default { } }, created() { - this.$p.loadCategory(['global', 'stv', 'lehre']) + this.$p.loadCategory(['global', 'stv', 'lehre', 'person']) .then(() => { - if (this.$refs.table.tableBuilt) - this.$refs.table.tabulator.columnManager.setColumns(this.tabulatorOptions.columns); + this.renderTabulator = true; }); + + if(this?.optionalTabulatorOptions) + { + const localStorageKey = 'tabulator-' + this.optionalTabulatorOptions.persistenceZeugnisID + '-columns'; + this.setOptinalTabulatorOptionsVisibility = (window.localStorage.getItem(localStorageKey) === null) ? true : false; + } }, template: `
@@ -49,16 +54,16 @@ ref="formc" type-id: id to which table the notizdata should be connected... eg. person_id, prestudent_id, uid (for mitarbeiter_uid), projekt_kurzbz, projektphase_id, projekttask_id, bestellung_id, lehreinheit_id, anrechnung_id -notizLayout: "classicFas", "twoColumnsFormLeft", twoColumnsFormRight, popupModal" +notizLayout: "classicFas", "twoColumnsFormLeft", twoColumnsFormRight, popupModal"; is now configurable in config/stv.php showErweitert: if true: section with following fields will be displayed: - 'verfasser', 'bearbeiter', 'von', 'bis' + 'verfasser', 'bearbeiter', 'von', 'bis' and 'erledigt'; is now configurable in config/stv.php showDocument: if true: section with documentHandling will be displayed showTinyMCE: if true: section with WYSIWYG Editor for Text will be displayed -visibleColumns: list, which fields shoult be showed as default in filter component +visibleColumns: list, which fields should be showed as default in filter component fullVersion: :visibleColumns="['titel','text','bearbeiter','verfasser','von','bis','dokumente','erledigt','notiz_id','notizzuordnung_id','id','lastupdate']" diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Prestudent.js b/public/js/components/Stv/Studentenverwaltung/Details/Prestudent.js index 5f70e4951..1d8d5ac26 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Prestudent.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Prestudent.js @@ -325,6 +325,7 @@ export default { :label="$p.t('infocenter', 'zgvDatum')" type="DatePicker" v-model="data.zgvdatum" + model-type="yyyy-MM-dd" name="zgvdatum" no-today auto-apply @@ -389,6 +390,7 @@ export default { :label="$p.t('lehre', 'zgvMasterDatum')" type="DatePicker" v-model="data.zgvmadatum" + model-type="yyyy-MM-dd" name="zgvmadatum" no-today auto-apply @@ -454,6 +456,7 @@ export default { :label="$p.t('lehre', 'zgvDoktorDatum')" type="DatePicker" v-model="data.zgvdoktordatum" + model-type="yyyy-MM-dd" name="zgvdoktordatum" no-today auto-apply diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Status/Modal.js b/public/js/components/Stv/Studentenverwaltung/Details/Status/Modal.js index 7507b0ebd..ce190fdb5 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Status/Modal.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Status/Modal.js @@ -276,6 +276,7 @@ export default{ container-class="mb-3" type="DatePicker" v-model="formData.datum" + model-type="yyyy-MM-dd" name="datum" :label="$p.t('global/datum')" auto-apply @@ -291,6 +292,7 @@ export default{ container-class="mb-3" type="DatePicker" v-model="formData.bestaetigtam" + model-type="yyyy-MM-dd" name="bestaetigtam" :label="$p.t('lehre/bestaetigt_am')" auto-apply @@ -306,13 +308,14 @@ export default{ container-class="mb-3" type="DatePicker" v-model="formData.bewerbung_abgeschicktamum" + model-type="yyyy-MM-dd HH:mm:ss" name="bewerbung_abgeschicktamum" :label="$p.t('lehre/bewerbung_abgeschickt_am')" auto-apply - :enable-time-picker="false" - format="dd.MM.yyyy" + :enable-time-picker="true" + format="dd.MM.yyyy HH:mm:ss" text-input - preview-format="dd.MM.yyyy" + preview-format="dd.MM.yyyy HH:mm:ss" :teleport="true" :disabled="bisLocked || !hasPrestudentstatusPermission" > diff --git a/public/js/components/Stv/Studentenverwaltung/List.js b/public/js/components/Stv/Studentenverwaltung/List.js index 1aa2c6b70..b5973dbb0 100644 --- a/public/js/components/Stv/Studentenverwaltung/List.js +++ b/public/js/components/Stv/Studentenverwaltung/List.js @@ -1,9 +1,17 @@ import {CoreFilterCmpt} from "../../filter/Filter.js"; import ListNew from './List/New.js'; import CoreTag from '../../Tag/Tag.js'; -import { tagHeaderFilter } from "../../../tabulator/filters/extendedHeaderFilter.js"; +import { + buildTagHeaderFilterExpression, + customTagFilter, + setTagHeaderFilterValue, + syncSelectedTagOptionsWithHeaderFilters, + syncTagHeaderFilterOptions, + tagHeaderFilter, +} from "../../../tabulator/filters/extendedHeaderFilter.js"; import { addTagInTable, deleteTagInTable, updateTagInTable } from "../../../../js/helpers/TagHelper.js"; import { tagFormatter } from "../../../../js/tabulator/formatter/tags.js"; +import ModalLoading from "./List/ModalLoading.js"; import ApiTag from "../../../api/factory/stv/tag.js"; import ListFilter from './List/Filter.js'; @@ -12,658 +20,883 @@ import { capitalize } from '../../../helpers/StringHelpers.js'; import draggable from '../../../directives/draggable.js'; +import StvColumns from '../../../../../index.ci.php/js/tabulatorcolumns/stv'; + +import ModuleFilterFilters from '../../../tabulator/filter/filters.js'; +Tabulator.extendModule("filter", "filters", ModuleFilterFilters); + export default { - name: "ListPrestudents", - components: { - CoreFilterCmpt, - ListNew, - CoreTag, - ListFilter - }, - directives: { - draggable - }, - inject: { - lists: { - from: 'lists', - required: true - }, - $reloadList: { - from: '$reloadList', - required: true - }, - currentSemester: { - from: 'currentSemester', - required: true - }, - tagsEnabled: { - from: 'configStvTagsEnabled', - default: false - }, - }, - props: { - selected: Array, - studiengangKz: Number, - studiensemesterKurzbz: String - }, - emits: [ - 'update:selected', - 'filterActive' - ], - data() { - function dateFormatter(cell) - { - let val = cell.getValue(); - if (!val) - return ' '; - let date = new Date(val); - return date.toLocaleDateString('de-AT', { - "day": "2-digit", - "month": "2-digit", - "year": "numeric" - }); - } + name: "ListPrestudents", + components: { + CoreFilterCmpt, + ListNew, + CoreTag, + ListFilter, + ModalLoading + }, + directives: { + draggable + }, + inject: { + lists: { + from: 'lists', + required: true + }, + $reloadList: { + from: '$reloadList', + required: true + }, + currentSemester: { + from: 'currentSemester', + required: true + }, + tagsEnabled: { + from: 'configStvTagsEnabled', + default: false + }, + }, + props: { + selected: Array, + studiengangKz: Number, + studiensemesterKurzbz: String + }, + emits: [ + 'update:selected', + 'filterActive' + ], + data() { + return { + tagFilterState: { + initialOptions: [], + selectedOptions: [], + }, + tagFilterLabels: { + tag: "Tag", + clear: "Clear", + connectors: { + AND: "AND", + OR: "OR", + NOT: "NOT", + }, + }, + selectedColumnValues: [], + tabulatorOptions: { + columns: StvColumns, + locale: true, + rowFormatter(row) { + if (row.getData().bnaktiv === false) { + row.getElement().classList.add('text-black','text-opacity-50','fst-italic'); + } + row.getElement().draggable = true + }, - return { - tabulatorOptions: { - columns:[ - {title:"UID", field:"uid", headerFilter: true}, - {title:"TitelPre", field:"titelpre", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"Nachname", field:"nachname", headerFilter: true}, - {title:"Vorname", field:"vorname", headerFilter: true}, - {title:"Wahlname", field:"wahlname", visible:false, headerFilter: true}, - {title:"Vornamen", field:"vornamen", visible:false, headerFilter: true}, - {title:"TitelPost", field:"titelpost", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"Ersatzkennzeichen", field:"ersatzkennzeichen", headerFilter: true}, - { - title: "Geburtsdatum", - field: "gebdatum", - formatter: dateFormatter, - headerFilter: true, - headerFilterFunc(headerValue, rowValue) { - const matches = headerValue.match(/^(([0-9]{2})\.)?([0-9]{2})\.([0-9]{4})?$/); - let comparestr = headerValue; - if(matches !== null) { - const year = (matches[4] !== undefined) ? matches[4] : ''; - const month = matches[3]; - const day = (matches[2] !== undefined) ? matches[2] : ''; - comparestr = year + '-' + month + '-' + day; - } - return rowValue.match(comparestr); - } - }, - {title:"Geschlecht", field:"geschlecht", headerFilter: "list", headerFilterParams: {values:{'m':'männlich','w':'weiblich','x':'divers','u':'unbekannt'}, listOnEmpty:true, autocomplete:true}}, - {title:"Sem.", field:"semester", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"Verb.", field:"verband", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"Grp.", field:"gruppe", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"Studiengang", field:"studiengang", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"Studiengang_kz", field:"studiengang_kz", visible:false, headerFilter: true}, - {title:"Personenkennzeichen", field:"matrikelnr", headerFilter: true}, - {title:"PersonID", field:"person_id", headerFilter: true}, - {title:"Status", field:"status", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"Status Datum", field:"status_datum", visible:false, formatter:dateFormatter}, - {title:"Status Bestaetigung", field:"status_bestaetigung", visible:false, formatter:dateFormatter, headerFilter: true}, - {title:"EMail (Privat)", field:"mail_privat", visible:false, headerFilter: true}, - {title:"EMail (Intern)", field:"mail_intern", visible:false, headerFilter: true}, - {title:"Anmerkungen", field:"anmerkungen", visible:false, headerFilter: true}, - {title:"AnmerkungPre", field:"anmerkung", visible:false, headerFilter: true}, - {title:"OrgForm", field:"orgform_kurzbz", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"Aufmerksamdurch", field:"aufmerksamdurch_kurzbz", visible:false}, - {title:"Gesamtpunkte", field:"punkte", visible:false}, - {title:"Aufnahmegruppe", field:"aufnahmegruppe_kurzbz", visible:false}, - {title:"Dual", field:"dual", visible:false, - formatter:'tickCross', formatterParams: { - tickElement: '', - crossElement: '' - }, - headerFilter:"tickCross", headerFilterParams: { - "tristate":true, elementAttributes:{"value":"true"} - }, headerFilterEmptyCheck:function(value){return value === null} - }, - {title:"Matrikelnummer", field:"matr_nr", visible:false, headerFilter: true}, - {title:"Studienplan", field:"studienplan_bezeichnung", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}}, - {title:"PreStudentInnenID", field:"prestudent_id", headerFilter: true}, - {title:"Priorität", field:"priorisierung_relativ"}, - {title:"Mentor", field:"mentor", visible:false}, - {title:"Aktiv", field:"bnaktiv", visible:false, - formatter:'tickCross', formatterParams: { - allowEmpty:true, - tickElement: '', - crossElement: '' - }, - headerFilter:"tickCross", headerFilterParams: { - "tristate":true, elementAttributes:{"value":"true"} - }, headerFilterEmptyCheck:function(value){return value === null} - }, - {title:"Unruly", field:"unruly", visible:false}, + ajaxRequestFunc: (url, config, params) => { + if ( url === '' ) { + return Promise.resolve({ data: []}); + } + /** + * NOTE(chris): Because of a bug in Tabulator + * we need to get the params from elsewhere. + * @see https://github.com/olifolkerd/tabulator/issues/4318 + */ + const apiconfig = { + ...this.tabulatorOptions.ajaxConfig, + url: this.tabulatorOptions.ajaxURL, + params: this.tabulatorOptions.ajaxParams + }; + return this.$api.call(apiconfig); + }, + ajaxResponse: (url, params, response) => { + return response?.data; + }, + + layout: 'fitDataStretch', + layoutColumnsOnNewData: false, + height: '100%', + selectableRows: true, + selectableRowsRangeMode: 'click', + index: 'prestudent_id', + persistence: { + sort: true, + columns: ["width", "visible"], + filter: false, + headerFilter: false, + group: false, + page: false, + }, + persistenceID: 'stv-list-20260223_01' + }, + tabulatorEvents: [ + { + event: 'rowSelectionChanged', + handler: this.rowSelectionChanged + }, + { + event: 'dataLoading', + handler: this.handleDataLoading + }, + { + event: 'renderComplete', + handler: this.handleRenderComplete + }, + { + event: 'dataProcessed', + handler: (data) => { + this.getAllRows() + this.autoSelectRows(data) + } + }, + { + event: 'dataLoaded', + handler: data => { + if (Array.isArray(data)) { + this.count = data.length; + this.allPrestudents = data.map(item => item.prestudent_id); + } else { + this.count = 0; + this.allPrestudents = []; + } + syncTagHeaderFilterOptions( + Array.isArray(data) ? data : [], + this.tagFilterState.initialOptions, + this.tagFilterState.selectedOptions, + ); + } + }, + { + event: 'dataFiltered', + handler: (filters, rows) => { + this.filteredcount = rows.length; + syncSelectedTagOptionsWithHeaderFilters( + filters, + this.tagFilterState.selectedOptions, + this.tagsEnabled, + ); + }, + }, + { + event: 'rowClick', + handler: this.handleRowClick // TODO(chris): this should be in the filter component + }, + { + event: 'dataTreeRowExpanded', + handler: (data) => { + this.getExpandedRows() + } + }, + { + event: 'dataTreeRowCollapsed', + handler: (data) => { + this.getExpandedRows() + } + }, + { + event: 'rowMouseDown', + handler: this.handleMouseDown + }, + { + event: "columnWidth", + handler: (column) => { + if (column.getField() !== "tags") return; + + column.getCells().forEach((cell) => { + cell.getElement().firstElementChild?.fitTags?.(); + }); + }, + }, + ], + focusObj: null, // TODO(chris): this should be in the filter component + lastSelected: null, + filter: [], + count: 0, + filteredcount: 0, + selectedcount: 0, + //tags + expanded: [], + tagEndpoint: ApiTag, + currentEndpoint: null, + headerFilterActive: false, + dragSource: [], + oldScrollUrl: '', + oldScrollLeft: 0, + oldScrollTop: 0, + allPrestudents: [], + rebuildData: [], + isLoading: false, + progress: -1, + total: -1, + processed: -1 + } + }, + computed: { + generalPresets: function() { + return [ + { + "id": null, + "name": "Standard", + "displayedColumns": [ + "uid","titelpre","tags","nachname","vorname","titelpost", + "ersatzkennzeichen","gebdatum","geschlecht", + "semester_berechnet","verband","gruppe","studiengang", + "matrikelnr","person_id","status","orgform_kurzbz", + "studienplan_bezeichnung","prestudent_id","priorisierung_relativ" ], - rowFormatter(row) { - if (row.getData().bnaktiv === false) { - row.getElement().classList.add('text-black','text-opacity-50','fst-italic'); - } - row.getElement().draggable = true - }, - - ajaxRequestFunc: (url, config, params) => { - if( url === '' ) - { - return Promise.resolve({ data: []}); - } - /** - * NOTE(chris): Because of a bug in Tabulator - * we need to get the params from elsewhere. - * @see https://github.com/olifolkerd/tabulator/issues/4318 - */ - const apiconfig = { - ...this.tabulatorOptions.ajaxConfig, - url: this.tabulatorOptions.ajaxURL, - params: this.tabulatorOptions.ajaxParams - }; - return this.$api.call(apiconfig); - }, - ajaxResponse: (url, params, response) => { - return response?.data; - }, - - layout: 'fitDataStretch', - layoutColumnsOnNewData: false, - height: '100%', - selectableRows: true, - selectableRowsRangeMode: 'click', - index: 'prestudent_id', - persistenceID: 'stv-list', - }, - tabulatorEvents: [ - { - event: 'rowSelectionChanged', - handler: this.rowSelectionChanged - }, - { - event: 'dataLoading', - handler: this.handleDataLoading - }, - { - event: 'renderComplete', - handler: this.handleRenderComplete - }, - { - event: 'dataProcessed', - handler: (data) => { - this.getAllRows() - this.autoSelectRows(data) - } - }, - { - event: 'dataLoaded', - handler: data => this.count = data.length - }, - { - event: 'dataFiltered', - handler: (filters, rows) => this.filteredcount = rows.length - }, - { - event: 'rowClick', - handler: this.handleRowClick // TODO(chris): this should be in the filter component - }, - { - event: 'dataTreeRowExpanded', - handler: (data) => { - this.getExpandedRows() - } - }, - { - event: 'dataTreeRowCollapsed', - handler: (data) => { - this.getExpandedRows() - } - }, - { - event: 'rowMouseDown', - handler: this.handleMouseDown - } - ], - focusObj: null, // TODO(chris): this should be in the filter component - lastSelected: null, - filter: [], - count: 0, - filteredcount: 0, - selectedcount: 0, - //tags - expanded: [], - selectedColumnValues: [], - tagEndpoint: ApiTag, - currentEndpoint: null, - headerFilterActive: false, - dragSource: [], - oldScrollUrl: '', - oldScrollLeft: 0, - oldScrollTop: 0 - } + "headerFilters": [], + "sort": null + } + ]; }, - computed: { - countsToHTML: function() { - return this.$p.t('global/ausgewaehlt') - + ': ' + (this.selectedcount || 0) + '' - + ' | ' - + this.$p.t('global/gefiltert') - + ': ' - + '' + (this.filteredcount || 0) + '' - + ' | ' - + this.$p.t('global/gesamt') - + ': ' + (this.count || 0) + ''; - }, - selectedDragObject() { - let items = this.dragSource?.length ? this.dragSource : this.selected; + countsToHTML: function() { + return this.$p.t('global/ausgewaehlt') + + ': ' + (this.selectedcount || 0) + '' + + ' | ' + + this.$p.t('global/gefiltert') + + ': ' + + '' + (this.filteredcount || 0) + '' + + ' | ' + + this.$p.t('global/gesamt') + + ': ' + (this.count || 0) + ''; + }, + selectedDragObject() { + let items = this.dragSource?.length ? this.dragSource : this.selected; - return items.map(item => { - let type, id; - if (item.uid) { - type = 'student'; - id = item.uid; - } else if (item.prestudent_id) { - type = 'prestudent'; - id = item.prestudent_id; - } else if (item.person_id) { - type = 'person'; - id = item.person_id; - } - return { - ...item, - type, - id - }; - }); - }, - downloadConfig() { - return { - csv: { - formatter: 'csv', - file: this.fileString, - options: { - delimiter: ';', - bom: true, - } - } + return items.map(item => { + let type, id; + if (item.uid) { + type = 'student'; + id = item.uid; + } else if (item.prestudent_id) { + type = 'prestudent'; + id = item.prestudent_id; + } else if (item.person_id) { + type = 'person'; + id = item.person_id; + } + return { + ...item, + type, + id + }; + }); + }, + downloadConfig() { + return { + csv: { + formatter: 'csv', + file: this.fileString, + options: { + delimiter: ';', + bom: true, + }, + rowRange: this.selectedcount > 0 ? "selected" : "all", + }, + }; + }, + fileString() { + let today = new Date().toLocaleDateString('en-GB') + .replace(/\//g, '_'); + return "StudentList_" + today + ".csv"; + }, + selectedPrestudents() { + if (this.selected && this.selected.length > 0) { + return this.selected.map(item => item.prestudent_id); + } else { + // fallback whole list of prestudents + return this.allPrestudents || []; + } + }, + + linkXLS(){ + return FHC_JS_DATA_STORAGE_OBJECT.app_root + + 'content/statistik/studentenexportextended.xls.php?' + + '&studiensemester_kurzbz=' + this.currentSemester + + '&data=' + this.selectedPrestudents.join(";"); + }, + }, + watch: { + "$p.user_language.value"(n, o) { + if (n !== o && o !== undefined && this.$refs.table.tableBuilt) { + this.translateTabulator(); + } + }, + "tagFilterState.selectedOptions": { + handler() { + const selectedOptions = this.tagFilterState.selectedOptions; + const combinedFilterStatement = + buildTagHeaderFilterExpression(selectedOptions); + + setTagHeaderFilterValue( + combinedFilterStatement, + this.$refs.table.tabulator, + ); + }, + deep: true, + }, + }, + methods: { + translateTabulator() { + this.$p + .loadCategory([ + "global", + "person", + "lehre", + "ui", + "profilUpdate", + "admission", + "stv", + ]) + .then(() => { + this.updateTagFilterLabels(); + + const translations = { + uid: capitalize(this.$p.t("person/uid")), + titelpre: capitalize(this.$p.t("person/titelpre")), + nachname: capitalize(this.$p.t("person/nachname")), + vorname: capitalize(this.$p.t("person/vorname")), + wahlname: capitalize(this.$p.t("person/wahlname")), + vornamen: capitalize(this.$p.t("person/vornamen")), + titelpost: capitalize(this.$p.t("person/titelpost")), + ersatzkennzeichen: capitalize( + this.$p.t("person/ersatzkennzeichen"), + ), + gebdatum: capitalize(this.$p.t("person/geburtsdatum")), + geschlecht: capitalize(this.$p.t("person/geschlecht")), + semester_berechnet: capitalize(this.$p.t("lehre/sem")), + verband: capitalize(this.$p.t("lehre/verb")), + gruppe: capitalize(this.$p.t("lehre/grp")), + studiengang: capitalize(this.$p.t("lehre/studiengang")), + studiengang_kz: capitalize(this.$p.t("lehre/studiengang_kz")), + matrikelnr: capitalize(this.$p.t("person/personenkennzeichen")), + person_id: capitalize(this.$p.t("person/person_id")), + status: capitalize(this.$p.t("global/status")), + status_datum: capitalize(this.$p.t("profilUpdate/statusDate")), + status_bestaetigung: capitalize( + this.$p.t("global/status_bestaetigung"), + ), + mail_privat: capitalize(this.$p.t("person/email_private")), + mail_intern: capitalize(this.$p.t("person/email_intern")), + anmerkungen: capitalize(this.$p.t("stv/notes_person")), + anmerkung: capitalize(this.$p.t("stv/notes_prestudent")), + orgform_kurzbz: capitalize(this.$p.t("lehre/orgform")), + aufmerksamdurch_kurzbz: capitalize( + this.$p.t("person/aufmerksamDurch"), + ), + punkte: capitalize(this.$p.t("admission/gesamtpunkte")), + aufnahmegruppe_kurzbz: capitalize( + this.$p.t("stv/aufnahmegruppe_kurzbz"), + ), + dual: capitalize(this.$p.t("lehre/dual_short")), + matr_nr: capitalize(this.$p.t("person/matrikelnummer")), + studienplan_bezeichnung: capitalize(this.$p.t("lehre/studienplan")), + prestudent_id: capitalize(this.$p.t("ui/prestudent_id")), + priorisierung_relativ: capitalize(this.$p.t("lehre/prioritaet")), + mentor: capitalize(this.$p.t("stv/mentor")), + bnaktiv: capitalize(this.$p.t("person/aktiv")), + }; + + /** NOTE(chris): + * use this approach because updateDefinition + * on the Tabulator columns is way slower and + * freezes up the GUI. + */ + // Overwrite definition for column show/hide + this.$refs.table.tabulator.getColumns().forEach((col) => { + const trans = translations[col.getField()]; + if (!trans) return; + col.getDefinition().title = trans; + }); + // Overwrite node in dom + this.$refs.table.tabulator.element + .querySelectorAll(".tabulator-col[tabulator-field]") + .forEach((el) => { + const field = el.getAttribute("tabulator-field"); + if (!translations[field]) return; + + const title = el.querySelector(".tabulator-col-title"); + if (!title) return; + + title.innerText = translations[field]; + }); + }); + }, + reload() { + this.$refs.table.reloadTable(); + }, + actionNewPrestudent() { + this.$refs.new.open(); + }, + rowSelectionChanged(data, rows, selected, deselected) { + this.selectedcount = data.length; + + //in case of empty selection (eg. in future or past semester of selected student without sem) + if (selected.length == 0 ) { + this.lastSelected = this.selected; + } + + if (selected.length > 0 || deselected.length > 0) { + this.lastSelected = this.selected; + + //for tags + this.selectedRows = this.$refs.table.tabulator.getSelectedRows(); + this.selectedColumnValues = this.selectedRows.filter( + row => row.getData().prestudent_id !== undefined + && row.getData().prestudent_id + ).map( + row => row.getData().prestudent_id + ); + + this.$emit('update:selected', data); + } + }, + autoSelectRows(data) { + if (Array.isArray(this.lastSelected) && this.lastSelected.length){ + // NOTE(chris): reselect rows on refresh + let selected = this.lastSelected.map(el => this.$refs.table.tabulator.getRow(el.prestudent_id)) + // TODO(chris): unselect current item if it's no longer in the table? + // or maybe reselect only the last one? + selected = selected.filter(el => el); + + if (selected.length) + this.$refs.table.tabulator.selectRow(selected); + } else if(data && this.lastSelected === undefined) { + // NOTE(chris): select row if it's the only one (preferably only on startup) + if (data.length == 1) { + this.$refs.table.tabulator.selectRow(this.$refs.table.tabulator.getRows()); + } + } + }, + updateFilter(filter) { + this.filter = filter; + this.$emit('filterActive', filter); + this.updateUrl(); + }, + updateTagFilterLabels() { + this.tagFilterLabels.clear = this.$p.t("ui/filterdelete"); + this.tagFilterLabels.tag = "Tag"; + this.tagFilterLabels.connectors.AND = this.$p + .t("ui/andCondition") + .toUpperCase(); + this.tagFilterLabels.connectors.OR = this.$p + .t("ui/orCondition") + .toUpperCase(); + this.tagFilterLabels.connectors.NOT = this.$p + .t("ui/notCondition") + .toUpperCase(); + }, + updateUrl(endpoint, first) { + this.lastSelected = first ? undefined : this.selected; + + if (endpoint === undefined && this.currentEndpoint === null) { + endpoint = { url: '' }; + } else if (endpoint === undefined) { + endpoint = JSON.parse(JSON.stringify(this.currentEndpoint)); + } else { + this.currentEndpoint = JSON.parse(JSON.stringify(endpoint)); + } + + endpoint.url = endpoint.url.replace( + 'CURRENT_SEMESTER', + encodeURIComponent(this.currentSemester) + ); + + const params = (endpoint?.params !== undefined) ? endpoint.params : {}; + let method = (endpoint?.method !== undefined) ? endpoint.method : 'get'; + if (this.filter.length && !endpoint.url.match(/\/search\//)) { + params.filter = this.filter; + method = 'post'; + } + + this.tabulatorOptions.ajaxURL = endpoint.url; + this.tabulatorOptions.ajaxParams = { ...params }; + this.tabulatorOptions.ajaxConfig = { method }; + + if (!this.$refs.table.tableBuilt) { + if (this.$refs.table.tabulator) { + this.$refs.table.tabulator.on("tableBuilt", () => { + this.$refs.table.tabulator.setData(endpoint.url, params, method); + }); + } + } else + this.$refs.table.tabulator.setData(endpoint.url, params, method); + }, + dragCleanup(evt) { + this.dragSource = []; + if (evt.dataTransfer.dropEffect == 'none') + return; // aborted or wrong target + + this.$reloadList(); + }, + onKeydown(e) { // TODO(chris): this should be in the filter component + + if ((e.ctrlKey || e.metaKey) && e.code === "KeyA") { + e.preventDefault(); + + this.$refs.table.tabulator.deselectRow(); + this.$refs.table.tabulator.selectRow('active'); + } + + if (!this.focusObj) + return; + + // Ignore typing inside editable elements + if ( + e.target instanceof HTMLInputElement || + e.target instanceof HTMLTextAreaElement || + e.target.isContentEditable + ) + return; + + var next; + switch (e.code) { + case 'Enter': + case 'Space': + e.preventDefault(); + var e2 = new Event('click', e); + e2.altKey = e.altKey; + e2.ctrlKey = e.ctrlKey; + e2.shiftKey = e.shiftKey; + this.focusObj.dispatchEvent(e2); + //row.component.toggleSelect(); + break; + case 'ArrowUp': + e.preventDefault(); + next = this.focusObj.previousElementSibling; + if (next) + this.changeFocus(this.focusObj, next); + break; + case 'ArrowDown': + e.preventDefault(); + next = this.focusObj.nextElementSibling; + if (next) + this.changeFocus(this.focusObj, next); + break; + } + }, + changeFocus(a, b) { // TODO(chris): this should be in the filter component + if (b) { + b.tabIndex = 0; + this.focusObj = b; + b.focus(); + } else { + this.focusObj = null; + } + a.tabIndex = -1; + return this.focusObj; + }, + onFocus(e) { // TODO(chris): this should be in the filter component + if (!this.focusObj) { + var container, target; + if (e.target.classList.contains('tabulator-container')) { + container = e.target; + target = container.querySelector('.tabulator-row'); + } else if (e.target.classList.contains('tabulator-row')) { + container = e.target.closest('.tabulator-container'); + target = e.target; + } + if (container && target) { + this.changeFocus(container, target); + } + } + }, + handleRowClick(e, row) { // TODO(chris): this should be in the filter component + if (this.focusObj) { + let el = row.getElement(); + if (el != this.focusObj) + this.changeFocus(this.focusObj, el); + } + }, + clearSelection() { + this.lastSelected = []; + this.$emit('update:selected',[]); + }, + //methods tags + addedTag(addedTag) { + addTagInTable(addedTag, this.allRows, 'prestudent_id') + syncTagHeaderFilterOptions( + this.$refs.table?.tabulator?.getData() || [], + this.tagFilterState.initialOptions, + this.tagFilterState.selectedOptions, + ); + }, + deletedTag(deletedTag) { + deleteTagInTable(deletedTag, this.allRows); + syncTagHeaderFilterOptions( + this.$refs.table?.tabulator?.getData() || [], + this.tagFilterState.initialOptions, + this.tagFilterState.selectedOptions, + ); + }, + updatedTag(updatedTag) { + updateTagInTable(updatedTag, this.allRows) + syncTagHeaderFilterOptions( + this.$refs.table?.tabulator?.getData() || [], + this.tagFilterState.initialOptions, + this.tagFilterState.selectedOptions, + ); + }, + getAllRows() { + this.allRows = this.$refs.table.tabulator.getRows(); + }, + resetFilter() { + this.clearSelectedTagHeaderFilters(); + this.$refs.listfilter.resetFilter(); + this.$refs.table.clearFilters(); + }, + clearSelectedTagHeaderFilters() { + this.tagFilterState.selectedOptions.splice( + 0, + this.tagFilterState.selectedOptions.length + ); + }, + handleHeaderFilter(filterActive) { + this.headerFilterActive = filterActive; + }, + handleMouseDown(e, row) { + let data = row.getData(); + let id = data.uid ?? data.prestudent_id ?? data.person_id; + + const isAlreadySelected = this.selected?.some( + row => (row.uid ?? row.prestudent_id ?? row.person_id) === id + ); + + this.dragSource = + isAlreadySelected && this.selected?.length ? this.selected : [data]; + }, + handleDataLoading() { + this.oldScrollLeft = this.$refs.table.tabulator.rowManager.scrollLeft; + this.oldScrollTop = this.$refs.table.tabulator.rowManager.scrollTop; + }, + handleRenderComplete() { + const table = this.$refs.table.tabulator.element.querySelector( + '.tabulator-tableholder' + ); + if (table) { + const curAjaxUrl = this.$refs.table.tabulator.getAjaxUrl(); + if (this.oldScrollUrl === curAjaxUrl) { + table.scrollLeft = this.oldScrollLeft; + table.scrollTop = this.oldScrollTop; + } else { + this.oldScrollLeft = table.scrollLeft; + this.oldScrollTop = table.scrollTop; + } + this.oldScrollUrl = this.$refs.table.tabulator.getAjaxUrl(); + } + }, + async rebuild(selected){ + const maxbulksize = 25; + const minbulksize = 5; + let bulksize = minbulksize; + this.isLoading = true; + let prestudentIds = []; + if(!selected.length) + prestudentIds = this.$refs.table.tabulator + .getData() + .map(row => row.prestudent_id); + else + prestudentIds = selected.map(item => item.prestudent_id); + + this.progress = 0; + this.total = prestudentIds.length; + this.processed = 0; + this.rebuildData = [ + [], + [], + [] + ]; + + this.showModal(); + + while(this.processed < this.total) + { + let iteration_prestudentIds = prestudentIds.slice( + this.processed, + (this.processed + bulksize) + ); + const params = { + ids: iteration_prestudentIds, + typeId: 'prestudent_id', + sem: this.studiensemesterKurzbz }; - }, - fileString() { - let today = new Date().toLocaleDateString('en-GB') - .replace(/\//g, '_'); - return "StudentList_" + today + ".csv"; - }, - }, - created: function() { - if(this.tagsEnabled) { - const coltags = { - title: 'Tags', - field: 'tags', - tooltip: false, - headerFilter: "input", - headerFilterFunc: tagHeaderFilter, - headerFilterFuncParams: {field: 'tags'}, - formatter: (cell) => tagFormatter(cell, this.$refs.tagComponent), - width: 150, + + try + { + const result = await this.$api.call(ApiTag.rebuildTagsforTypeId(params)); + this.rebuildData[1] = [...this.rebuildData[1], ...result.data[1]]; + this.rebuildData[2] = [...this.rebuildData[2], ...result.data[2]]; + } + catch(error) { + this.$fhcAlert.handleSystemError(error); }; - this.tabulatorOptions.columns.splice(2, 0, coltags); + + this.processed += iteration_prestudentIds.length; + this.progress = Math.round(this.processed * 100 / this.total); + + bulksize = bulksize * 2; + if(bulksize > maxbulksize) bulksize = maxbulksize; } - }, - watch: { - '$p.user_language.value'(n, o) { - if (n !== o && o !== undefined && this.$refs.table.tableBuilt) { - this.translateTabulator(); - } + + if(this.rebuildData[1].length > 0) + { + this.$fhcAlert.alertSuccess(this.$p.t('tag', 'alertSuccessRebuild', { count: this.rebuildData[1].length })); } + if(this.rebuildData[2].length > 0) + { + this.$fhcAlert.alertError(this.$p.t('tag', 'alertErrorRebuild') + this.rebuildData[2].toString()); + } + + this.isLoading = false; + this.$reloadList(); }, - methods: { - translateTabulator() { - this.$p - .loadCategory(['global', 'person', 'lehre', 'ui', 'profilUpdate', 'admission', 'stv']) - .then(() => { - const translations = { - uid: capitalize(this.$p.t('person/uid')), - titelpre: capitalize(this.$p.t('person/titelpre')), - nachname: capitalize(this.$p.t('person/nachname')), - vorname: capitalize(this.$p.t('person/vorname')), - wahlname: capitalize(this.$p.t('person/wahlname')), - vornamen: capitalize(this.$p.t('person/vornamen')), - titelpost: capitalize(this.$p.t('person/titelpost')), - ersatzkennzeichen: capitalize(this.$p.t('person/ersatzkennzeichen')), - gebdatum: capitalize(this.$p.t('person/geburtsdatum')), - geschlecht: capitalize(this.$p.t('person/geschlecht')), - semester: capitalize(this.$p.t('lehre/sem')), - verband: capitalize(this.$p.t('lehre/verb')), - gruppe: capitalize(this.$p.t('lehre/grp')), - studiengang: capitalize(this.$p.t('lehre/studiengang')), - studiengang_kz: capitalize(this.$p.t('lehre/studiengang_kz')), - matrikelnr: capitalize(this.$p.t('person/personenkennzeichen')), - person_id: capitalize(this.$p.t('person/person_id')), - status: capitalize(this.$p.t('global/status')), - status_datum: capitalize(this.$p.t('profilUpdate/statusDate')), - status_bestaetigung: capitalize(this.$p.t('global/status_bestaetigung')), - mail_privat: capitalize(this.$p.t('person/email_private')), - mail_intern: capitalize(this.$p.t('person/email_intern')), - anmerkungen: capitalize(this.$p.t('stv/notes_person')), - anmerkung: capitalize(this.$p.t('stv/notes_prestudent')), - orgform_kurzbz: capitalize(this.$p.t('lehre/orgform')), - aufmerksamdurch_kurzbz: capitalize(this.$p.t('person/aufmerksamDurch')), - punkte: capitalize(this.$p.t('admission/gesamtpunkte')), - aufnahmegruppe_kurzbz: capitalize(this.$p.t('stv/aufnahmegruppe_kurzbz')), - dual: capitalize(this.$p.t('lehre/dual_short')), - matr_nr: capitalize(this.$p.t('person/matrikelnummer')), - studienplan_bezeichnung: capitalize(this.$p.t('lehre/studienplan')), - prestudent_id: capitalize(this.$p.t('ui/prestudent_id')), - priorisierung_relativ: capitalize(this.$p.t('lehre/prioritaet')), - mentor: capitalize(this.$p.t('stv/mentor')), - bnaktiv: capitalize(this.$p.t('person/aktiv')) - }; - - /** NOTE(chris): - * use this approach because updateDefinition - * on the Tabulator columns is way slower and - * freezes up the GUI. - */ - // Overwrite definition for column show/hide - this.$refs.table.tabulator.getColumns().forEach(col => { - const trans = translations[col.getField()]; - if (!trans) - return; - col.getDefinition().title = trans; - }); - // Overwrite node in dom - this.$refs.table.tabulator.element - .querySelectorAll('.tabulator-col[tabulator-field]') - .forEach(el => { - const field = el.getAttribute('tabulator-field'); - if (!translations[field]) - return; - - const title = el.querySelector('.tabulator-col-title'); - if (!title) - return; - - title.innerText = translations[field]; - }); - }); - }, - reload() { - this.$refs.table.reloadTable(); - }, - actionNewPrestudent() { - this.$refs.new.open(); - }, - rowSelectionChanged(data, rows) { - this.selectedcount = data.length; - this.lastSelected = this.selected; - - //for tags - this.selectedRows = this.$refs.table.tabulator.getSelectedRows(); - this.selectedColumnValues = this.selectedRows.filter(row => row.getData().prestudent_id !== undefined && row.getData().prestudent_id).map(row => row.getData().prestudent_id); - - this.$emit('update:selected', data); - }, - autoSelectRows(data) { - if (Array.isArray(this.lastSelected) && this.lastSelected.length){ - // NOTE(chris): reselect rows on refresh - let selected = this.lastSelected.map(el => this.$refs.table.tabulator.getRow(el.prestudent_id)) - // TODO(chris): unselect current item if it's no longer in the table? - // or maybe reselect only the last one? - selected = selected.filter(el => el); - - if (selected.length) - this.$refs.table.tabulator.selectRow(selected); - } else if(data && this.lastSelected === undefined) { - // NOTE(chris): select row if it's the only one (preferably only on startup) - if (data.length == 1) { - this.$refs.table.tabulator.selectRow(this.$refs.table.tabulator.getRows()); - } - } - }, - updateFilter(filter) { - this.filter = filter; - this.$emit('filterActive', filter); - this.updateUrl(); - }, - updateUrl(endpoint, first) { - this.lastSelected = first ? undefined : this.selected; - -/* console.log('function param endpoint: ' + JSON.stringify(endpoint)); - console.log('current endpoint: ' + JSON.stringify(this.currentEndpoint));*/ - - if( endpoint === undefined && this.currentEndpoint === null) - { - endpoint = { url: '' }; - } - else if( endpoint === undefined ) - { - endpoint = JSON.parse(JSON.stringify(this.currentEndpoint)); - } - else - { - this.currentEndpoint = JSON.parse(JSON.stringify(endpoint)); - } - - endpoint.url = endpoint.url.replace( - 'CURRENT_SEMESTER', - encodeURIComponent(this.currentSemester) - ); - - const params = (endpoint?.params !== undefined) ? endpoint.params : {}; - let method = (endpoint?.method !== undefined) ? endpoint.method : 'get'; - if (this.filter.length && !endpoint.url.match(/\/search\//)) - { - params.filter = this.filter; - method = 'post'; - } - - this.tabulatorOptions.ajaxURL = endpoint.url; - this.tabulatorOptions.ajaxParams = { ...params }; - this.tabulatorOptions.ajaxConfig = {method}; - - if (!this.$refs.table.tableBuilt) { - if (this.$refs.table.tabulator) { - this.$refs.table.tabulator.on("tableBuilt", () => { - this.$refs.table.tabulator.setData(endpoint.url, params, method); - }); - } - } else - this.$refs.table.tabulator.setData(endpoint.url, params, method); - }, - dragCleanup(evt) { - this.dragSource = []; - if (evt.dataTransfer.dropEffect == 'none') - return; // aborted or wrong target - - this.$reloadList(); - }, - onKeydown(e) { // TODO(chris): this should be in the filter component - if (!this.focusObj) - return; - - // Ignore typing inside editable elements - if ( - e.target instanceof HTMLInputElement || - e.target instanceof HTMLTextAreaElement || - e.target.isContentEditable - ) - return; - - var next; - switch (e.code) { - case 'Enter': - case 'Space': - e.preventDefault(); - var e2 = new Event('click', e); - e2.altKey = e.altKey; - e2.ctrlKey = e.ctrlKey; - e2.shiftKey = e.shiftKey; - this.focusObj.dispatchEvent(e2); - //row.component.toggleSelect(); - break; - case 'ArrowUp': - e.preventDefault(); - next = this.focusObj.previousElementSibling; - if (next) - this.changeFocus(this.focusObj, next); - break; - case 'ArrowDown': - e.preventDefault(); - next = this.focusObj.nextElementSibling; - if (next) - this.changeFocus(this.focusObj, next); - break; - } - }, - changeFocus(a, b) { // TODO(chris): this should be in the filter component - if (b) { - b.tabIndex = 0; - this.focusObj = b; - b.focus(); - } else { - this.focusObj = null; - } - a.tabIndex = -1; - return this.focusObj; - }, - onFocus(e) { // TODO(chris): this should be in the filter component - if (!this.focusObj) { - var container, target; - if (e.target.classList.contains('tabulator-container')) { - container = e.target; - target = container.querySelector('.tabulator-row'); - } else if (e.target.classList.contains('tabulator-row')) { - container = e.target.closest('.tabulator-container'); - target = e.target; - } - if (container && target) { - this.changeFocus(container, target); - } - } - }, - handleRowClick(e, row) { // TODO(chris): this should be in the filter component - if (this.focusObj) { - let el = row.getElement(); - if (el != this.focusObj) - this.changeFocus(this.focusObj, el); - } - }, - //methods tags - addedTag(addedTag) - { - addTagInTable(addedTag, this.allRows, 'prestudent_id') - }, - deletedTag(deletedTag) - { - deleteTagInTable(deletedTag, this.allRows); - }, - updatedTag(updatedTag) - { - updateTagInTable(updatedTag, this.allRows) - }, - getAllRows() { - this.allRows = this.$refs.table.tabulator.getRows(); - }, - resetFilter(){ - this.$refs.listfilter.resetFilter(); - this.$refs.table.clearFilters(); - }, - handleHeaderFilter(filterActive){ - this.headerFilterActive = filterActive; - }, - handleMouseDown(e, row) - { - let data = row.getData(); - let id = data.uid ?? data.prestudent_id ?? data.person_id; - - const isAlreadySelected = this.selected?.some(row => (row.uid ?? row.prestudent_id ?? row.person_id) === id); - - this.dragSource = (isAlreadySelected && this.selected?.length) ? this.selected : [data]; - }, - handleDataLoading() { - this.oldScrollLeft = this.$refs.table.tabulator.rowManager.scrollLeft; - this.oldScrollTop = this.$refs.table.tabulator.rowManager.scrollTop; - }, - handleRenderComplete() { - const table = this.$refs.table.tabulator.element.querySelector('.tabulator-tableholder'); - if(table) { - const curAjaxUrl = this.$refs.table.tabulator.getAjaxUrl(); - if(this.oldScrollUrl === curAjaxUrl) { - table.scrollLeft = this.oldScrollLeft; - table.scrollTop = this.oldScrollTop; - } else { - this.oldScrollLeft = table.scrollLeft; - this.oldScrollTop = table.scrollTop; - } - this.oldScrollUrl = this.$refs.table.tabulator.getAjaxUrl(); - } - }, + showModal(){ + this.$refs.modalLocked.open(); }, - // TODO(chris): focusin, focusout, keydown and tabindex should be in the filter component - // TODO(chris): filter component column chooser has no accessibilty features - template: ` -
-
- tagFormatter(cell, this.$refs.tagComponent, onRendered), + width: 150, + headerSort: false, + }; + this.tabulatorOptions.columns.splice(2, 0, coltags); + this.$p.loadCategory("ui").then(() => { + this.updateTagFilterLabels(); + }); + } + }, + // TODO(chris): focusin, focusout, keydown and tabindex should be in the filter component + // TODO(chris): filter component column chooser has no accessibilty features + template: /* html */` +
+ + + +
+ + + + + + + - - -
- -
` + +
+
+ +
` }; diff --git a/public/js/components/Stv/Studentenverwaltung/List/ModalLoading.js b/public/js/components/Stv/Studentenverwaltung/List/ModalLoading.js new file mode 100644 index 000000000..99368173e --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/List/ModalLoading.js @@ -0,0 +1,84 @@ +import BsModal from "../../../Bootstrap/Modal.js"; +import FormForm from "../../../Form/Form.js"; + +export default { + name: "modalLoading", + components: { + BsModal, + FormForm, + PvProgressBar: primevue.progressbar + }, + props: { + isLoading: { + type: Boolean, + required: true + }, + message: { + type: String, + required: true + }, + progress: { + type: Number, + default: -1 + }, + total: { + type: Number, + default: -1 + }, + processed: { + type: Number, + default: -1 + } + }, + data(){ + return{} + }, + methods:{ + open(){ + this.$refs.modalLoading.show(); + }, + closeModal(){ + this.$refs.modalLoading.hide(); + } + }, + template: ` + + + + +
+
+ {{message}} +
+
+ {{$p.t('ui', 'fenster_schliessen')}} +
+
+
+ + {{processed}} / {{total}} + + + +
+ `, +} \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/List/New.js b/public/js/components/Stv/Studentenverwaltung/List/New.js index 6b7df3581..90d4e498b 100644 --- a/public/js/components/Stv/Studentenverwaltung/List/New.js +++ b/public/js/components/Stv/Studentenverwaltung/List/New.js @@ -285,6 +285,7 @@ export default { uid="stv-list-new-gebdatum" name="gebdatum" v-model="formDataPerson['gebdatum']" + model-type="yyyy-MM-dd" :disabled="!!person" @update:model-value="loadSuggestions" text-input diff --git a/public/js/components/Stv/Studentenverwaltung/Studiensemester.js b/public/js/components/Stv/Studentenverwaltung/Studiensemester.js index 32b0a831d..33230b54a 100644 --- a/public/js/components/Stv/Studentenverwaltung/Studiensemester.js +++ b/public/js/components/Stv/Studentenverwaltung/Studiensemester.js @@ -1,4 +1,4 @@ -import {CoreRESTClient} from '../../../RESTClient.js'; +import ApiStudiensemester from '../../../api/factory/studiensemester.js'; export default { emits: [ @@ -18,6 +18,11 @@ export default { today: -1 }; }, + computed: { + lowerCaseList() { + return this.list.map(i => i.toLowerCase()); + } + }, methods: { set(n) { this.loading = true; @@ -47,25 +52,18 @@ export default { } this.loading = true; - CoreRESTClient - .get('components/stv/studiensemester/now') - .then(result => CoreRESTClient.getData(result.data)) + this.$api + .call(ApiStudiensemester.current()) .then(result => { - this.today = this.list.indexOf(result); - if (this.today >= 0) { - if (this.today != this.current) - this.set(this.today); - } else { - // TODO(chris): handle error (list might not be loaded yet) - } + this.today = this.list.indexOf(result.data); + if (this.today >= 0 && this.today != this.current) + this.set(this.today); }) .catch(this.$fhcAlert.handleSystemError); }, save(fallback) { - CoreRESTClient - .post('components/stv/studiensemester/set', { - studiensemester: this.list[this.current] - }) + this.$api + .call(ApiStudiensemester.set(this.list[this.current])) .then(() => { this.loading = false; this.$emit('update:studiensemesterKurzbz', this.list[this.current]); @@ -73,27 +71,42 @@ export default { .catch(error => { this.current = fallback; this.loading = false; - this.$fhcAlert.handleFormValidation(error); + this.$fhcAlert.handleSystemError(error); }); + }, + index() { + let index = this.list.indexOf(this.studiensemesterKurzbz); + if (index >= 0) + return index; + + // check if input is lower cases (because it came from an url) + index = this.lowerCaseList.indexOf(this.studiensemesterKurzbz); + if (index >= 0) { + this.$emit('update:studiensemesterKurzbz', this.list[index]); + return index; + } + + this.$emit('update:studiensemesterKurzbz', null); + return -1; } }, watch: { - 'studiensemesterKurzbz': function () { - this.current = this.list.indexOf(this.studiensemesterKurzbz); + studiensemesterKurzbz() { + if (!this.loading) + this.current = this.index(this.studiensemesterKurzbz); } }, created() { - CoreRESTClient - .get('components/stv/studiensemester') - .then(result => CoreRESTClient.getData(result.data) || []) + this.$api + .call(ApiStudiensemester.getAll()) .then(result => { - this.list = result.map(el => el.studiensemester_kurzbz); + this.list = result.data.map(el => el.studiensemester_kurzbz); this.loading = false; - this.current = this.list.indexOf(this.studiensemesterKurzbz); + this.current = this.index(this.studiensemesterKurzbz); }) .catch(this.$fhcAlert.handleSystemError); }, - template: ` + template: /* html */`
diff --git a/public/js/components/Tag/tagFormatter.js b/public/js/components/Tag/tagFormatter.js new file mode 100644 index 000000000..072906be8 --- /dev/null +++ b/public/js/components/Tag/tagFormatter.js @@ -0,0 +1,111 @@ +export function idTagFormatter (id, tagData, tagComponent, typeId, semesterStart=null, semesterEnd=null) +{ + if (!id) return; + + const hasSemesterFilter = !!(semesterStart && semesterEnd); + + const semStart = hasSemesterFilter ? new Date(semesterStart) : null; + const semEnd = hasSemesterFilter ? new Date(semesterEnd) : null; + + const parsedTags = tagData.map(tag => ({ + id: tag.notiz_id, + typ_kurzbz: tag.titel?.toLowerCase(), + beschreibung: tag.bezeichnung, + notiz: tag.text || "", + style: tag.style, + done: tag.done, + automatisiert: tag.automatisiert, + typeId: id, + validFrom: tag.start ? new Date(tag.start) : null, + validTo: tag.ende ? new Date(tag.ende) : null + })); + + const isInSemester = (tag) => { + if (!hasSemesterFilter) return true; + + if (!tag.validFrom && !tag.validTo) return true; + + if (!tag.validFrom && !tag.validTo) return true; + + if (tag.validFrom && tag.validTo) { + return tag.validFrom <= semEnd && tag.validTo >= semStart; + } + + if (tag.validFrom && !tag.validTo) { + return tag.validFrom <= semEnd; + } + + if (!tag.validFrom && tag.validTo) { + return tag.validTo >= semStart; + } + + return false; + }; + + let container = document.createElement('div'); + container.className = "d-flex gap-1"; + + let maxVisibleTags = 5; + let expanded = false; + const renderTags = () => { + container.innerHTML = ''; + + let filtered = parsedTags + .filter(t => t != null) + .filter(isInSemester); + + filtered.sort((a, b) => { + let adone = a.done ? 1 : 0; + let bdone = b.done ? 1 : 0; + + if (adone !== bdone) return adone - bdone; + return a.prioritaet - b.prioritaet; + }); + + const tagsToShow = expanded + ? filtered + : filtered.slice(0, maxVisibleTags); + + tagsToShow.forEach(tag => { + let tagElement = document.createElement('span'); + + if(tag.automatisiert) + tagElement.innerHTML = " " + tag.beschreibung; + else + tagElement.innerText = tag.beschreibung; + tagElement.title = tag.notiz; + tagElement.className = "tag " + tag.style; + + if (tag.done) { + tagElement.className += " tag_done"; + } + if (tag.automatisiert) + tagElement.className += " tag_auto"; + + tagElement.addEventListener('click', (event) => { + event.stopPropagation(); + event.preventDefault(); + tagComponent.editTag(tag.id); + }); + + container.appendChild(tagElement); + }); + + if (filtered.length > maxVisibleTags) { + let toggle = document.createElement('button'); + toggle.innerText = (expanded ? '- ' : '+ ') + (filtered.length - maxVisibleTags); + toggle.className = "display_all"; + + toggle.addEventListener('click', () => { + expanded = !expanded; + renderTags(); + }); + + container.appendChild(toggle); + } + }; + + renderTags(); + + return container; +} \ No newline at end of file diff --git a/public/js/components/Udf/Udf.js b/public/js/components/Udf/Udf.js index c765d8a14..cfb5384c8 100644 --- a/public/js/components/Udf/Udf.js +++ b/public/js/components/Udf/Udf.js @@ -4,6 +4,7 @@ import FormInput from '../Form/Input.js'; import ApiUdf from '../../api/udf.js'; export default { + name: 'CoreUdfCmpt', components: { CoreFetchCmpt, FormInput @@ -26,7 +27,8 @@ export default { // The values as associative array modelValue: Object, // Show only fields with a name that exists in the filter - filter: [String, Array] + filter: [String, Array], + readonly: true }, data() { return { @@ -35,6 +37,13 @@ export default { }; }, computed: { + isPkValid() { + // If the provided pk is _not_ valid + return !Object.keys(this.pk).every(key => !this.pk[key]); + }, + getReadonly() { + return this.readonly; + }, filterArray() { if (!this.filter || Array.isArray(this.filter)) return this.filter; @@ -61,6 +70,10 @@ export default { }, watch: { pk(n, o) { + + // If the provided pk is _not_ valid + if (Object.keys(n).every(key => !n[key])) return; + if (!this.$refs.fetch) return; // NOTE(chris): no initial load yet @@ -124,6 +137,7 @@ export default { template: `
` -} \ No newline at end of file +} diff --git a/public/js/components/filter/Filter.js b/public/js/components/filter/Filter.js index 3d44b51da..31b1efe12 100644 --- a/public/js/components/filter/Filter.js +++ b/public/js/components/filter/Filter.js @@ -15,25 +15,27 @@ * along with this program. If not, see . */ -import {CoreFetchCmpt} from '../../components/Fetch.js'; -import FilterConfig from './Filter/Config.js'; -import FilterColumns from './Filter/Columns.js'; -import TableDownload from './Table/Download.js'; -import collapseAutoClose from '../../directives/collapseAutoClose.js'; +import { CoreFetchCmpt } from "../../components/Fetch.js"; +import FilterConfig from "./Filter/Config.js"; +import FilterColumns from "./Filter/Columns.js"; +import TablePresets from "./Filter/TablePresets.js"; +import TableDownload from "./Table/Download.js"; +import collapseAutoClose from "../../directives/collapseAutoClose.js"; -import moduleLayoutFitDataStretchFrozen from '../../tabulator/layouts/fitDataStretchFrozen.js'; -import InternalToExternalEventBroadcastModule from "../../tabulator/customModules/InternalToExternalEventBroadcastModule.js" -import MenuExtensionModule from "../../tabulator/customModules/MenuExtensionModule.js" -import ResponsiveLayoutExtensionModule from "../../tabulator/customModules/ResponsiveLayoutExtensionModule.js" +import moduleLayoutFitDataStretchFrozen from "../../tabulator/layouts/fitDataStretchFrozen.js"; +import InternalToExternalEventBroadcastModule from "../../tabulator/customModules/InternalToExternalEventBroadcastModule.js"; +import MenuExtensionModule from "../../tabulator/customModules/MenuExtensionModule.js"; +import ResponsiveLayoutExtensionModule from "../../tabulator/customModules/ResponsiveLayoutExtensionModule.js"; +import TablePresetsModule from "../../tabulator/customModules/TablePresetsModule.js"; import { debounce } from "../../helpers/DebounceHelper.js"; -import ApiFilter from '../../api/factory/filter.js'; -import ApiPhrases from '../../api/factory/phrasen.js'; +import ApiFilter from "../../api/factory/filter.js"; +import ApiPhrases from "../../api/factory/phrasen.js"; // -const FILTER_COMPONENT_NEW_FILTER = 'Filter Component New Filter'; -const FILTER_COMPONENT_NEW_FILTER_TYPE = 'Filter Component New Filter Type'; +const FILTER_COMPONENT_NEW_FILTER = "Filter Component New Filter"; +const FILTER_COMPONENT_NEW_FILTER_TYPE = "Filter Component New Filter Type"; var _uuid = 0; @@ -45,17 +47,18 @@ export const CoreFilterCmpt = { CoreFetchCmpt, FilterConfig, FilterColumns, - TableDownload + TablePresets, + TableDownload, }, directives: { - collapseAutoClose + collapseAutoClose, }, emits: [ - 'nwNewEntry', - 'click:new', - 'tableBuilt', - 'uuidDefined', - 'headerFilterOn' + "nwNewEntry", + "click:new", + "tableBuilt", + "uuidDefined", + "headerFilterOn", ], props: { onNwNewEntry: Function, // NOTE(chris): Hack to get the nwNewEntry listener into $props @@ -63,20 +66,20 @@ export const CoreFilterCmpt = { description: String, sideMenu: { type: Boolean, - default: true + default: true, }, filterType: { - type: String + type: String, }, tabulatorOptions: Object, tabulatorEvents: Array, tableOnly: Boolean, - noColumnFilter:Boolean, + noColumnFilter: Boolean, reload: Boolean, reloadBtnInfotext: String, download: { type: [Boolean, String, Function, Array, Object], - default: false + default: false, }, newBtnShow: Boolean, newBtnClass: [String, Array, Object], @@ -89,11 +92,22 @@ export const CoreFilterCmpt = { countOnly: Boolean, useSelectionSpan: { type: Boolean, - default: true + default: true, + }, + isUsingPresets: { + Boolean, + default: false, + }, + presetsId: { + String, + default: "", + }, + generalPresets: { + type: Array, + default: [], }, }, - inject: ["language"], - data: function() { + data: function () { return { uuid: 0, // FilterCmpt properties @@ -129,41 +143,48 @@ export const CoreFilterCmpt = { page: false, }, collapsedHeadingLocalizationTimer: null, + hasPresetBeenModified: false, + appliedPreset: null, }; }, computed: { notSelectedFields() { - if (!this.fields || !this.selectedFields) - return null; - return this.fields.filter(x => this.selectedFields.indexOf(x) === -1) + if (!this.fields || !this.selectedFields) return null; + return this.fields.filter( + (x) => this.selectedFields.indexOf(x) === -1, + ); }, filteredData() { - if (!this.dataset) - return []; + if (!this.dataset) return []; return JSON.parse(JSON.stringify(this.dataset)); }, filteredColumns() { let fields = JSON.parse(JSON.stringify(this.fields)) || []; - let selectedFields = JSON.parse(JSON.stringify(this.selectedFields)) || []; + let selectedFields = + JSON.parse(JSON.stringify(this.selectedFields)) || []; let columns = null; // If the tabulator options has been provided and it contains the property columns - if (this.tabulatorOptions && this.tabulatorOptions.hasOwnProperty('columns')) + if ( + this.tabulatorOptions && + this.tabulatorOptions.hasOwnProperty("columns") + ) columns = this.tabulatorOptions.columns; // If columns is not an array or it is an array with less elements then the array fields - if (!Array.isArray(columns) || (Array.isArray(columns) && columns.length < fields.length)) - { + if ( + !Array.isArray(columns) || + (Array.isArray(columns) && columns.length < fields.length) + ) { columns = []; // set it as an empty array // Loop throught all the retrieved columns from database - for (let field of fields) - { + for (let field of fields) { // Create a new column having the title equal to the field name let column = { title: field, - field: field + field: field, }; // If the column has to be displayed or not @@ -172,18 +193,18 @@ export const CoreFilterCmpt = { // Add the new column to the list of columns columns.push(column); } - } - else // the property columns has been provided in the tabulator options + } else // the property columns has been provided in the tabulator options { // Loop throught the property columns of the tabulator options - for (let col of columns) - { + for (let col of columns) { // If the column has to be displayed or not /* fields.indexOf(col.field) == -1; ensures displaying formatter colums e.g. column with rowSelection checkboxes or with custom formatted action buttons */ - col.visible = selectedFields.indexOf(col.field) >= 0 || fields.indexOf(col.field) == -1; + col.visible = + selectedFields.indexOf(col.field) >= 0 || + fields.indexOf(col.field) == -1; - if (col.hasOwnProperty('resizable')) + if (col.hasOwnProperty("resizable")) col.resizable = col.visible; } } @@ -193,28 +214,13 @@ export const CoreFilterCmpt = { fieldIdsForVisibilty() { if (!this.tableBuilt) return []; - const localizedColumnTitles = this.tabulator.getLang().columns; - const isTabulatorLocalized = !!this.$props.tabulatorOptions.locale; - return this.tabulator - .getColumns() - .filter((col) => { - let def = col.getDefinition(); - let title = - isTabulatorLocalized && localizedColumnTitles[def.field] - ? localizedColumnTitles[def.field] - : def.title; - return ( - !def.frozen && - title && - def.formatter != "responsiveCollapse" - ); - }) - .map((col) => col.getField()); + return this.$props.tabulatorOptions?.columns + ?.filter((column) => column.formatter !== "responsiveCollapse") + .map((column) => column.field) ?? []; }, idExtra() { - if (!this.uuid) - return ''; - return '-' + this.uuid; + if (!this.uuid) return ""; + return "-" + this.uuid; }, columnsForFilter() { if (!this.filteredColumns || !this.datasetMetadata) return []; @@ -240,6 +246,9 @@ export const CoreFilterCmpt = { ...{ title: filterTitles[el.name] }, })); }, + language() { + return this.$p.user_language; + }, }, watch: { "language.value": { @@ -252,66 +261,90 @@ export const CoreFilterCmpt = { }, methods: { reloadTable() { - if (this.tableOnly) - this.tabulator.setData(); - else - this.getFilter(); + if (this.tableOnly) this.tabulator.setData(); + else this.getFilter(); }, setSelectedFields() { const cols = this.tabulator.getColumns(); - this.selectedFields = cols.filter(col => col.isVisible()).map(col => col.getField()); + this.selectedFields = cols + .filter((col) => col.isVisible()) + .map((col) => col.getField()); }, async initTabulator() { - let placeholder = '< Phrasen Plugin not loaded! >'; + let placeholder = "< Phrasen Plugin not loaded! >"; if (this.$p) { - await this.$p.loadCategory('ui'); - placeholder = this.$p.t('ui/keineDatenVorhanden'); + 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; + await new Promise((resolve) => { + const filterId = window.location.hash + ? window.location.hash.slice(1) + : null; - const resolvePromiseFunc = data => { + const resolvePromiseFunc = (data) => { this.setRenderData(data); resolve(); }; // get the filter data if (filterId === null) this.startFetchCmpt( - wsParams => this.$api.call(ApiFilter.getFilter(wsParams)), + (wsParams) => + this.$api.call(ApiFilter.getFilter(wsParams)), null, - resolvePromiseFunc + resolvePromiseFunc, ); else this.startFetchCmpt( - wsParams => this.$api.call(ApiFilter.getFilterById(wsParams)), + (wsParams) => + this.$api.call( + ApiFilter.getFilterById(wsParams), + ), { filterId }, - resolvePromiseFunc + resolvePromiseFunc, ); }); } // Define a default tabulator options in case it was not provided - let tabulatorOptions = {...{ + let tabulatorOptions = { + ...{ layout: "fitDataStretchFrozen", movableColumns: true, - columnDefaults:{ - tooltip: true + columnDefaults: { + tooltip: true, }, placeholder, persistence: this.persistence, - }, ...(this.tabulatorOptions || {})}; + }, + ...(this.tabulatorOptions || {}), + }; + + if ( + this.$props.isUsingPresets && + "tabulatorPreset-" + this.$props.presetsId in + window.localStorage + ) { + tabulatorOptions.persistence.columns = false; + tabulatorOptions.persistence.headerFilter = false; + tabulatorOptions.persistence.sort = false; + } if (tabulatorOptions.locale) { - tabulatorOptions = await this.configureTabulatorLocalizations(tabulatorOptions); + tabulatorOptions = + await this.configureTabulatorLocalizations( + tabulatorOptions, + ); } // set default height if no height property is set - if (tabulatorOptions.height === undefined && + if ( + tabulatorOptions.height === undefined && tabulatorOptions.minHeight === undefined && - tabulatorOptions.maxHeight === undefined) { + tabulatorOptions.maxHeight === undefined + ) { tabulatorOptions.height = 500; } @@ -319,13 +352,19 @@ export const CoreFilterCmpt = { tabulatorOptions.data = this.filteredData; tabulatorOptions.columns = this.filteredColumns; } else { - tabulatorOptions.columns.forEach(col => { - if (col.visible === undefined) - col.visible = true; + tabulatorOptions.columns.forEach((col) => { + if (col.visible === undefined) col.visible = true; }); } - if (tabulatorOptions.selectable || tabulatorOptions.selectableRows || (tabulatorOptions.columns && tabulatorOptions.columns.filter(el => el.formatter == 'rowSelection').length)) + if ( + tabulatorOptions.selectable || + tabulatorOptions.selectableRows || + (tabulatorOptions.columns && + tabulatorOptions.columns.filter( + (el) => el.formatter == "rowSelection", + ).length) + ) this.tabulatorHasSelector = true; if (this.idField) { @@ -341,60 +380,70 @@ export const CoreFilterCmpt = { debugInvalidOptions: false, }); // If event handlers have been provided - if (Array.isArray(this.tabulatorEvents) && this.tabulatorEvents.length > 0) - { + if ( + Array.isArray(this.tabulatorEvents) && + this.tabulatorEvents.length > 0 + ) { // Attach all the provided event handlers to the started tabulator for (let evt of this.tabulatorEvents) this.tabulator.on(evt.event, evt.handler); } - this.tabulator.on('tableBuilt', () => { + this.tabulator.on("tableBuilt", () => { this.tableBuilt = true; - this.$emit('tableBuilt'); + this.$emit("tableBuilt"); }); - this.tabulator.on("rowSelectionChanged", data => { + this.tabulator.on("rowSelectionChanged", (data) => { this.selectedData = data; }); + this.tabulator.on("columnMoved", (column, columns) => { + this.checkAgainstAppliedPreset(); + }); + this.tabulator.on("dataSorted", (sorters, rows) => { + this.checkAgainstAppliedPreset(); + }); // if nested tabulator, restructure data if (this.parentIdField && this.idField) { - this.tabulator.on("dataLoading", data => { + this.tabulator.on("dataLoading", (data) => { let toDelete = []; // loop through all data - for (let childIdx = 0; childIdx < data.length; childIdx++) - { + for (let childIdx = 0; childIdx < data.length; childIdx++) { let child = data[childIdx]; // if it has parent id, it is a child - if (child[this.parentIdField]) - { + if (child[this.parentIdField]) { // append the child on the right place. If parent found, mark original sw child on 0 level for deleting - if (this.appendChild(data, child)) toDelete.push(childIdx); + if (this.appendChild(data, child)) + toDelete.push(childIdx); } } // delete the marked children from 0 level - for (let counter = 0; counter < toDelete.length; counter++) - { + for ( + let counter = 0; + counter < toDelete.length; + counter++ + ) { // decrease index by counter as index of data array changes after every deletion data.splice(toDelete[counter] - counter, 1); } }); } if (this.tableOnly) { - this.tabulator.on('tableBuilt', () => { + this.tabulator.on("tableBuilt", () => { const cols = this.tabulator.getColumns(); - this.fields = cols.map(col => col.getField()); + this.fields = cols.map((col) => col.getField()); this.setSelectedFields(); if (this.tabulator.options.persistence.headerFilter) this._setHeaderFilter(); }); - } - this.tabulator.on("dataFiltered", filters => { + this.tabulator.on("dataFiltered", (filters) => { this.filterActive = filters.length > 0; this.$emit("headerFilterOn", this.filterActive); + this.checkAgainstAppliedPreset(); }); this.tabulator.on("localized", () => { this.tabulator.modules.responsiveLayout.generateCollapsedContent(); @@ -407,46 +456,75 @@ export const CoreFilterCmpt = { const genericTabulatorPhrasesResponse = await this.$api.call( ApiPhrases.getTabulatorPhrases(), ); - const genericTabulatorPhrasesData = genericTabulatorPhrasesResponse.data; + const genericTabulatorPhrasesData = + genericTabulatorPhrasesResponse.data; Object.keys(genericTabulatorPhrasesData).forEach((language) => { let genericTabulatorPhraseToTranslationMapper = {}; genericTabulatorPhrasesData[language].forEach((phrase) => { - genericTabulatorPhraseToTranslationMapper[phrase.phrase] = phrase.text; + genericTabulatorPhraseToTranslationMapper[phrase.phrase] = + phrase.text; }); tabulatorOptions.langs[language] = { data: { - loading: genericTabulatorPhraseToTranslationMapper["loading"], - error: genericTabulatorPhraseToTranslationMapper["error"], + loading: + genericTabulatorPhraseToTranslationMapper[ + "loading" + ], + error: genericTabulatorPhraseToTranslationMapper[ + "error" + ], }, groups: { item: genericTabulatorPhraseToTranslationMapper["item"], - items: genericTabulatorPhraseToTranslationMapper["items"], + items: genericTabulatorPhraseToTranslationMapper[ + "items" + ], }, pagination: { page_size: - genericTabulatorPhraseToTranslationMapper["page_size"], + genericTabulatorPhraseToTranslationMapper[ + "page_size" + ], page_title: - genericTabulatorPhraseToTranslationMapper["page_title"], - first: genericTabulatorPhraseToTranslationMapper["first"], + genericTabulatorPhraseToTranslationMapper[ + "page_title" + ], + first: genericTabulatorPhraseToTranslationMapper[ + "first" + ], first_title: - genericTabulatorPhraseToTranslationMapper["first_title"], + genericTabulatorPhraseToTranslationMapper[ + "first_title" + ], last: genericTabulatorPhraseToTranslationMapper["last"], last_title: - genericTabulatorPhraseToTranslationMapper["last_title"], + genericTabulatorPhraseToTranslationMapper[ + "last_title" + ], prev: genericTabulatorPhraseToTranslationMapper["prev"], prev_title: - genericTabulatorPhraseToTranslationMapper["prev_title"], + genericTabulatorPhraseToTranslationMapper[ + "prev_title" + ], next: genericTabulatorPhraseToTranslationMapper["next"], next_title: - genericTabulatorPhraseToTranslationMapper["next_title"], + genericTabulatorPhraseToTranslationMapper[ + "next_title" + ], all: genericTabulatorPhraseToTranslationMapper["all"], counter: { showing: - genericTabulatorPhraseToTranslationMapper["showing"], + genericTabulatorPhraseToTranslationMapper[ + "showing" + ], of: genericTabulatorPhraseToTranslationMapper["of"], - rows: genericTabulatorPhraseToTranslationMapper["rows"], - pages: genericTabulatorPhraseToTranslationMapper["pages"], + rows: genericTabulatorPhraseToTranslationMapper[ + "rows" + ], + pages: genericTabulatorPhraseToTranslationMapper[ + "pages" + ], }, }, }; @@ -514,49 +592,51 @@ export const CoreFilterCmpt = { }, updateTabulator() { if (this.tabulator) { - if (this.tableBuilt) - this._updateTabulator(); - else - this.tabulator.on('tableBuilt', this._updateTabulator); + if (this.tableBuilt) this._updateTabulator(); + else this.tabulator.on("tableBuilt", this._updateTabulator); } }, _updateTabulator() { - this.tabulatorHasSelector = this.tabulatorOptions.selectable || this.tabulatorOptions.selectableRows || this.filteredColumns.filter(el => el.formatter == 'rowSelection').length; + this.tabulatorHasSelector = + this.tabulatorOptions.selectable || + this.tabulatorOptions.selectableRows || + this.filteredColumns.filter( + (el) => el.formatter == "rowSelection", + ).length; this.tabulator.setColumns(this.filteredColumns); this.tabulator.setData(this.filteredData); - this._setHeaderFilter() + this._setHeaderFilter(); }, - clearFilters() - { + clearFilters() { let existingFilters = this.tabulator.getHeaderFilters(); - existingFilters.forEach(filter => { + existingFilters.forEach((filter) => { this.tabulator.setHeaderFilterValue(filter.field, ""); }); this.tabulator.clearFilter(); this.filterActive = false; - this.$emit('headerFilterOn', this.filterActive) + this.$emit("headerFilterOn", this.filterActive); }, - _setHeaderFilter() - { + _setHeaderFilter() { const existingFilters = this.tabulator.getHeaderFilters(); - existingFilters.forEach(filter => { + existingFilters.forEach((filter) => { this.tabulator.setHeaderFilterValue(filter.field, filter.value); }); }, getFilter() { if (this.selectedFilter === null) this.startFetchCmpt( - wsParams => this.$api.call(ApiFilter.getFilter(wsParams)), + (wsParams) => this.$api.call(ApiFilter.getFilter(wsParams)), null, - this.render + this.render, ); else this.startFetchCmpt( - wsParams => this.$api.call(ApiFilter.getFilterById(wsParams)), + (wsParams) => + this.$api.call(ApiFilter.getFilterById(wsParams)), { - filterId: this.selectedFilter + filterId: this.selectedFilter, }, - this.render + this.render, ); }, setRenderData(data) { @@ -569,12 +649,9 @@ export const CoreFilterCmpt = { this.selectedFields = data.selectedFields; this.filterFields = []; - for (let i = 0; i < data.datasetMetadata.length; i++) - { - for (let j = 0; j < data.filters.length; j++) - { - if (data.datasetMetadata[i].name == data.filters[j].name) - { + for (let i = 0; i < data.datasetMetadata.length; i++) { + for (let j = 0; j < data.filters.length; j++) { + if (data.datasetMetadata[i].name == data.filters[j].name) { let filter = data.filters[j]; filter.type = data.datasetMetadata[i].type; @@ -593,11 +670,9 @@ export const CoreFilterCmpt = { this.setRenderData(data); // If the side menu is active - if (this.sideMenu === true) - { + if (this.sideMenu === true) { this.setSideMenu(data); - } - else // otherwise use the dropdown in the filter options + } else // otherwise use the dropdown in the filter options { this.setDropDownMenu(data); } @@ -611,51 +686,57 @@ export const CoreFilterCmpt = { let personalFilters = data.sideMenu.personalFilters; let filtersArray = []; - for (let filtersCount = 0; filtersCount < filters.length; filtersCount++) - { + for ( + let filtersCount = 0; + filtersCount < filters.length; + filtersCount++ + ) { let link = filters[filtersCount].link; - if (link == null) link = '#'; + if (link == null) link = "#"; filtersArray[filtersArray.length] = { id: filters[filtersCount].filter_id, link: link + filters[filtersCount].filter_id, description: filters[filtersCount].desc, sort: filtersCount, - onClickCall: this.handlerGetFilterById + onClickCall: this.handlerGetFilterById, }; } - for (let filtersCount = 0; filtersCount < personalFilters.length; filtersCount++) - { + for ( + let filtersCount = 0; + filtersCount < personalFilters.length; + filtersCount++ + ) { let link = personalFilters[filtersCount].link; - if (link == null) link = '#'; + if (link == null) link = "#"; filtersArray[filtersArray.length] = { id: personalFilters[filtersCount].filter_id, link: link + personalFilters[filtersCount].filter_id, description: personalFilters[filtersCount].desc, - subscriptDescription: personalFilters[filtersCount].subscriptDescription, - subscriptLinkClass: personalFilters[filtersCount].subscriptLinkClass, - subscriptLinkValue: personalFilters[filtersCount].subscriptLinkValue, + subscriptDescription: + personalFilters[filtersCount].subscriptDescription, + subscriptLinkClass: + personalFilters[filtersCount].subscriptLinkClass, + subscriptLinkValue: + personalFilters[filtersCount].subscriptLinkValue, sort: filtersCount, onClickCall: this.handlerGetFilterById, - onClickSubscriptCall: this.handlerRemoveCustomFilter + onClickSubscriptCall: this.handlerRemoveCustomFilter, }; } this.availableFilters = filtersArray; - this.$emit( - 'nwNewEntry', - { - link: "#", - description: "Filters", - icon: "filter", - children: filtersArray - } - ); + this.$emit("nwNewEntry", { + link: "#", + description: "Filters", + icon: "filter", + children: filtersArray, + }); }, /** * Set the drop down menu @@ -665,29 +746,35 @@ export const CoreFilterCmpt = { let personalFilters = data.sideMenu.personalFilters; let filtersArray = []; - for (let filtersCount = 0; filtersCount < filters.length; filtersCount++) - { + for ( + let filtersCount = 0; + filtersCount < filters.length; + filtersCount++ + ) { let link = filters[filtersCount].link; - if (link == null) link = '#'; + if (link == null) link = "#"; filtersArray[filtersArray.length] = { id: filters[filtersCount].filter_id, option: filters[filtersCount].filter_id, - description: filters[filtersCount].desc + description: filters[filtersCount].desc, }; } - for (let filtersCount = 0; filtersCount < personalFilters.length; filtersCount++) - { + for ( + let filtersCount = 0; + filtersCount < personalFilters.length; + filtersCount++ + ) { let link = personalFilters[filtersCount].link; - if (link == null) link = '#'; + if (link == null) link = "#"; filtersArray[filtersArray.length] = { id: personalFilters[filtersCount].filter_id, option: personalFilters[filtersCount].filter_id, - description: personalFilters[filtersCount].desc + description: personalFilters[filtersCount].desc, }; } @@ -696,7 +783,11 @@ export const CoreFilterCmpt = { /** * Used to start/refresh the FetchCmpt */ - startFetchCmpt(apiFunction, apiFunctionParameters, dataFetchedCallback) { + startFetchCmpt( + apiFunction, + apiFunctionParameters, + dataFetchedCallback, + ) { // Assign the function api of the FetchCmpt binded property this.fetchCmptApiFunction = apiFunction; @@ -704,11 +795,14 @@ export const CoreFilterCmpt = { if (apiFunctionParameters == null) apiFunctionParameters = {}; // Always needed parameters - apiFunctionParameters.filterUniqueId = FHC_JS_DATA_STORAGE_OBJECT.called_path + "/" + FHC_JS_DATA_STORAGE_OBJECT.called_method; + apiFunctionParameters.filterUniqueId = + FHC_JS_DATA_STORAGE_OBJECT.called_path + + "/" + + FHC_JS_DATA_STORAGE_OBJECT.called_method; apiFunctionParameters.filterType = this.filterType; if (this.uniqueId) - apiFunctionParameters.filterUniqueId += '_' + this.uniqueId; + apiFunctionParameters.filterUniqueId += "_" + this.uniqueId; // Assign parameters to the FetchCmpt binded properties this.fetchCmptApiFunctionParams = apiFunctionParameters; @@ -716,7 +810,9 @@ export const CoreFilterCmpt = { this.fetchCmptDataFetched = dataFetchedCallback; // Set the FetchCmpt binded property refresh to have the component to refresh // NOTE: this should be the last one to be called because it triggers the FetchCmpt to start to refresh - this.fetchCmptRefresh === true ? this.fetchCmptRefresh = false : this.fetchCmptRefresh = true; + this.fetchCmptRefresh === true + ? (this.fetchCmptRefresh = false) + : (this.fetchCmptRefresh = true); }, // ------------------------------------------------------------------------------------------------------------------ @@ -729,47 +825,46 @@ export const CoreFilterCmpt = { this.selectedFilter = null; // this.startFetchCmpt( - wsParams => this.$api.call(ApiFilter.saveCustomFilter(wsParams)), + (wsParams) => + this.$api.call(ApiFilter.saveCustomFilter(wsParams)), { - customFilterName + customFilterName, }, - this.getFilter + this.getFilter, ); }, /** * */ handlerRemoveCustomFilter(event) { - let filterId = event.currentTarget.getAttribute("href").substring(1); - if (filterId === this.selectedFilter) - this.selectedFilter = null; + let filterId = event.currentTarget + .getAttribute("href") + .substring(1); + if (filterId === this.selectedFilter) this.selectedFilter = null; // this.startFetchCmpt( - wsParams => this.$api.call(ApiFilter.removeCustomFilter(wsParams)), + (wsParams) => + this.$api.call(ApiFilter.removeCustomFilter(wsParams)), { - filterId: filterId + filterId: filterId, }, - this.getFilter + this.getFilter, ); }, /* * */ - handlerGetFilterById: function(event) { - + handlerGetFilterById: function (event) { let filterId = null; // Get the attribute href if side menu is rendered let attr = event.currentTarget.getAttribute("href"); // Otherwise get the value of the drop down menu - if (attr == null) - { + if (attr == null) { filterId = event.currentTarget.value; - } - else - { + } else { filterId = attr.substring(1); } @@ -782,11 +877,12 @@ export const CoreFilterCmpt = { applyFilterConfig(filterFields) { this.selectedFilter = null; this.startFetchCmpt( - wsParams => this.$api.call(ApiFilter.applyFilterFields(wsParams)), + (wsParams) => + this.$api.call(ApiFilter.applyFilterFields(wsParams)), { - filterFields + filterFields, }, - this.getFilter + this.getFilter, ); }, @@ -796,24 +892,24 @@ export const CoreFilterCmpt = { let parentId = child[this.parentIdField]; // loop thorugh all data - for (let parentIdx = 0; parentIdx < data.length; parentIdx++) - { + for (let parentIdx = 0; parentIdx < data.length; parentIdx++) { let parent = data[parentIdx]; // if it's the parent - if (parent[this.idField] == parentId) - { + if (parent[this.idField] == parentId) { // create children array if not done yet if (!parent._children) parent._children = []; // if child is not included in children array, append the child - if (!parent._children.includes(child)) parent._children.push(child); + if (!parent._children.includes(child)) + parent._children.push(child); // parent found return true; } // search children for parents - else if (parent._children) this.appendChild(parent._children, child); + else if (parent._children) + this.appendChild(parent._children, child); } // parent not found @@ -823,38 +919,162 @@ export const CoreFilterCmpt = { if (!this.tableBuilt) { return {}; } else if (this.tabulator.options.locale) { - return this.tabulator.getLang().columns; + let result = this.tabulator.getLang().columns; + const columnFieldTitlePairs = this.tabulator + .getColumns() + .map((column) => { + const definition = column.getDefinition(); + return [definition.field, definition.title]; + }); + columnFieldTitlePairs.forEach((fieldTitlePair) => { + if ( + fieldTitlePair[0]?.length && + fieldTitlePair[1]?.length && + !(fieldTitlePair[0] in result) + ) { + result[fieldTitlePair[0]] = fieldTitlePair[1]; + } + }); + return result; } return this.tabulator.getColumns().reduce((res, col) => { res[col.getField()] = col.getDefinition().title; return res; }, {}); }, + afterTablePresetApplied(preset) { + this.selectedFields = [...preset.displayedColumns]; + this.appliedPreset = { ...preset }; + this.hasPresetBeenModified = false; + }, + updateTablePresetsSubCollapsibles() { + if ( + !this.$refs.tablePresets.$el + .getAttribute("class") + .split(" ") + .includes("show") + ) { + this.$refs.tablePresets?.hidePresetInfo(); + this.$refs.tablePresets?.hideNewPresetForm(); + } + }, + hideColumn(column) { + this.tabulator.hideColumn(column); + this.checkAgainstAppliedPreset(); + }, + showColumn(column) { + this.tabulator.showColumn(column); + this.checkAgainstAppliedPreset(); + }, + checkAgainstAppliedPreset() { + if (!this.appliedPreset) return; + + let hasPresetBeenModified = false; + + const displayedColumns = this.tabulator.columnManager.columns + .filter((column) => column.field !== "collapse") + .filter((column) => column.visible) + .map((column) => column.field); + + const displayedColumnsLength = displayedColumns.length; + const presetDisplayedColumnsLength = + this.appliedPreset.displayedColumns?.length ?? 0; + + if ( + displayedColumns.length !== + (this.appliedPreset.displayedColumns?.length ?? 0) + ) { + hasPresetBeenModified = true; + } + + displayedColumns.forEach((displayedColumn, index) => { + if ( + this.appliedPreset.displayedColumns[index] !== + displayedColumn + ) { + hasPresetBeenModified = true; + } + }); + + const unparsedHeaderFilters = + this.tabulator.modules.filter.headerFilters; + const headerFilterFieldValuePairs = Object.entries( + unparsedHeaderFilters, + ).map(([columnField, filter]) => [columnField, filter.value]); + const headerFiltersFieldValuePairsExcludingHiddenColumns = + headerFilterFieldValuePairs.filter( + ([columnField, filterValue]) => { + return displayedColumns.includes(columnField); + }, + ); + const headerFilters = Object.fromEntries( + headerFiltersFieldValuePairsExcludingHiddenColumns, + ); + const headerFilterKeys = Object.keys(headerFilters); + const appliedHeaderFilterKeys = this.appliedPreset.headerFilters + ? Object.keys(this.appliedPreset.headerFilters) + : []; + + if (headerFilterKeys.length !== appliedHeaderFilterKeys.length) { + hasPresetBeenModified = true; + } + + headerFilterKeys.forEach((key) => { + if ( + !(key in this.appliedPreset.headerFilters) || + this.appliedPreset.headerFilters[key] !== headerFilters[key] + ) { + hasPresetBeenModified = true; + } + }); + + const activeSort = this.tabulator.getSorters()[0]; + const sort = activeSort + ? { + column: activeSort.field, + direction: activeSort.dir, + } + : null; + if ( + sort?.column !== this.appliedPreset.sort?.column || + sort?.direction !== this.appliedPreset.sort?.direction + ) { + hasPresetBeenModified = true; + } + + this.hasPresetBeenModified = hasPresetBeenModified; + }, }, beforeCreate() { if (!this.tableOnly == !this.filterType) - alert('You can not have a filter-type in table-only mode!'); + alert("You can not have a filter-type in table-only mode!"); }, created() { - if (this.sideMenu && (!this.$props.onNwNewEntry || !(this.$props.onNwNewEntry instanceof Function))) + if ( + this.sideMenu && + (!this.$props.onNwNewEntry || + !(this.$props.onNwNewEntry instanceof Function)) + ) alert('"nwNewEntry" listener is mandatory when sideMenu is true'); this.uuid = _uuid++; - this.$emit('uuidDefined', this.uuid) + this.$emit("uuidDefined", this.uuid); Tabulator.registerModule(InternalToExternalEventBroadcastModule); Tabulator.registerModule(MenuExtensionModule); Tabulator.registerModule(ResponsiveLayoutExtensionModule); + Tabulator.registerModule(TablePresetsModule); }, mounted() { this.initTabulator().then(() => { if (!this.tableOnly) { - this.selectedFilter = window.location.hash ? window.location.hash.slice(1) : null; + this.selectedFilter = window.location.hash + ? window.location.hash.slice(1) + : null; this.render(this.fetchResult); } }); - }, - template: ` + template: /*html*/ ` + + + + + {{ appliedPreset.name }} + {{ " [" + $p.t("tabulator_presets/modified") + "]" }} + +
+ + @@ -943,5 +1189,5 @@ export const CoreFilterCmpt = {
- ` + `, }; diff --git a/public/js/components/filter/Filter/TablePresets.js b/public/js/components/filter/Filter/TablePresets.js new file mode 100644 index 000000000..a8a0c8e4c --- /dev/null +++ b/public/js/components/filter/Filter/TablePresets.js @@ -0,0 +1,551 @@ +/** + * Copyright (C) 2026 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import ApiTabulatorPresets from "../../../api/factory/tabulatorPresets.js"; + +/** + * + */ +export default { + name: "TablePresets", + props: { + presetsId: { type: String }, + tabulator: { type: Object }, + generalPresets: { type: Array, default: [] }, + }, + emits: ["tablePresetApplied"], + data: function () { + return { + customUserPresets: [], + presetInfo: null, + newPreset: null, + newPresetName: "", + presetIndexBeingApplied: null, + }; + }, + computed: { + allPresets() { + return this.$props.generalPresets.concat(this.customUserPresets); + }, + presetInfoFormattedStrings() { + if (!this.presetInfo) return null; + + const columnTitles = this.getColumnTitles(); + + let displayedColumns = + this.presetInfo.displayedColumns?.map( + (columnField) => columnTitles[columnField] ?? "", + ) ?? []; + displayedColumns = displayedColumns.filter( + (string) => string.length, + ); + const displayedColumnsString = displayedColumns.length + ? this.$p.t("tabulator_presets/displayed_columns") + + " (" + + this.$p.t("tabulator_presets/in_order") + + "): " + + displayedColumns.join(", ") + : ""; + + let headerFilters = this.presetInfo.headerFilters + ? Object.keys(this.presetInfo.headerFilters).map((column) => + columnTitles[column] + ? columnTitles[column] + + '["' + + this.presetInfo.headerFilters[column] + + '"]' + : "", + ) + : []; + headerFilters = headerFilters.filter((string) => string.length); + const headerFiltersString = headerFilters.length + ? this.$p.t("tabulator_presets/column_header_filters") + + ": " + + headerFilters.join(", ") + : ""; + + const sortColumn = this.presetInfo.sort?.column + ? columnTitles[this.presetInfo.sort.column] + : null; + const sortDirection = + this.presetInfo.sort?.direction === "desc" + ? this.$p.t("tabulator_presets/desc") + : this.$p.t("tabulator_presets/asc"); + const sortString = sortColumn + ? this.$p.t("tabulator_presets/sort") + + ": " + + sortColumn + + " (" + + sortDirection + + ")" + : ""; + + return { + displayedColumns: displayedColumnsString, + headerFilters: headerFiltersString, + sort: sortString, + }; + }, + newPresetFormattedStrings() { + if (!this.newPreset) return null; + + const columnTitles = this.getColumnTitles(); + + const displayedColumns = this.newPreset.displayedColumns.map( + (columnField) => columnTitles[columnField], + ); + const displayedColumnsString = displayedColumns.length + ? this.$p.t("tabulator_presets/displayed_columns") + + " (" + + this.$p.t("tabulator_presets/in_order") + + "): " + + displayedColumns.join(", ") + : ""; + + const headerFilters = Object.keys(this.newPreset.headerFilters).map( + (column) => + columnTitles[column] + + '["' + + this.newPreset.headerFilters[column] + + '"]', + ); + const headerFiltersString = headerFilters.length + ? this.$p.t("tabulator_presets/column_header_filters") + + ": " + + headerFilters.join(", ") + : ""; + + const sortColumn = this.newPreset.sort?.column + ? columnTitles[this.newPreset.sort.column] + : null; + const sortDirection = + this.newPreset.sort?.direction === "desc" + ? this.$p.t("tabulator_presets/desc") + : this.$p.t("tabulator_presets/asc"); + const sortString = sortColumn + ? this.$p.t("tabulator_presets/sort") + + ": " + + sortColumn + + " (" + + sortDirection + + ")" + : ""; + + return { + displayedColumns: displayedColumnsString, + headerFilters: headerFiltersString, + sort: sortString, + }; + }, + }, + watch: { + allPresets: { + handler() { + this.hidePresetInfo(); + this.hideNewPresetForm(); + }, + deep: true, + }, + }, + methods: { + getPresetIndex() { + return this.presetIndexBeingApplied; + }, + async showPresetInfo(preset) { + let shouldUpdatePresetInfo; + if (!this.presetInfo) { + shouldUpdatePresetInfo = true; + } else if (preset.id) { + shouldUpdatePresetInfo = preset.id !== this.presetInfo.id; + } else { + shouldUpdatePresetInfo = preset.name !== this.presetInfo.name; + } + + if (shouldUpdatePresetInfo) { + this.presetInfo = { ...preset }; + } + + this.hideNewPresetForm(); + + await this.$nextTick(); + + if ( + !this.$refs.presetInfoCollapsible + .getAttribute("class") + .split(" ") + .includes("show") + ) { + this.togglePresetInfoCollapsible(); + } + }, + async hidePresetInfo() { + if ( + this.$refs.presetInfoCollapsible + .getAttribute("class") + .split(" ") + .includes("show") + ) { + this.togglePresetInfoCollapsible(); + } + await this.$nextTick(); + this.presetInfo = null; + }, + togglePresetInfoCollapsible() { + new bootstrap.Collapse( + "#presetInfoCollapsible_" + this.$props.presetsId, + { toggle: true }, + ); + }, + async showNewPresetForm() { + this.generateNewPreset(); + + this.hidePresetInfo(); + + await this.$nextTick(); + + if ( + !this.$refs.newPresetFormCollapsible + .getAttribute("class") + .split(" ") + .includes("show") + ) { + this.toggleNewPresetFormCollapsible(); + } + }, + generateNewPreset() { + const columns = this.tabulator.columnManager.columns.filter( + (column) => column.field !== "collapse", + ); + + const displayedColumns = columns + .filter((column) => column.visible) + .map((column) => column.field); + + const unparsedHeaderFilters = + this.tabulator.modules.filter.headerFilters; + const headerFilterFieldValuePairs = Object.entries( + unparsedHeaderFilters, + ).map(([columnField, filter]) => [columnField, filter.value]); + const headerFiltersFieldValuePairsExcludingHiddenColumns = + headerFilterFieldValuePairs.filter( + ([columnField, filterValue]) => { + return displayedColumns.includes(columnField); + }, + ); + const headerFilters = Object.fromEntries( + headerFiltersFieldValuePairsExcludingHiddenColumns, + ); + + const activeSort = this.tabulator.getSorters()[0]; + const sort = activeSort + ? { + column: activeSort.field, + direction: activeSort.dir, + } + : null; + + this.newPreset = { + displayedColumns, + headerFilters, + sort, + }; + }, + hideNewPresetForm() { + if ( + this.$refs.newPresetFormCollapsible + .getAttribute("class") + .split(" ") + .includes("show") + ) { + this.toggleNewPresetFormCollapsible(); + } + }, + toggleNewPresetFormCollapsible() { + this.newPresetName = ""; + new bootstrap.Collapse( + "#newPresetFormCollapsible_" + this.$props.presetsId, + { toggle: true }, + ); + }, + async createPreset() { + if (!this.newPresetName?.length) return; + + const presetCreationResponse = await this.$api.call( + ApiTabulatorPresets.createTabulatorPreset( + this.$props.presetsId, + this.newPresetName, + this.newPreset, + ), + ); + + if (presetCreationResponse.meta.status === "success") { + this.fetchCustomUserTabulatorPresets(); + this.hideNewPresetForm(); + } + }, + async deletePreset(preset) { + if (!preset.id) return; + + if ( + window.confirm( + this.$p + .t("tabulator_presets/preset_deletion_confirmation") + .replace("(((presetName)))", preset.name), + ) + ) { + const presetDeletionResponse = await this.$api.call( + ApiTabulatorPresets.deleteTabulatorPreset(preset.id), + ); + + if (presetDeletionResponse.meta.status === "success") { + this.fetchCustomUserTabulatorPresets(); + } + } + }, + syncPresetWithConfig(preset) { + const validColumns = this.$props.tabulator.getColumnDefinitions(); + let isUpdateNecessary = false; + + if (preset.displayedColumns) { + const initialNumberOfDisplayedColumns = + preset.displayedColumns.length; + preset.displayedColumns = preset.displayedColumns.filter( + (columnField) => { + return validColumns.some( + (column) => column.field === columnField, + ); + }, + ); + const updatedNumberOfDisplayedColumns = + preset.displayedColumns.length; + + isUpdateNecessary = + updatedNumberOfDisplayedColumns < + initialNumberOfDisplayedColumns; + } + + if (preset.headerFilters) { + const initialNumberOfHeaderFilters = Object.keys( + preset.headerFilters, + ).length; + Object.keys(preset.headerFilters).forEach((columnField) => { + if ( + !validColumns.some( + (column) => + column.field === columnField && + column.headerFilter, + ) || + !preset.displayedColumns.includes(columnField) + ) { + delete preset.headerFilters[columnField]; + } + }); + const updatedNumberOfHeaderFilters = Object.keys( + preset.headerFilters, + ).length; + + isUpdateNecessary = + isUpdateNecessary || + updatedNumberOfHeaderFilters < initialNumberOfHeaderFilters; + } + + if (preset.sort) { + if ( + !validColumns.some( + (column) => + column.field === preset.sort.column && + column.headerSort, + ) + ) { + preset.sort = null; + isUpdateNecessary = true; + } + } + + if (isUpdateNecessary) { + this.updatePreset(preset); + } + + return preset; + }, + async updatePreset(preset) { + if (!preset.id) return; + + const tabulatorPresetUpdateResponse = await this.$api.call( + ApiTabulatorPresets.updateTabulatorPreset(preset.id, { + displayedColumns: preset.displayedColumns, + headerFilters: preset.headerFilters, + sort: preset.sort, + }), + ); + + if (tabulatorPresetUpdateResponse.meta.status === "success") { + this.fetchCustomUserTabulatorPresets(); + } + }, + async applyPreset(preset, index) { + this.presetIndexBeingApplied = index; + + // workaround for updating DOM and displaying preset-loading spinner + // conventional use of $nextTick ineffective + setTimeout(() => { + preset = this.syncPresetWithConfig(preset); + window.localStorage["tabulatorPreset-" + this.$props.presetsId] = + preset.id ? "custom:" + preset.id : "general:" + preset.name; + + this.$props.tabulator.modules.tablePresets.applyPreset(preset); + + this.$emit("tablePresetApplied", { preset }); + + this.presetIndexBeingApplied = null; + }, 50); + }, + async fetchCustomUserTabulatorPresets() { + let tabulatorPresetsResponse = await this.$api.call( + ApiTabulatorPresets.getTabulatorPresets(this.$props.presetsId), + ); + + if (tabulatorPresetsResponse.meta.status === "success") { + this.customUserPresets = tabulatorPresetsResponse.data.map( + (presetInfo) => { + return { + id: presetInfo.preset_id, + name: presetInfo.preset_name, + ...JSON.parse(presetInfo.preset_json), + }; + }, + ); + } + }, + getColumnTitles() { + const columnLocalizations = + this.$props.tabulator.getLocale() !== "default" + ? this.$props.tabulator.getLang().columns + : {}; + const columnFieldTitlePairs = this.$props.tabulator + .getColumnDefinitions() + .map((column) => [ + column.field, + columnLocalizations[column.field] ?? column.title, + ]); + return Object.fromEntries(columnFieldTitlePairs); + }, + applyStoredPreset() { + let storedPreset = + window.localStorage["tabulatorPreset-" + this.$props.presetsId]; + if (!storedPreset) return; + + let preset = null; + + if (storedPreset.search("custom:") === 0) { + const customPresetId = parseInt(storedPreset.slice(7)); + preset = this.customUserPresets.find( + (customPreset) => customPreset.id === customPresetId, + ); + } else if (storedPreset.search("general:") === 0) { + const generalPresetName = storedPreset.slice(8); + preset = this.$props.generalPresets.find( + (generalPreset) => generalPreset.name === generalPresetName, + ); + } + + if (!preset) return; + + this.applyPreset(preset, null); + }, + }, + async created() { + await this.$p.loadCategory(["tabulator_presets", "global"]); + await this.fetchCustomUserTabulatorPresets(); + this.applyStoredPreset(); + }, + template: /*html*/ ` +
+
+
{{ $p.t("tabulator_presets/table_presets") }}
+
+
+
+
+ {{ preset.name }} +
+
+ Loading... +
+
+
+
+
+ + +
+
+
+
+
+ + {{ $p.t("tabulator_presets/save_current_config") }} +
+
+
+
+
+
+
+
+ {{ presetInfo?.name }} + +
+ {{ presetInfoFormattedStrings.displayedColumns }} + {{ presetInfoFormattedStrings.headerFilters }} + {{ presetInfoFormattedStrings.sort }} +
+
+
+
+
+
+
+
+ {{ $p.t("tabulator_presets/save_current_config") }} + +
+ {{ newPresetFormattedStrings.displayedColumns }} + {{ newPresetFormattedStrings.headerFilters }} + {{ newPresetFormattedStrings.sort }} +
+ +
+ {{ $p.t('global/speichern') }} +
+
+
+
+
+
+
+
+ `, +}; diff --git a/public/js/components/filter/Table/Download.js b/public/js/components/filter/Table/Download.js index 057fdad5e..1480a00ba 100644 --- a/public/js/components/filter/Table/Download.js +++ b/public/js/components/filter/Table/Download.js @@ -202,7 +202,7 @@ export default { return true; }, download(config) { - this.tabulator.download(config.formatter, config.file, config.options) + this.tabulator.download(config.formatter, config.file, config.options, config.rowRange) } }, template: ` diff --git a/public/js/components/horizontalsplit/horizontalsplit.js b/public/js/components/horizontalsplit/horizontalsplit.js new file mode 100644 index 000000000..2d91cc6a9 --- /dev/null +++ b/public/js/components/horizontalsplit/horizontalsplit.js @@ -0,0 +1,147 @@ +export default { + name: 'HorizontalSplit', + props: { + defaultRatio: { + type: Array, + default: () => [50, 50] + } + }, + data: function () { + return { + availWidth: 0, + leftwidth: 0, + rightwidth: 0, + mousePosX: 0, + resize: false, + hsplitterOffset: 0, + selfOffsetLeft: 0 + }; + }, + template: ` +
+
+ +

Left Panel

+
+
+
+
+ + + + + + + + + +
+
+
+ +

+ +

+
+ `, + mounted: function () { + this.calcWidths(); + this.trackHorizontalSplitterOffsetLeft(); + window.addEventListener('resize', this.calcWidths); + }, + updated: function () { + this.trackHorizontalSplitterOffsetLeft(); + }, + beforeDestroy: function () { + window.removeEventListener('resize', this.calcWidths); + }, + methods: { + calcWidths: function () { + var oldavailWidth = this.availWidth; + this.selfOffsetLeft = this.$refs.horizontalsplit.offsetLeft; + this.availWidth = this.$refs.horizontalsplit.offsetWidth - this.$refs.hsplitter.offsetWidth; + + if ((this.leftwidth === 0 && this.rightwidth === 0) || oldavailWidth === 0) { + this.leftwidth = Math.floor(this.availWidth * (this.defaultRatio[0] / 100)); + } else { + this.leftwidth = Math.floor(((this.leftwidth * 100) / oldavailWidth) / 100 * this.availWidth); + } + this.rightwidth = this.availWidth - this.leftwidth; + }, + collapseLeft: function () { + this.calcWidths(); + this.leftwidth = 0; + this.rightwidth = this.availWidth; + }, + collapseRight: function () { + this.calcWidths(); + this.leftwidth = this.availWidth; + this.rightwidth = 0; + }, + showBoth: function () { + this.leftwidth = Math.floor(this.availWidth * (this.defaultRatio[0] / 100)); + this.rightwidth = this.availWidth - this.leftwidth; + }, + isCollapsed: function () { + if (this.leftwidth === 0) { + return 'left'; + } else if (this.rightwidth === 0) { + return 'right'; + } else { + return false; + } + }, + dragStart: function (e) { + e.preventDefault(); + e.stopPropagation(); + window.addEventListener('mouseup', this.dragEnd); + window.addEventListener('mousemove', this.drag); + this.resize = true; + this.mousePosX = e.clientX; + }, + drag: function (e) { + if (!this.resize) { + return; + } + e.preventDefault(); + e.stopPropagation(); + var offsetX = e.clientX - this.mousePosX; + this.leftwidth = this.leftwidth + offsetX; + if (this.leftwidth < 0) { + this.leftwidth = 0; + } + if (this.leftwidth > this.availWidth) { + this.leftwidth = this.availWidth; + } + this.rightwidth = this.availWidth - this.leftwidth; + this.mousePosX = e.clientX; + }, + dragEnd: function (e) { + e.preventDefault(); + e.stopPropagation(); + window.removeEventListener('mousemove', this.drag); + window.removeEventListener('mouseup', this.dragEnd); + this.resize = false; + this.mousePosX = e.clientX; + }, + trackHorizontalSplitterOffsetLeft: function () { + this.hsplitterOffset = this.$refs.hsplitter.offsetLeft; + } + }, + computed: { + leftOrRightClass: function () { + return ((this.hsplitterOffset - this.selfOffsetLeft) <= Math.floor(this.availWidth / 2)) + ? 'left' + : 'right'; + }, + leftwidthcss: function () { + return this.leftwidth + 'px'; + }, + rightwidthcss: function () { + return this.rightwidth + 'px'; + } + } +}; \ No newline at end of file diff --git a/public/js/components/verticalsplit/verticalsplit.js b/public/js/components/verticalsplit/verticalsplit.js index e712d1ecb..91561a1d3 100644 --- a/public/js/components/verticalsplit/verticalsplit.js +++ b/public/js/components/verticalsplit/verticalsplit.js @@ -1,5 +1,11 @@ export default { name: 'VerticalSplit', + props: { + defaultRatio: { + type: Array, + default: () => [50, 50] + } + }, data: function() { return { availHeight: 0, @@ -50,17 +56,22 @@ export default { updated: function() { this.trackVerticalSplitterOffsetTop(); }, + beforeDestroy: function () { + window.removeEventListener('resize', this.calcHeights); + }, methods: { calcHeights: function() { var windowheight = window.innerHeight; var oldavailHeight = this.availHeight; this.selfOffsetTop = this.$refs.verticalsplit.offsetTop; this.availHeight = windowheight - this.selfOffsetTop - this.$refs.vsplitter.offsetHeight; + if( (this.topheight === 0 && this.bottomheight === 0) || oldavailHeight === 0 ) { - this.topheight = Math.floor(this.availHeight/2); + this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100)); } else { this.topheight = Math.floor( ((((this.topheight * 100) / oldavailHeight) / 100) * this.availHeight) ); } + this.bottomheight = this.availHeight - this.topheight; }, collapseTop: function() { @@ -74,8 +85,8 @@ export default { this.bottomheight = 0; }, showBoth: function() { - this.topheight = Math.floor(this.availHeight/2); - this.bottomheight = Math.floor(this.availHeight/2); + this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100)); + this.bottomheight = this.availHeight - this.topheight }, isCollapsed: function() { if( this.topheight === 0 ) { diff --git a/public/js/directives/draggable.js b/public/js/directives/draggable.js index d534b9703..7ce93b428 100644 --- a/public/js/directives/draggable.js +++ b/public/js/directives/draggable.js @@ -28,7 +28,7 @@ export default { // i.e: for dragging multiple elements // otherwise set draggable attribute if (!binding.modifiers.capture) { - el.draggable = true; + updateAttributes(el); } const bcc = new BroadcastChannel('fhc-dnd'); @@ -78,11 +78,21 @@ export default { el.fhcDraggableCleanup = () => { el.removeEventListener('dragstart', onStart, binding.modifiers.capture); el.removeEventListener('dragend', onEnd, true); + if (el.dataset.fhcDraggableValue) { + delete el.dataset.fhcDraggableValue; + } + if (el.dataset.fhcEffectAllowed) { + delete el.dataset.fhcEffectAllowed; + } + updateAttributes(el); }; }, updated(el, binding) { updateValue(el, binding.value); updateEffectAllowed(el, binding.arg); + if (!binding.modifiers.capture) { + updateAttributes(el); + } }, beforeUnmount(el) { el.fhcDraggableCleanup(); @@ -92,7 +102,8 @@ export default { // Helper functions function updateValue(el, value) { - value = convertToValidDragObject(value); + if (value) + value = convertToValidDragObject(value); if (value) { el.dataset.fhcDraggableValue = JSON.stringify(value); } else if (el.dataset.fhcDraggableValue) { @@ -106,3 +117,9 @@ function updateEffectAllowed(el, effectAllowed) { delete el.dataset.fhcEffectAllowed; } } +function updateAttributes(el) { + if (el.dataset.fhcEffectAllowed && el.dataset.fhcDraggableValue) + el.draggable = true; + else if (el.hasAttribute('draggable')) + el.removeAttribute('draggable'); +} diff --git a/public/js/directives/drop.js b/public/js/directives/drop.js index 4e14c5296..63ea4826e 100644 --- a/public/js/directives/drop.js +++ b/public/js/directives/drop.js @@ -16,6 +16,26 @@ const EFFECTS = [ export default { mounted(el, binding) { + if (!binding.arg) { + binding.arg = 'none'; + } else if (typeof binding.arg === 'object' && !Array.isArray(binding.arg)) { + // NOTE(chris): allow object as arg and map it to arg and + // modifiers to allow dynamic modifiers. + if (binding.arg.allowed) { + binding.modifiers = binding.arg.allowed.reduce((a, c) => { + a[c] = true; + return a; + }, {}); + } + if (!binding.arg.effect) + binding.arg.effect = 'none'; + + if (binding.arg.strict) + binding.arg = binding.arg.effect + '-strict'; + else + binding.arg = binding.arg.effect; + } + const allowedTypes = Object.keys(binding.modifiers); allowedTypes.forEach(type => { if (type.substr(-11) == '-collection') { diff --git a/public/js/helpers/DocumentHelper.js b/public/js/helpers/DocumentHelper.js new file mode 100644 index 000000000..c37c632d5 --- /dev/null +++ b/public/js/helpers/DocumentHelper.js @@ -0,0 +1,44 @@ +export function highlightGesamtnote(zeugnisTable, teacherTable) +{ + let zeugnisData = zeugnisTable.getData(); + + let studentsWithNote = new Set(); + + zeugnisData.forEach(row => { + let student = row.uid; + let lv = row.lehrveranstaltung_id; + let note = row.note; + + if (!student || !lv) + return; + + if (note == null || note === "") + return; + + let key = `${student}_${lv}`; + studentsWithNote.add(key); + }); + + teacherTable.deselectRow(); + + teacherTable.getRows().forEach(row => { + let data = row.getData(); + + let student = data.student_uid; + let lv = data.lehrveranstaltung_id; + let note = data.note; + + if (!student || !lv) + return; + + if (note == null || note === "") + return; + + let key = `${student}_${lv}`; + + if (!studentsWithNote.has(key)) + { + row.select(); + } + }); +} \ No newline at end of file diff --git a/public/js/helpers/DragAndDrop.js b/public/js/helpers/DragAndDrop.js index f98697eb2..e08740344 100644 --- a/public/js/helpers/DragAndDrop.js +++ b/public/js/helpers/DragAndDrop.js @@ -30,6 +30,10 @@ const TYPE_DEFINITION = { prestudent: { id: "prestudent_id", dragIcon: "fa-solid fa-user-graduate text-muted" + }, + verband: { + id: "gruppe_id", + dragIcon: "fa-solid fa-user-group text-muted" } // TODO: IMPLEMENT OTHER TYPES }; @@ -116,12 +120,12 @@ function isValidTransferData(event, allowedTypes, strict) { function getTransferData(event, strict) { const result = []; - for (const type of event.dataTransfer.types) { - if (type.substr(0, 16) != 'application/fhc-') { - if (strict) - return null; - continue; - } + const dataTypes = [...event.dataTransfer.types].filter(type => type.startsWith('application/fhc-')); + + if (!dataTypes.length) + return null; + + for (const type of dataTypes) { let base_type = type.substr(16); let collection = false; if (base_type.substr(-11) == '-collection') { @@ -266,18 +270,16 @@ function eventHasTypes(event, allowedTypes, strict) { allowedTypes = VALID_TYPES; allowedTypes = allowedTypes.map(type => 'application/fhc-' + type); - const dataTypes = [...event.dataTransfer.types]; - + const dataTypes = [...event.dataTransfer.types].filter(type => type.startsWith('application/fhc-')); // NOTE(chris): if dragging across browsers the dataTransfer object is // set to a default one without data. Since we do not support dragging // across browsers (yet) we return false which will disallow dropping. if (!dataTypes.length) return false; - + if (!strict) - return allowedTypes.some(type => [...event.dataTransfer.types].includes(type)); - - return [...event.dataTransfer.types].every(type => allowedTypes.includes(type)); + return allowedTypes.some(type => dataTypes.includes(type)); + return dataTypes.every(type => allowedTypes.includes(type)); } function bindDragEnterLeave(el, onEnter, onLeave) { diff --git a/public/js/helpers/EmailHelpers.js b/public/js/helpers/EmailHelpers.js index 4ffdc8122..d4be2dff1 100644 --- a/public/js/helpers/EmailHelpers.js +++ b/public/js/helpers/EmailHelpers.js @@ -1,5 +1,6 @@ -export async function splitMailsHelper(mails, event, subject, body, alertPluginRef, phrasenPluginRef) { +export async function splitMailsHelper(mails, event, subject, body, alertPluginRef, phrasenPluginRef, uidCC) { await phrasenPluginRef.loadCategory('ui'); + let splititem = ","; let maillist = mails.join(splititem); let useBcc = event?.ctrlKey || event?.metaKey; @@ -46,7 +47,10 @@ export async function splitMailsHelper(mails, event, subject, body, alertPluginR } // construct the clean mailLink - let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`; + let mailadressUid = (uidCC && typeof uidCC === 'string') + ? uidCC + '@' + FHC_JS_DATA_STORAGE_OBJECT.domain + : 'noreply@' + FHC_JS_DATA_STORAGE_OBJECT.domain; + let mailLink = useBcc ? `mailto:${mailadressUid}?cc=${mailadressUid}&bcc=${mailto}` : `mailto:${mailto}`; if (queryString) { // If using BCC, the string already has a '?', so append with '&'. Otherwise, start with '?' mailLink += useBcc ? `&${queryString}` : `?${queryString}`; @@ -63,4 +67,79 @@ export async function splitMailsHelper(mails, event, subject, body, alertPluginR } } } -} \ No newline at end of file +} + + +/** + * just splits the list of mails + * + * @param mails emailadresses + * @param subject subject + * @param useBcc useBcc + * @returns array of links for splitted mails + */ +export function splitMailLinks(mails, subject = "", useBcc = false, uidCC= "", maxLength = 2024) { + if (!Array.isArray(mails) || mails.length === 0) { + return []; + } + + const separator = ","; + const encodedSubject = subject ? encodeURIComponent(subject) : ""; + + // reserve space for the subject parameter + const subjectLength = encodedSubject + ? `?subject=${encodedSubject}`.length + : 0; + + const limit = maxLength - subjectLength; + + const links = []; + let currentRecipients = []; + + for (const mail of mails) { + if (!mail) continue; + + const testRecipients = [...currentRecipients, mail]; + const recipientString = testRecipients.join(separator); + + if (recipientString.length > limit && currentRecipients.length > 0) { + + links.push(createMailto( + currentRecipients.join(separator), + encodedSubject, + useBcc, + uidCC + )); + + currentRecipients = [mail]; + } + else { + currentRecipients.push(mail); + } + } + + if (currentRecipients.length > 0) { + links.push(createMailto( + currentRecipients.join(separator), + encodedSubject, + useBcc, + uidCC + )); + } + + return links; +} + +function createMailto(recipients, encodedSubject, useBcc, uidCC) { + let mailadressUid = uidCC + '@technikum-wien.at'; + + let link = useBcc + ? `mailto:${encodeURIComponent(mailadressUid)}?cc=${encodeURIComponent(mailadressUid)}&bcc=${encodeURIComponent(recipients)}` + : `mailto:${encodeURIComponent(recipients)}`; + + if (encodedSubject) { + link += `${useBcc ? "&" : "?"}subject=${encodedSubject}`; + } + + return link; +} diff --git a/public/js/helpers/TagHelper.js b/public/js/helpers/TagHelper.js index 9282aa167..826e4126a 100644 --- a/public/js/helpers/TagHelper.js +++ b/public/js/helpers/TagHelper.js @@ -15,7 +15,12 @@ export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags") let tags; try { - tags = JSON.parse(rowData[tagsKey] || "[]"); + let initialTags = rowData[tagsKey] || "[]"; + if (typeof initialTags === 'string') { + tags = JSON.parse(initialTags); + } else { + tags = initialTags; + } } catch (e) { tags = []; } @@ -28,8 +33,14 @@ export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags") let newTag = { ...addedTag, id: tag.id }; - tags.unshift(newTag); + tags.push(newTag); + tags.sort((a, b) => { + let adone = a.done ? 1 : 0; + let bdone = b.done ? 1 : 0; + if (adone !== bdone) return adone - bdone; + return a.prioritaet - b.prioritaet; + }); rowData[tagsKey] = JSON.stringify(tags); updated = true; }); @@ -53,7 +64,12 @@ export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags']) let tags; try { - tags = JSON.parse(rowData[key] || "[]"); + let initialTags = rowData[key] || "[]"; + if (typeof initialTags === 'string') { + tags = JSON.parse(initialTags); + } else { + tags = initialTags; + } } catch (e) { tags = []; } @@ -62,7 +78,6 @@ export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags']) return; let filtered = tags.filter(tag => tag?.id !== deletedTag); - if (filtered.length !== tags.length) { updates[key] = JSON.stringify(filtered); @@ -95,7 +110,12 @@ export function updateTagInTable(updatedTag, rows, fields = ['tags']) let fieldData; try { - fieldData = JSON.parse(rowData[field] || "[]"); + let initialTags = rowData[field] || "[]"; + if (typeof initialTags === 'string') { + fieldData = JSON.parse(initialTags); + } else { + fieldData = initialTags; + } } catch (e) { return; } @@ -108,6 +128,14 @@ export function updateTagInTable(updatedTag, rows, fields = ['tags']) if (index !== -1) { fieldData[index] = { ...updatedTag }; + fieldData.sort((a, b) => { + let adone = a.done ? 1 : 0; + let bdone = b.done ? 1 : 0; + + if (adone !== bdone) return adone - bdone; + return a.prioritaet - b.prioritaet; + }); + let updatedFieldData = JSON.stringify(fieldData); if (updatedFieldData !== rowData[field]) diff --git a/public/js/tabulator/customModules/ResponsiveLayoutExtensionModule.js b/public/js/tabulator/customModules/ResponsiveLayoutExtensionModule.js index c0e66cff0..038bef527 100644 --- a/public/js/tabulator/customModules/ResponsiveLayoutExtensionModule.js +++ b/public/js/tabulator/customModules/ResponsiveLayoutExtensionModule.js @@ -9,9 +9,13 @@ export default class ResponsiveLayoutExtensionModule extends ResponsiveLayout { } generateCollapsedRowData(row) { - let result = super.generateCollapsedRowData(row) - const isLocalizationEnabled = this.table.options.locale && this.table.options.locale !== "default"; - const columnHeadingTranslations = this.table?.getLang()?.columns; + let result = super.generateCollapsedRowData(row); + const isLocalizationEnabled = + this.table.options.locale && + this.table.options.locale !== "default"; + const columnHeadingTranslations = this.table?.initialized + ? this.table.getLang()?.columns + : null; if (isLocalizationEnabled && columnHeadingTranslations) { result = result.map((column) => { diff --git a/public/js/tabulator/customModules/TablePresetsModule.js b/public/js/tabulator/customModules/TablePresetsModule.js new file mode 100644 index 000000000..644514dc5 --- /dev/null +++ b/public/js/tabulator/customModules/TablePresetsModule.js @@ -0,0 +1,64 @@ +import Module from "../../../../vendor/olifolkerd/tabulator5/src/js/core/Module.js"; + +export default class TablePresetsModule extends Module { + static moduleName = "tablePresets"; + static moduleInitNumber = 1; + + constructor(table) { + super(table); + } + + applyPreset(preset) { + let columnsConfig = this.table.options.columns; + let reorderedColumnsConfig = []; + preset.displayedColumns.forEach((columnField) => { + let columnConfig = columnsConfig.find( + (column) => column.field === columnField, + ); + if (!columnConfig) return; + columnConfig.visible = true; + reorderedColumnsConfig.push(columnConfig); + }); + + let hiddenColumnsConfig = columnsConfig + .filter((column) => !preset.displayedColumns.includes(column.field)) + .map((column) => { + column.visible = false; + return column; + }); + this.table.setColumns( + reorderedColumnsConfig.concat(hiddenColumnsConfig), + ); + + this.table.clearHeaderFilter(); + if (preset.headerFilters) { + Object.entries(preset.headerFilters).forEach( + ([columnField, filterValue]) => { + this.table.setHeaderFilterValue(columnField, filterValue); + + // double checking if filter inputs are filled in due to issues with custom header filters + const headerFilterInputElement = + this.table.columnManager.columns + .find((column) => column.field === columnField) + ?.element.querySelector( + ".tabulator-header-filter input", + ); + if ( + headerFilterInputElement && + headerFilterInputElement.value !== filterValue + ) { + headerFilterInputElement.value = filterValue; + } + }, + ); + } + + this.table.clearSort(); + if (preset.sort?.column) { + this.table.setSort( + preset.sort.column, + preset.sort.direction ?? "asc", + ); + } + } +} diff --git a/public/js/tabulator/filter/filters.js b/public/js/tabulator/filter/filters.js new file mode 100644 index 000000000..36d48cc8f --- /dev/null +++ b/public/js/tabulator/filter/filters.js @@ -0,0 +1,54 @@ +function dateinput(headerValue, rowValue, rowData, config) { + if (!luxon) + return this.like(headerValue, rowValue, rowData, config); + + let inputFormat = config.inputFormat || 'iso'; + let outputFormat = config.outputFormat || 'dd.MM.yyyy'; + + let value; + if (inputFormat == 'iso') + value = luxon.DateTime.fromISO(rowValue); + else + value = luxon.DateTime.fromFormat(rowValue, inputFormat); + + let range = []; + range = headerValue.split(' - '); + if (range.length == 1) { + range = headerValue.split('-'); + } + if (range.length == 2) { + range = range.map(r => { + r = r.trim(); + if (r) { + let d = luxon.DateTime.fromFormat(r, outputFormat); + if (!d.isValid && inputFormat != 'iso' && inputFormat != outputFormat) + d = luxon.DateTime.fromFormat(r, inputFormat); + if (!d.isValid) + d = luxon.DateTime.fromISO(r); + if (d.isValid) + return d; + } + return null; + }); + if (!range[0] && !range[1]) + return true; + else if (!range[0]) + return this['<='](range[1], value, rowData, config); + else if (!range[1]) + return this['>='](range[0], value, rowData, config); + + let smaller = this['<='](range[1], value, rowData, config); + let bigger = this['>='](range[0], value, rowData, config); + return smaller && bigger; + } + + // fallback + return this.like(headerValue, value.toFormat(outputFormat), rowData, config); +} + +export { + dateinput +}; +export default { + dateinput +}; diff --git a/public/js/tabulator/filters/extendedHeaderFilter.js b/public/js/tabulator/filters/extendedHeaderFilter.js index 35b66dc1c..8626ad4f9 100644 --- a/public/js/tabulator/filters/extendedHeaderFilter.js +++ b/public/js/tabulator/filters/extendedHeaderFilter.js @@ -1,163 +1,617 @@ -function parseFilterExpression(expression) -{ - const collections = []; +let activeTagDropdownCleanup = null; +const TAG_FILTER_CONNECTORS = { + AND: { + key: "AND", + label: "AND", + operator: "&&", + isVisible: false, + }, + OR: { + key: "OR", + label: "OR", + operator: "||", + isVisible: true, + }, + NOT: { + key: "NOT", + label: "NOT", + operator: "!", + isVisible: true, + }, +}; +const TAG_FILTER_CONNECTOR_ORDER = [ + //TAG_FILTER_CONNECTORS.AND, + TAG_FILTER_CONNECTORS.OR, + TAG_FILTER_CONNECTORS.NOT, +]; - try { - const orParts = expression.split('||').map(part => part.trim()); - - orParts.forEach(part => { - - const andParts = part.split('&&').map(p => p.trim()); - - const collection = { positives: [], negatives: [] }; - - andParts.forEach(term => { - - const comparisonMatch = term.match(/^(<=|>=|<|>|=|!=)\s*(-?\d+(?:[.,]\d+)?)$/); - - if (comparisonMatch) - { - const operator = comparisonMatch[1]; - const numberStr = comparisonMatch[2].replace(',', '.'); - const number = parseFloat(numberStr); - collection.positives.push({ type: 'comparison', operator, number }); - } - else if (term.startsWith('!')) - { - const excludeTerm = term.substring(1).trim().replace(/\*/g, '.*'); - collection.negatives.push({ type: 'regex', regex: new RegExp(excludeTerm, 'i') }); - } - else - { - const includeTerm = term.replace(/\*/g, '.*'); - collection.positives.push({ type: 'regex', regex: new RegExp(includeTerm, 'i') }); - } - }); - collections.push(collection); - }); - } catch (e) {} - return collections; +function getConnectorLabel(connector, labels) { + return labels?.connectors?.[connector.key] || connector.label; } -export function extendedHeaderFilter(headerValue, rowValue, rowData, filterParams) -{ - const fields = Array.isArray(filterParams?.field) - ? filterParams.field - : [filterParams?.field]; +export function setTagHeaderFilterValue(value, tabulator, columnField = "tags") { + tabulator?.setHeaderFilterValue(columnField, value); - if (fields.length > 1 && rowData) - { - rowValue = fields - .map(f => rowData[f] ?? '') - .filter(Boolean) - .join(' '); - } - if (typeof headerValue === 'boolean') - { - return rowValue === headerValue; - } + const input = tabulator + ?.getColumn(columnField) + ?.getElement() + ?.querySelector(".tabulator-header-filter input"); - const collections = parseFilterExpression(headerValue); - - function matchValue(value) - { - try { - return collections.some(collection => { - - let positives = collection.positives.length === 0 || collection.positives.every(condition => { - - if (condition.type === 'comparison') - { - let value = parseFloat(rowValue); - if (isNaN(value)) return false; - - switch (condition.operator) { - case '<': - return value < condition.number; - case '>': - return value > condition.number; - case '<=': - return value <= condition.number; - case '>=': - return value >= condition.number; - case '=': - return value === condition.number; - case '!=': - return value !== condition.number; - default: - return false; - } - } - else if (condition.type === 'regex') - { - return condition.regex.test(rowValue); - } - return false; - }); - - let negatives = collection.negatives.every(condition => { - return !condition.regex.test(rowValue); - }); - - return positives && negatives; - }); - } catch (e) { - - } - } - - if (matchValue(rowValue)) - return true; - - if (rowData && filterParams) - { - const childrenField = filterParams?.children || '_children'; - const field = filterParams?.field; - - const children = rowData[childrenField]; - if (Array.isArray(children)) - { - for (let child of children) - { - let childValue = child[field]; - if (extendedHeaderFilter(headerValue, childValue, child, filterParams)) - { - return true; - } - } - } - } - - return false; + if (input) { + input.value = value ?? ""; + } } -export function tagHeaderFilter(headerValue, rowValue, rowData, filterParams) -{ - let data; +export function buildTagHeaderFilterExpression(selectedTagOptions) { + let expression = null; - try { - data = typeof rowValue === 'string' ? JSON.parse(rowValue) : rowValue; - } catch (error) { - return false; - } + selectedTagOptions.forEach(({ value, connector }) => { + const connectorDef = TAG_FILTER_CONNECTORS[connector]; + if (!connectorDef) return; - let combinedText; + if (connectorDef.key === TAG_FILTER_CONNECTORS.NOT.key) { + expression = expression + ? `${expression} ${TAG_FILTER_CONNECTORS.AND.operator} ${connectorDef.operator}${value}` + : `${connectorDef.operator}${value}`; + } else { + expression = expression + ? `${expression} ${connectorDef.operator} ${value}` + : value; + } + }); - if (Array.isArray(data)) - { - combinedText = data - .filter(item => item?.done !== true) - .map(item => `${item?.beschreibung} ${item?.notiz}`) - .join(' '); - } - else if (typeof data === 'object' && data !== null) - { - combinedText = data?.erledigt === false ? `${data?.beschreibung} ${data?.notiz}` : ''; - } - else - { - combinedText = String(data); - } + return expression; +} - return extendedHeaderFilter(headerValue, combinedText, rowData, filterParams) -} \ No newline at end of file +export function buildTagOptionsFromRows(rows, field = "tags") { + let parsedTags = [ + ...new Map( + getRawTagValuesFromRows(rows, field) + .filter((tag) => tag && tag.done !== true) + .sort((a, b) => { + return a.prioritaet - b.prioritaet; + }) + .map((tag) => { + return { + label: tag.beschreibung || tag.typ_kurzbz, + value: tag.beschreibung || tag.typ_kurzbz, + style: tag.style, + automatisiert: tag.automatisiert, + done: tag.done, + }; + }) + .map((obj) => [obj.value, obj]), + ).values(), + ]; + if (!Array.isArray(parsedTags)) return; + + return parsedTags; +} + +export function syncTagHeaderFilterOptions( + rows = [], + initialOptions = [], + selectedOptions = [], + field = "tags" +) { + const options = buildTagOptionsFromRows(rows, field) || []; + + if (Array.isArray(initialOptions)) { + initialOptions.splice(0, initialOptions.length, ...options); + } + + if (Array.isArray(selectedOptions)) { + const availableValues = new Set(options.map((option) => option.value)); + + for (let i = selectedOptions.length - 1; i >= 0; i--) { + if (!availableValues.has(selectedOptions[i].value)) { + selectedOptions.splice(i, 1); + } + } + } + + return options; +} + +export function syncSelectedTagOptionsWithHeaderFilters( + filters = [], + selectedOptions = [], + tagsEnabled = true, + field = "tags", +) { + if ( + !tagsEnabled || + !Array.isArray(selectedOptions) || + !selectedOptions.length + ) { + return false; + } + + const hasActiveTagFilter = filters.some( + (filter) => + filter.field === field && + filter.value !== "" && + filter.value !== null && + filter.value !== undefined, + ); + + if (hasActiveTagFilter) { + return false; + } + + selectedOptions.splice(0, selectedOptions.length); + + return true; +} + +export function getRawTagValuesFromRows(rows = [], field = "tags") { + let parsedTags = []; + + rows.forEach((row) => { + if (!row || (!row[field]?.length && !row._children?.length)) return; + + let newTags = row[field]?.length + ? [row[field]] + : getRawTagValuesFromRows(row._children, field); + + newTags = newTags + .map((newTag) => { + if (typeof newTag === "string") { + try { + newTag = JSON.parse(newTag); + } catch { + return null; + } + } + + return newTag; + }) + .filter((newTag) => newTag !== null); + + parsedTags = parsedTags.concat(newTags); + }); + + return parsedTags.flat(); +} + +export function customTagFilter(cell, onRendered, success, cancel, params) { + const container = document.createElement("div"); + container.style.width = "100%"; + + const input = document.createElement("input"); + input.style.width = "100%"; + input.type = "text"; + let dropdownMenu = null; + let suppressScrollCloseUntil = 0; + + Object.defineProperty(container, "value", { + get: () => input.value, + set: (value) => { + input.value = value ?? ""; + }, + }); + + const positionDropdown = () => { + if (!dropdownMenu) return; + + const rect = input.getBoundingClientRect(); + const width = Math.min(Math.max(rect.width, 360), window.innerWidth - 16); + const left = Math.min( + Math.max(rect.left, 8), + window.innerWidth - width - 8, + ); + + dropdownMenu.style.position = "fixed"; + dropdownMenu.style.left = `${left}px`; + dropdownMenu.style.top = `${rect.bottom + 2}px`; + dropdownMenu.style.width = `${width}px`; + dropdownMenu.style.maxHeight = "50vh"; + dropdownMenu.style.overflowY = "auto"; + dropdownMenu.style.zIndex = "2000"; + }; + + const closeDropdown = () => { + if (dropdownMenu) { + dropdownMenu.remove(); + dropdownMenu = null; + } + + if (activeTagDropdownCleanup === closeDropdown) { + activeTagDropdownCleanup = null; + } + + window.removeEventListener("scroll", handleScroll, true); + window.removeEventListener("resize", positionDropdown); + document.removeEventListener("mousedown", handleOutsideClick, true); + }; + + const suppressScrollClose = () => { + suppressScrollCloseUntil = Date.now() + 300; + }; + + const handleScroll = (event) => { + const target = event.target; + const isDropdownScroll = + target instanceof Node && dropdownMenu?.contains(target); + + if (isDropdownScroll || Date.now() < suppressScrollCloseUntil) { + return; + } + + closeDropdown(); + }; + + const handleOutsideClick = (event) => { + if ( + dropdownMenu?.contains(event.target) || + container.contains(event.target) + ) { + return; + } + + closeDropdown(); + }; + + const clearFilter = ({ closeDropdownAfterClear = true } = {}) => { + input.value = ""; + + if (Array.isArray(params.selectedOptions)) { + params.selectedOptions.splice(0, params.selectedOptions.length); + } + + success(""); + + if (closeDropdownAfterClear) { + closeDropdown(); + } + }; + + const openDropdown = () => { + if (activeTagDropdownCleanup) { + activeTagDropdownCleanup(); + } + + dropdownMenu = getRebuiltDropdown( + params.initialOptions, + params.selectedOptions, + params.labels, + clearFilter, + ); + dropdownMenu.addEventListener("mousedown", (event) => { + event.stopPropagation(); + }); + dropdownMenu.addEventListener("click", suppressScrollClose, true); + + document.body.appendChild(dropdownMenu); + activeTagDropdownCleanup = closeDropdown; + + positionDropdown(); + + window.addEventListener("scroll", handleScroll, true); + window.addEventListener("resize", positionDropdown); + document.addEventListener("mousedown", handleOutsideClick, true); + }; + + const stopHeaderFilterGesture = (event) => { + event.stopPropagation(); + }; + + input.addEventListener("pointerdown", stopHeaderFilterGesture); + input.addEventListener("mousedown", stopHeaderFilterGesture); + input.addEventListener("touchstart", stopHeaderFilterGesture); + input.addEventListener("click", stopHeaderFilterGesture); + input.addEventListener("input", () => { + success(input.value); + }); + input.addEventListener("change", () => { + success(input.value); + }); + input.addEventListener("keydown", (event) => { + if (event.key !== "Escape") return; + + event.preventDefault(); + event.stopPropagation(); + + clearFilter({ closeDropdownAfterClear: false }); + + openDropdown(); + }); + input.addEventListener("focus", (e) => { + e.stopPropagation(); + openDropdown(); + }); + + container.appendChild(input); + + return container; +} + +function getRebuiltDropdown( + initialOptions, + selectedOptions, + labels, + clearFilter, +) { + const dropdownTable = generateDropdownTable( + initialOptions || [], + selectedOptions || [], + labels, + ); + + const menu = document.createElement("div"); + menu.className = "dropdown-menu show py-3 px-1"; + + const clearButton = document.createElement("button"); + clearButton.type = "button"; + clearButton.className = + "btn btn-link btn-sm p-0 position-absolute top-0 end-0 m-1"; + clearButton.title = labels?.clear || "Clear"; + clearButton.setAttribute("aria-label", labels?.clear || "Clear"); + + const clearIcon = document.createElement("i"); + clearIcon.className = "fa-solid fa-filter-circle-xmark"; + clearButton.appendChild(clearIcon); + + clearButton.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + clearFilter({ closeDropdownAfterClear: false }); + menu.querySelectorAll("input[type='checkbox']").forEach((checkbox) => { + checkbox.checked = false; + }); + }); + + menu.appendChild(clearButton); + menu.appendChild(dropdownTable); + + return menu; +} + +function generateDropdownTable(options, selectedTagOptions, labels) { + const table = document.createElement("table"); + table.className = "table table-sm mb-0"; + + // HEADER + const thead = document.createElement("thead"); + const headRow = document.createElement("tr"); + + const headers = [ + labels?.tag || "Tag", + ...TAG_FILTER_CONNECTOR_ORDER.map((connector) => + getConnectorLabel(connector, labels), + ), + ]; + + headers.forEach((h, index) => { + const th = document.createElement("th"); + if (index > 0) { + th.className = "text-center"; + } + + const strong = document.createElement("strong"); + strong.textContent = h; + th.appendChild(strong); + headRow.appendChild(th); + }); + + thead.appendChild(headRow); + table.appendChild(thead); + + // BODY + const tbody = document.createElement("tbody"); + options.forEach((option) => { + const row = document.createElement("tr"); + + // LABEL + const labelTd = document.createElement("td"); + const label = document.createElement("span"); + label.className = "tag " + option.style; + label.innerText = option.label; + + if (option.automatisiert) { + label.innerHTML = " " + option.label; + label.className += " tag_auto"; + } + + labelTd.appendChild(label); + row.appendChild(labelTd); + + function makeCheckbox(connector) { + const td = document.createElement("td"); + const cb = document.createElement("input"); + + td.className = "text-center align-middle"; + + cb.type = "checkbox"; + cb.style.cursor = "pointer"; + + cb.checked = selectedTagOptions.some( + (t) => t.value === option.value && t.connector === connector.key, + ); + + cb.addEventListener("click", (event) => { + event.stopPropagation(); + + if (event.target.checked) { + selectedTagOptions.push({ + label: option.label, + value: option.value, + connector: connector.key, + }); + } else { + const idx = selectedTagOptions.findIndex( + (t) => t.value === option.value && t.connector === connector.key, + ); + + if (idx !== -1) { + selectedTagOptions.splice(idx, 1); + } + } + }); + + td.appendChild(cb); + return td; + } + + TAG_FILTER_CONNECTOR_ORDER.forEach((connector) => { + row.appendChild(makeCheckbox(connector)); + }); + + tbody.appendChild(row); + }); + + table.appendChild(tbody); + + return table; +} + +function parseFilterExpression(expression) { + const collections = []; + + try { + const orParts = expression.split("||").map((part) => part.trim()); + + orParts.forEach((part) => { + const andParts = part.split("&&").map((p) => p.trim()); + + const collection = { positives: [], negatives: [] }; + + andParts.forEach((term) => { + const comparisonMatch = term.match( + /^(<=|>=|<|>|=|!=)\s*(-?\d+(?:[.,]\d+)?)$/, + ); + + if (comparisonMatch) { + const operator = comparisonMatch[1]; + const numberStr = comparisonMatch[2].replace(",", "."); + const number = parseFloat(numberStr); + collection.positives.push({ type: "comparison", operator, number }); + } else if (term.startsWith("!")) { + const excludeTerm = term.substring(1).trim().replace(/\*/g, ".*"); + collection.negatives.push({ + type: "regex", + regex: new RegExp(excludeTerm, "i"), + }); + } else { + const includeTerm = term.replace(/\*/g, ".*"); + collection.positives.push({ + type: "regex", + regex: new RegExp(includeTerm, "i"), + }); + } + }); + collections.push(collection); + }); + } catch (e) {} + return collections; +} + +export function extendedHeaderFilter( + headerValue, + rowValue, + rowData, + filterParams, +) { + const fields = Array.isArray(filterParams?.field) + ? filterParams.field + : [filterParams?.field]; + + if (fields.length > 1 && rowData) { + rowValue = fields + .map((f) => rowData[f] ?? "") + .filter(Boolean) + .join(" "); + } + if (typeof headerValue === "boolean") { + return rowValue === headerValue; + } + + const collections = parseFilterExpression(headerValue); + + function matchValue(value) { + try { + return collections.some((collection) => { + let positives = + collection.positives.length === 0 || + collection.positives.every((condition) => { + if (condition.type === "comparison") { + let value = parseFloat(rowValue); + if (isNaN(value)) return false; + + switch (condition.operator) { + case "<": + return value < condition.number; + case ">": + return value > condition.number; + case "<=": + return value <= condition.number; + case ">=": + return value >= condition.number; + case "=": + return value === condition.number; + case "!=": + return value !== condition.number; + default: + return false; + } + } else if (condition.type === "regex") { + return condition.regex.test(rowValue); + } + return false; + }); + + let negatives = collection.negatives.every((condition) => { + return !condition.regex.test(rowValue); + }); + + return positives && negatives; + }); + } catch (e) {} + } + + if (matchValue(rowValue)) return true; + + if (rowData && filterParams) { + const childrenField = filterParams?.children || "_children"; + const field = filterParams?.field; + + const children = rowData[childrenField]; + if (Array.isArray(children)) { + for (let child of children) { + let childValue = child[field]; + if ( + extendedHeaderFilter(headerValue, childValue, child, filterParams) + ) { + return true; + } + } + } + } + + return false; +} +export function tagHeaderFilter(headerValue, rowValue, rowData, filterParams) { + let data; + + try { + data = typeof rowValue === "string" ? JSON.parse(rowValue) : rowValue; + } catch (error) { + return false; + } + + let combinedText; + + if (Array.isArray(data)) { + combinedText = data + .filter((item) => item?.done !== true) + .map((item) => `${item?.beschreibung} ${item?.notiz}`) + .join(" "); + } else if (typeof data === "object" && data !== null) { + combinedText = + data?.erledigt === false ? `${data?.beschreibung} ${data?.notiz}` : ""; + } else { + combinedText = String(data); + } + + return extendedHeaderFilter(headerValue, combinedText, rowData, filterParams); +} diff --git a/public/js/tabulator/formatter/tags.js b/public/js/tabulator/formatter/tags.js index 0d2f5004c..d78ed2026 100644 --- a/public/js/tabulator/formatter/tags.js +++ b/public/js/tabulator/formatter/tags.js @@ -1,67 +1,128 @@ -export function tagFormatter(cell, tagComponent) -{ - let tags = cell.getValue(); - if (!tags) return; +export function tagFormatter(cell, tagComponent, onRendered, tagKey = "tags") { + const mappedData = tagComponent[tagKey]?.map((tag) => ({ + typ_kurzbz: tag.tag_typ_kurzbz, + automatisiert: tag.automatisiert, + })); - let container = document.createElement('div'); - container.className = "d-flex gap-1"; + let tags = cell.getValue(); + if (!tags) return; - let parsedTags = JSON.parse(tags); - let maxVisibleTags = 2; + let container = document.createElement("div"); + container.className = "d-flex gap-1"; - const rowData = cell.getRow().getData(); - if (rowData._tagExpanded === undefined) { - rowData._tagExpanded = false; - } + let parsedTags = []; + if (typeof tags === "string") { + parsedTags = JSON.parse(tags); + } else if (Array.isArray(tags)) { + parsedTags = tags; + } - const renderTags = () => { - container.innerHTML = ''; - parsedTags = parsedTags.filter(item => item !== null); + let maxVisibleTags = 3; - parsedTags.sort((a, b) => { - let adone = a.done ? 1 : 0; - let bbone = b.done ? 1 : 0; + const rowData = cell.getRow().getData(); + if (rowData._tagExpanded === undefined) { + rowData._tagExpanded = false; + } - if (adone !== bbone) - { - return adone - bbone; - } - return b.id - a.id; - }); - const tagsToShow = rowData._tagExpanded ? parsedTags : parsedTags.slice(0, maxVisibleTags); + const renderTags = () => { + container.innerHTML = ""; + parsedTags = parsedTags.filter((item) => item !== null); - tagsToShow.forEach(tag => { - if (!tag) return; - let tagElement = document.createElement('span'); - tagElement.innerText = tag.beschreibung; - tagElement.title = tag.notiz; - tagElement.className = "tag " + tag.style; - if (tag.done) tagElement.className += " tag_done"; + parsedTags.sort((a, b) => { + let adone = a.done ? 1 : 0; + let bbone = b.done ? 1 : 0; - tagElement.addEventListener('click', (event) => { - event.stopPropagation(); - event.preventDefault(); - tagComponent.editTag(tag.id); - }); + if (adone !== bbone) { + return adone - bbone; + } + return a.prioritaet - b.prioritaet; + }); + const tagsToShow = rowData._tagExpanded + ? parsedTags + : parsedTags.slice(0, maxVisibleTags); - container.appendChild(tagElement); - }); + tagsToShow.forEach((tag) => { + if (!tag) return; + let tagElement = document.createElement("span"); + tagElement.innerText = tag.beschreibung; + tagElement.title = tag.notiz; + tagElement.className = "tag " + tag.style; + if (tag.done) tagElement.className += " tag_done"; - if (parsedTags.length > maxVisibleTags) { - let toggle = document.createElement('button'); - toggle.innerText = (rowData._tagExpanded ? '- ' : '+ ') + (parsedTags.length - maxVisibleTags); - toggle.className = "display_all"; - toggle.title = rowData._tagExpanded ? "Tags ausblenden" : "Tags einblenden"; + const tagDef = mappedData?.find((t) => t.typ_kurzbz === tag.typ_kurzbz); - toggle.addEventListener('click', () => { - rowData._tagExpanded = !rowData._tagExpanded; - renderTags(); - }); + if ( + (!tagDef && tag.typ_kurzbz?.includes("_auto")) || + tagDef?.automatisiert + ) { + tagElement.className += " tag_auto"; + tagElement.innerHTML = + " " + tag.beschreibung; + } - container.appendChild(toggle); - } - }; + tagElement.addEventListener("click", (event) => { + event.stopPropagation(); + event.preventDefault(); + tagComponent.editTag(tag.id); + }); - renderTags(); - return container; -} \ No newline at end of file + container.appendChild(tagElement); + }); + + if (parsedTags.length > maxVisibleTags) { + let toggle = document.createElement("button"); + toggle.innerText = + (rowData._tagExpanded ? "- " : "+ ") + + (parsedTags.length - maxVisibleTags); + toggle.className = "display_all"; + toggle.title = rowData._tagExpanded + ? "Tags ausblenden" + : "Tags einblenden"; + + toggle.addEventListener("click", () => { + rowData._tagExpanded = !rowData._tagExpanded; + renderTags(); + }); + + container.appendChild(toggle); + } + }; + + const fitTags = () => { + if (rowData._tagExpanded) { + renderTags(); + return; + } + + let widthBuffer = 10; + maxVisibleTags = parsedTags.length; + renderTags(); + + while ( + maxVisibleTags > 0 && + container.scrollWidth > (container.clientWidth + widthBuffer) + ) { + maxVisibleTags--; + renderTags(); + } + }; + + let animationFrame = null; + container.fitTags = () => { + if (animationFrame !== null) return; + + animationFrame = requestAnimationFrame(() => { + animationFrame = null; + fitTags(); + }); + }; + + if (onRendered) { + onRendered(() => { + container.fitTags(); + }); + } + + renderTags(); + return container; +} diff --git a/system/dbupdate_3.4.php b/system/dbupdate_3.4.php index 9fd1b8d98..c57f88984 100644 --- a/system/dbupdate_3.4.php +++ b/system/dbupdate_3.4.php @@ -80,7 +80,6 @@ require_once('dbupdate_3.4/61730_Dashboard_Anpassungen.php'); require_once('dbupdate_3.4/40128_search.php'); require_once('dbupdate_3.4/63394_Variablenbeschraenkung.php'); require_once('dbupdate_3.4/63436_cis4_iframe_component.php'); -require_once('dbupdate_3.4/60882_lehrfaecherverteilung_favorites.php'); require_once('dbupdate_3.4/66982_berufsschule.php'); require_once('dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php'); require_once('dbupdate_3.4/47972_pruefungsverwaltung_ects_angabe.php'); @@ -98,6 +97,9 @@ require_once('dbupdate_3.4/75888_reihungstest_mehrfachdurchfuehrung.php'); require_once('dbupdate_3.4/76150_perm_other_lv_plan.php'); require_once('dbupdate_3.4/68957_dashboard_bookmark_neue_Spalte_sort.php'); 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'); // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen echo '

Pruefe Tabellen und Attribute!

'; @@ -395,6 +397,7 @@ $tabellen=array( "public.tbl_studiengangstyp" => array("typ","bezeichnung","beschreibung","bezeichnung_mehrsprachig"), "public.tbl_studienjahr" => array("studienjahr_kurzbz","bezeichnung"), "public.tbl_studiensemester" => array("studiensemester_kurzbz","bezeichnung","start","ende","studienjahr_kurzbz","ext_id","beschreibung","onlinebewerbung"), + "public.tbl_tabulator_presets" => array("preset_id", "benutzer_uid", "table_name", "preset_json"), "public.tbl_tag" => array("tag"), "public.tbl_variable" => array("name","uid","wert"), "public.tbl_variablenname" => array("name","defaultwert"), diff --git a/system/dbupdate_3.4/75959_StudVw_Automatische_Tags.php b/system/dbupdate_3.4/75959_StudVw_Automatische_Tags.php new file mode 100644 index 000000000..1954ae48d --- /dev/null +++ b/system/dbupdate_3.4/75959_StudVw_Automatische_Tags.php @@ -0,0 +1,139 @@ +db_query("SELECT taglib FROM public.tbl_notiz_typ LIMIT 1")) +{ + $qry = 'ALTER TABLE public.tbl_notiz_typ ADD COLUMN taglib character varying(32);'; + + if(!$db->db_query($qry)) + echo ' public.tbl_notiz_typ '.$db->db_last_error().'
'; + else + echo '
public.tbl_notiz_typ: Neue Spalte taglib hinzugefügt'; +} + +//automatic tags anlegen +$tags = [ + [ + 'typ_kurzbz' => 'wh_auto', + 'bezeichnung' => '{WH, WH}', + 'style' => 'tag_dark_grey', + 'prioritaet' => 11, + 'taglib' => 'tags/CoreWiederholerTagLib' + ], + [ + 'typ_kurzbz' => 'dd_auto', + 'bezeichnung' => '{DD, DD}', + 'style' => 'tag_braun', + 'prioritaet' => 10, + 'taglib' => 'tags/CoreDoubleDegreeTagLib' + ], + [ + 'typ_kurzbz' => 'out_auto', + 'bezeichnung' => '{Outgoing, Outgoing}', + 'style' => 'tag_limette', + 'prioritaet' => 12, + 'taglib' => 'tags/CoreOutgoingTagLib' + ], + [ + 'typ_kurzbz' => 'prewh_auto', + 'bezeichnung' => '{Pre-WH, pre-WH}', + 'style' => 'tag_light_grey', + 'prioritaet' => 13, + 'taglib' => 'tags/CorePrewiederholerTagLib' + ], + [ + 'typ_kurzbz' => 'zgv_auto', + 'bezeichnung' => '{ZGV offen, ZGV missing}', + 'style' => 'tag_beige', + 'prioritaet' => 14, + 'taglib' => 'tags/CoreMissingZgvTagLib' + ], + [ + 'typ_kurzbz' => 'unterbrecher_auto', + 'bezeichnung' => '{UB, UB}', + 'style' => 'tag_turquoise', + 'prioritaet' => 15, + 'taglib' => 'tags/CoreUnterbrecherTagLib' + ], + [ + 'typ_kurzbz' => 'stbtr_erh_auto', + 'bezeichnung' => '{erh.Studienbeitrag, Incr. Tuition Fees}', + 'style' => 'tag_bordeaux', + 'prioritaet' => 16, + 'taglib' => 'tags/CoreStbErhoehtTagLib' + ], + [ + 'typ_kurzbz' => 'jgv_auto', + 'bezeichnung' => '{JGV, YGR}', + 'style' => 'tag_olive', + 'prioritaet' => 17, + 'taglib' => 'tags/CoreJgvTagLib' + ], + [ + 'typ_kurzbz' => 'preabbr_auto', + 'bezeichnung' => '{Pre-Abbr, pre-Abbr}', + 'style' => 'tag_blush', + 'prioritaet' => 18, + 'taglib' => 'tags/CorePreabbrecherTagLib' + ], + [ + 'typ_kurzbz' => 'in_auto', + 'bezeichnung' => '{Incoming, Incoming}', + 'style' => 'tag_skyblue', + 'prioritaet' => 19, + 'taglib' => 'tags/CoreIncomingTagLib' + ], + +]; + +foreach ($tags as $tag) { + + $checkQry = " + SELECT 1 + FROM public.tbl_notiz_typ + WHERE typ_kurzbz = '".$tag['typ_kurzbz']."' + "; + + if ($result = $db->db_query($checkQry)) { + + if ($db->db_num_rows($result) == 0) { + + $qry = " + INSERT INTO public.tbl_notiz_typ + ( + typ_kurzbz, + bezeichnung_mehrsprachig, + automatisiert, + aktiv, + tag, + style, + vorrueckung, + prioritaet, + taglib + ) + VALUES + ( + '".$tag['typ_kurzbz']."', + '".$tag['bezeichnung']."', + true, + true, + true, + '".$tag['style']."', + false, + '".$tag['prioritaet']."', + '".$tag['taglib']."' + ) + "; + + if (!$db->db_query($qry)) + { + echo 'public.tbl_notiz_typ: '. $tag['typ_kurzbz']. ' '. $db->db_last_error().'
'; + } + else + { + echo '
public.tbl_notiz_typ: Automatic Tag '.$tag['typ_kurzbz'].' hinzugefuegt'; + } + } + } +} diff --git a/system/dbupdate_3.4/60882_lehrfaecherverteilung_favorites.php b/system/dbupdate_3.4/76160_lvv_favorites.php similarity index 74% rename from system/dbupdate_3.4/60882_lehrfaecherverteilung_favorites.php rename to system/dbupdate_3.4/76160_lvv_favorites.php index 55fb55b80..6d6dfb382 100644 --- a/system/dbupdate_3.4/60882_lehrfaecherverteilung_favorites.php +++ b/system/dbupdate_3.4/76160_lvv_favorites.php @@ -2,15 +2,15 @@ if (!defined('DB_NAME')) exit('No direct script access allowed'); // Add new name type in public.tbl_variablenname -if ($result = @$db->db_query("SELECT 1 FROM public.tbl_variablenname WHERE name = 'lv_favorites';")) +if ($result = @$db->db_query("SELECT 1 FROM public.tbl_variablenname WHERE name = 'lvv_favorites';")) { if ($db->db_num_rows($result) == 0) { - $qry = "INSERT INTO public.tbl_variablenname(name, defaultwert) VALUES('lv_favorites', null);"; + $qry = "INSERT INTO public.tbl_variablenname(name, defaultwert) VALUES('lvv_favorites', null);"; if (!$db->db_query($qry)) echo 'public.tbl_variablenname '.$db->db_last_error().'
'; else - echo 'public.tbl_variablenname: Added name "lv_favorites"
'; + echo 'public.tbl_variablenname: Added name "lvv_favorites"
'; } } diff --git a/system/dbupdate_3.4/77080_tabulator_presets_table.php b/system/dbupdate_3.4/77080_tabulator_presets_table.php new file mode 100644 index 000000000..bef240b17 --- /dev/null +++ b/system/dbupdate_3.4/77080_tabulator_presets_table.php @@ -0,0 +1,55 @@ +db_query("SELECT 1 FROM public.tbl_tabulator_presets LIMIT 1")) { + $qry = "CREATE TABLE public.tbl_tabulator_presets ( + preset_id int NOT NULL, + benutzer_uid varchar(33) NOT NULL, + table_name varchar(255) NOT NULL, + preset_name varchar(255) NOT NULL, + preset_json varchar(10000) NOT NULL, + CONSTRAINT tbl_tabulator_presets_preset_id_pk PRIMARY KEY(preset_id), + CONSTRAINT tbl_tabulator_presets_benutzer_uid_fk FOREIGN KEY (benutzer_uid) REFERENCES public.tbl_benutzer(uid) + );"; + + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
public.tbl_tabulator_presets: table created'; + + + $db->db_query('CREATE SEQUENCE IF NOT EXISTS public.seq_tbl_tabulator_presets_preset_id + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1;'); + + $db->db_query("ALTER TABLE public.tbl_tabulator_presets ALTER COLUMN preset_id SET DEFAULT nextval('public.seq_tbl_tabulator_presets_preset_id');"); + + + $qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE public.tbl_tabulator_presets TO web;'; + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
Granted privileges to web on public.tbl_tabulator_presets'; + + $qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE public.tbl_tabulator_presets TO vilesci;'; + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
Granted privileges to vilesci on public.tbl_tabulator_presets'; + + $qry = 'GRANT USAGE ON public.seq_tbl_tabulator_presets_preset_id TO web;'; + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
Granted privileges to web on public.tbl_tabulator_presets'; + + $qry = 'GRANT USAGE ON public.seq_tbl_tabulator_presets_preset_id TO vilesci;'; + if (!$db->db_query($qry)) + echo 'public.tbl_tabulator_presets: ' . $db->db_last_error() . '
'; + else + echo '
Granted privileges to vilesci on public.tbl_tabulator_presets'; +} diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index c29b5df3c..4f29de1d6 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -29763,6 +29763,88 @@ array( ) ), //Profil Phrasen ende + //Studium Phrasen start + array( + 'app' => 'core', + 'category' => 'studium', + 'phrase' => 'studium', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Studium', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Study', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'studium', + 'phrase' => 'studienordnung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Studienordnung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Study regulation', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'studium', + 'phrase' => 'lektoren', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Lektoren', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Lecturers', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'studium', + 'phrase' => 'studiensemester', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Studiensemester', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Study semester', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + // Studium Phrasen Ende // LvPlan Phrasen start array( 'app' => 'core', @@ -40028,6 +40110,447 @@ array( ) ) ), + //**************************** CORE/treemenu + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_interessenten', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Interessenten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Candidates', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_bewerbungnichtabgeschickt', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bewerbung nicht abgeschickt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Application not submitted', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_bewerbungabgeschickt', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bewerbung abgeschickt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Application submitted', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_zgv', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'ZGV erfüllt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'ZGV fulfilled', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_statusbestaetigt', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Status bestätigt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Status confirmed', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_statusbestaetigtrtnichtangemeldet', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Reihungstest nicht angemeldet', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Ranking test not registered', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_statusbestaetigtrtangemeldet', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Reihungstest angemeldet', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Ranking test registered', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_reihungstestnichtangemeldet', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Reihungstest nicht angemeldet', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Ranking test not registered', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_reihungstestangemeldet', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Reihungstest angemeldet', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Ranking test registered', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_bewerber', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bewerber', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Applicants', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_bewerberrtnichtangemeldet', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Reihungstest nicht angemeldet', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Ranking test not registered', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_bewerberrtangemeldet', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Reihungstest angemeldet', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Ranking test registered', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_bewerberrtangemeldetteilgenommen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Teilgenommen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Participated', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_bewerberrtangemeldetnichtteilgenommen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Nicht teilgenommen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Not participated', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_aufgenommen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Aufgenommen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Accepted', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_warteliste', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Warteliste', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Waiting list', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_absage', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Absage', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Rejected', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'filter_incoming', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Incoming', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Incoming', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'inout_inout', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'International', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'International', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'inout_incoming', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Incoming', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Incoming', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'inout_outgoing', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Outgoing', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Outgoing', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'treemenu', + 'phrase' => 'inout_shared_studies', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Gemeinsame Studien', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Shared studies', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), //**************************** CORE/stv array( 'app' => 'core', @@ -41073,6 +41596,26 @@ array( ) ), // grades + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'grades_gradelist', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Studienverlauf', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Grade list', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'stv', @@ -49321,7 +49864,7 @@ array( ), array( 'app' => 'core', - 'category' => 'stv', + 'category' => 'ui', 'phrase' => 'weitereEMail', 'insertvon' => 'system', 'phrases' => array( @@ -51067,6 +51610,126 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'lehre', + 'phrase' => 'berichte', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Berichte', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Reports', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'lehre', + 'phrase' => 'lvplanung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'LV-Planung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Course Planning', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'lehre', + 'phrase' => 'lehrauftragsliste', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Lehrauftragsliste', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Teaching Assignments', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'lehre', + 'phrase' => 'lehrauftraege', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Lehraufträge', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Lectureships', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'lehre', + 'phrase' => 'lehrauftragslisteemp', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Lehrauftrag Lehrende', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Teaching Assignment Lecturers', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'lehre', + 'phrase' => 'extrakvverwaltung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Lehrveranstaltung Verwaltung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Course Management', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'core', 'category' => 'lehre', @@ -55510,6 +56173,46 @@ and represent the current state of research on the topic. The prescribed citatio ) ) ), + array( + 'app' => 'core', + 'category' => 'person', + 'phrase' => 'mailText_profilfoto', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Hallo,\n\nIhr Profilbild wurde entfernt, da es nicht den aktuellen Bildrichtlinen entspricht. Bitte laden Sie unter CIS->Profil ein neues Profilbild hoch!\n\nDanke!", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Hello,\n\nYour profile picture has been removed because it does not comply with the current image guidelines. Please upload a new profile picture under CIS->Profile!\n\nThank you!", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'person', + 'phrase' => 'betreffProfilfoto', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Profilbild', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Profile picture', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // FHC-4 Finetuning END //**************************** CORE/search @@ -59926,6 +60629,86 @@ I have been informed that I am under no obligation to consent to the transmissio ) ) ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'lvplanung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'LV-Planung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Course-Planning', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'lvplanung_xls', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'LV-Planung EXCEL', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Course-Planning EXCEL', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'lvplanung_html', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'LV-Planung HTML', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Course-Planning HTML', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'RTVerwaltung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Reihungstestverwaltung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Administration Placement Test', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // ### Phrases Dashboard Admin START array( @@ -60696,6 +61479,958 @@ I have been informed that I am under no obligation to consent to the transmissio ) ), // generic tabulator phrases END + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'text_exportXLS', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => '{count} Prestudent(Innen) als Excel exportieren', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Export {count} prestudent(s) to Excel', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'alert_chooseStudent', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bitte eine/n Studierende/n markieren!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Please select a student!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'studienverlauf', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Studienverlauf', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Gradelist', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'lehre', + 'phrase' => 'textNoStatusInSem', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Kein Status im {sem}', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'No status in {sem}', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'alert_chooseOnlyOneStudent', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bitte nur eine/n Studierende/n markieren!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Please select only one student!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'alert_chooseGroupSem', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bitte eine Gruppe auswählen!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Please select a group!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'errorSavingData', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Speichern der Daten nicht erfolgreich', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' =>'Data saving failed', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'private', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Privat', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'private', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'andCondition', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'und', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'and', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'orCondition', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'oder', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'or', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'notCondition', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'nicht', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'not', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + //Start Finetuning StudVW + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'fenster_schliessen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sie können nun das Fenster schließen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'This window can be closed now', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tag', + 'phrase' => 'rebuild_tags', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Neuberechnen Tags', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Rebuild Tags', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'selection', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Auswahl', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'selection', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tag', + 'phrase' => 'messageModalWait', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bitte warten Sie, bis alle Tags neu berechnet wurden!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Please wait until all tags have been recalculated!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tag', + 'phrase' => 'alertSuccessRebuild', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Tags erfolgreich neuberechnet ({count} Prestudent*innnen)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Tags successfully recalculated ({count} prestudents)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tag', + 'phrase' => 'alertErrorRebuild', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Fehler bei Neuberechnen Tags, PrestudentId(s): ', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Error during Recalculating tags, PrestudentId(s): ', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + //End Finetuning StudVW + // tabulator presets section phrases START + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'displayed_columns', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Angezeigte Spalten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Displayed columns', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'in_order', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'in Reihenfolge', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'in order', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'column_header_filters', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Spalten Header Filter', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Column header filters', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'asc', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'aufsteigend', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'ascending', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'desc', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'absteigend', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'descending', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'sort', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sortierung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Sort', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_deletion_confirmation', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sind Sie sicher, dass Sie die Voreinstellung (((presetName))) löschen möchten?', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Are you sure you want to delete preset (((presetName)))?', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'table_presets', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Tabelle Voreinstellungen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Table presets', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'save_current_config', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Aktuelle Einstellung speichern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Save current configuration', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_name', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Voreinstellung Name', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Preset name', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'max_presets_count_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Maximale Anzahl an Voreinstellungen erreicht!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Maximum number of presets reached!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_name_duplicate_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Doppelte Voreinstellunge Namen sind nicht zulässig!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Preset name duplicate not allowed!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_not_found_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Voreinstellung nicht gefunden!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Preset not found!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_not_own_update_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sie dürfen diese Voreinstellung nicht ändern!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'You are not allowed to update this preset!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_not_own_delete_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sie dürfen diese Voreinstellung nicht löschen!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'You are not allowed to delete this preset!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_creation_generic_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bei der Erstellung der Voreinstellung ist ein Fehler aufgetreten!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Something went wrong during preset creation!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_update_generic_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bei der Voreinstellungsänderung ist ein Fehler aufgetreten!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Something went wrong during preset update!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'preset_deletion_generic_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Beim Löschen der Voreinstellung ist ein Fehler aufgetreten!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Something went wrong during preset deletion!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'invalid_params_err_msg', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ungültige Parameterübergabe!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Invalid parameters!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'tabulator_presets', + 'phrase' => 'modified', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'modifiziert', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'modified', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + // tabulator presets section phrases END + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'openInMailClient', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'In Mailanwendung öffnen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Open in Mailclient', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'kompatibilityMode_WebmailClient', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Kompatibilitätsmodus für Webmail', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Open in Compatibility mode (Webmail)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'webmailclient', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Webmail', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'webmail', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + // StudVw legacy Bis Links begin + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'bis_stud', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'BIS (Studierende)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'BIS (Students)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'bis_stud_meldung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Meldung generieren (Studierende)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Generate Report (Students)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'bis_stud_plausicheck', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Plausibilitätsprüfung (Studierende)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Plausibility Check (Students)', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'alert_chooseDegreeProgram', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bitte einen Studiengang auswählen!', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Please select a degree program!', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + // StudVw legacy Bis Links ende + array( + 'app' => 'core', + 'category' => 'studierendenantrag', + 'phrase' => 'link_Abmeldung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Weiter zur Abmeldung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Continue to De-registration', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'studierendenantrag', + 'phrase' => 'modalHinweistext_Abmeldung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Bevor du deine Abmeldung abschließt, möchten wir dir gerne ein Gespräch anbieten. Vielleicht gibt es Fragen, Unsicherheiten oder Herausforderungen, bei denen wir dich unterstützen können.
+
Du kannst dich jederzeit unverbindlich an deine Studiengangsassistenz, Studiengangsleitung oder das Student Support Team wenden. Ein Gespräch ist natürlich freiwillig – manchmal hilft es aber, gemeinsam Optionen zu besprechen, zum Beispiel auch eine vorübergehende +Studienunterbrechung. +

Wenn du deine Abmeldung fortsetzen möchtest, kannst du das selbstverständlich tun.", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Before you finalize your deregistration, we would like to offer you a consultation. Perhaps you have questions, uncertainties, or challenges that we can help you with.
+
You can contact your program assistant, program director, or the Student Support Team at any time without obligation. A consultation is, of course, voluntary; however, sometimes it helps to discuss options together, such as a +temporary break from your studies. +

If you wish to proceed with your deregistration, you can, of course, do so.", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'studierendenantrag', + 'phrase' => 'info_consultation', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Hinweis Beratungsgespräch', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Note Consultation', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), //**************************** CIS Projektabgabeuebersicht start array( 'app' => 'core', diff --git a/tests/cypress/.gitignore b/tests/cypress/.gitignore new file mode 100644 index 000000000..25e6d917c --- /dev/null +++ b/tests/cypress/.gitignore @@ -0,0 +1,7 @@ +node_modules +.env +.env.* +!.env.example +/cypress/videos +/cypress/screenshots +/results \ No newline at end of file