Erster Prototyp für Tempus Neu DB und GUI

This commit is contained in:
Andreas Österreicher
2025-10-20 11:01:50 +02:00
parent 9d306fcb7e
commit 352fc53e74
19 changed files with 1629 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*
*/
class Tempus extends Auth_Controller
{
public function __construct()
{
$permissions = [];
$router = load_class('Router');
$permissions[$router->method] = ['admin:r', 'assistenz:r'];
parent::__construct($permissions);
// Load Libraries
$this->load->library('VariableLib', ['uid' => getAuthUID()]);
// Load Config
$this->load->config('calendar');
}
/**
* @return void
*/
public function _remap()
{
$this->load->view('Tempus', [
'permissions' => [
'admin' => $this->permissionlib->isBerechtigt('admin')
],
'variables' => [
'semester_aktuell' => $this->variablelib->getVar('semester_aktuell'),
'timezone' => $this->config->item('timezone')
]
]);
}
}
@@ -0,0 +1,171 @@
<?php
/**
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
class Kalender extends FHCAPI_Controller
{
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'getRoomplan' => self::PERM_LOGGED,
'Stunden' => self::PERM_LOGGED,
'Reservierungen' => self::PERM_LOGGED,
'getStundenplan' => self::PERM_LOGGED,
'getLehreinheitStudiensemester' => self::PERM_LOGGED,
'updateKalenderEvent' => 'lehre/lvplan:rw',
'addKalenderEvent' => 'lehre/lvplan:rw'
]);
$this->load->library('LogLib');
$this->loglib->setConfigs(array(
'classIndex' => 5,
'functionIndex' => 5,
'lineIndex' => 4,
'dbLogType' => 'API', // required
'dbExecuteUser' => 'RESTful API'
));
$this->uid = getAuthUID();
$this->load->library('form_validation');
//load models
//$this->load->model('ressource/Stundenplan_model', 'StundenplanModel');
//$this->load->model('ressource/Reservierung_model', 'ReservierungModel');
$this->load->library('KalenderLib');
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
* fetches Stunden layout from database
* @access public
*
*/
public function Stunden()
{
$this->load->model('ressource/Stunde_model', 'StundeModel');
$stunden = $this->StundeModel->load();
$stunden = $this->getDataOrTerminateWithError($stunden);
$this->terminateWithSuccess($stunden);
}
/**
* fetches stundenplan events from a Room and start/end date
* @access public
*
*/
public function getRoomplan()
{
// form validation
$this->load->library('form_validation');
$this->form_validation->set_data($_GET);
$this->form_validation->set_rules('start_date',"start_date","required");
$this->form_validation->set_rules('end_date',"end_date","required");
$this->form_validation->set_rules('ort_kurzbz',"ort_kurzbz","required");
if($this->form_validation->run() === FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
// storing the get parameter in local variables
$start_date = $this->input->get('start_date', TRUE);
$end_date = $this->input->get('end_date', TRUE);
$ort_kurzbz = $this->input->get('ort_kurzbz', TRUE);
$stundenplan_data =$this->kalenderlib->getRoomData($ort_kurzbz, $start_date, $end_date);
$this->terminateWithSuccess($stundenplan_data);
}
public function updateKalenderEvent()
{
// form validation
$this->load->library('form_validation');
$this->form_validation->set_data($_POST);
$this->form_validation->set_rules('kalender_id',"kalender_id","required");
$this->form_validation->set_rules('ort_kurzbz',"ort_kurzbz","required");
$this->form_validation->set_rules('start_date',"start_date","required");
$this->form_validation->set_rules('end_date',"end_date","optional");
if($this->form_validation->run() === FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
// storing the get parameter in local variables
$kalender_id = $this->input->post('kalender_id', TRUE);
$ort_kurzbz = $this->input->post('ort_kurzbz', TRUE);
$start_date = $this->input->post('start_date', TRUE);
$end_date = $this->input->post('end_date', TRUE);
// Was passiert hier?
// Raumänderung, Tagesänderung, Start / Ende Zeit korrektur
// Ist das alles ein Endpunkt?
$stundenplan_data =$this->kalenderlib->updateKalenderEvent($this->uid,$kalender_id, $ort_kurzbz, $start_date, $end_date);
$this->terminateWithSuccess($stundenplan_data);
}
public function addKalenderEvent()
{
// form validation
$this->load->library('form_validation');
$this->form_validation->set_data($_POST);
$this->form_validation->set_rules('lehreinheit_id',"kalender_id","required");
$this->form_validation->set_rules('ort_kurzbz',"ort_kurzbz","required");
$this->form_validation->set_rules('start_date',"start_date","required");
$this->form_validation->set_rules('end_date',"end_date","required");
if($this->form_validation->run() === FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
// storing the get parameter in local variables
$lehreinheit_id = $this->input->post('lehreinheit_id', TRUE);
$ort_kurzbz = $this->input->post('ort_kurzbz', TRUE);
$start_date = $this->input->post('start_date', TRUE);
$end_date = $this->input->post('end_date', TRUE);
$this->kalenderlib->addKalenderEvent($this->uid, $ort_kurzbz, $start_date, $end_date, $lehreinheit_id);
$this->terminateWithSuccess();
}
// gets the reservierungen of a room if the ort_kurzbz parameter is supplied otherwise gets the reservierungen of the stundenplan of a student
public function Reservierungen($ort_kurzbz = null)
{
$this->terminateWithSuccess();
}
public function getLehreinheitStudiensemester($lehreinheit_id)
{
$this->load->model('education/Lehreinheit_model', 'LehreinheitModel');
$this->LehreinheitModel->addSelect(["studiensemester_kurzbz"]);
$result = $this->LehreinheitModel->load($lehreinheit_id);
$result = current($this->getDataOrTerminateWithError($result))->studiensemester_kurzbz;
$this->terminateWithSuccess($result);
}
}
@@ -0,0 +1,94 @@
<?php
/**
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
class Tempus extends FHCAPI_Controller
{
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'getCourses' => self::PERM_LOGGED,
]);
$this->load->library('LogLib');
$this->loglib->setConfigs(array(
'classIndex' => 5,
'functionIndex' => 5,
'lineIndex' => 4,
'dbLogType' => 'API', // required
'dbExecuteUser' => 'RESTful API'
));
$this->load->library('form_validation');
//load models
//$this->load->model('ressource/Stundenplan_model', 'StundenplanModel');
//$this->load->model('ressource/Reservierung_model', 'ReservierungModel');
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
* fetches courses
* @access public
*
*/
public function getCourses()
{
// form validation
$this->load->library('form_validation');
$this->form_validation->set_data($_GET);
$this->form_validation->set_rules('searchfilter',"searchfilter","required");
if($this->form_validation->run() === FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
// storing the get parameter in local variables
$searchfilter = $this->input->get('searchfiler', TRUE);
// TODO implement Loading Data
$course_data = array(
array(
'lehreinheit_id'=>'1',
'bezeichnung' => 'Englisch 1',
'studiengang_kurzbz' => 'BMR',
'semester' => '1',
'kurzbz' => 'ENG',
'lektoren' => array('OesterAn','KindlMa')
),
array(
'lehreinheit_id'=>'2',
'bezeichnung' => 'Mahtematik 1',
'studiengang_kurzbz' => 'BMR',
'semester' => '1',
'kurzbz' => 'MAT',
'lektoren' => array('BamberHa')
)
);
$this->terminateWithSuccess($course_data);
}
}
@@ -0,0 +1,122 @@
<?php
/*
* Job zur einmaligen Migration des Stundenplans
*
* Aufruf
* php index.ci.php system/MigrateKalender
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
class MigrateKalender extends CLI_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->load->model('ressource/Kalender_model', 'KalenderModel');
$this->load->model('ressource/Kalender_Lehreinheit_model', 'KalenderLehreinheitModel');
$this->load->model('ressource/Kalender_Ort_model', 'KalenderOrtModel');
$this->load->model('ressource/Stundenplandev_Kalender_model', 'SyncModel');
}
/**
* Everything has a beginning
*/
public function index()
{
$von = date('Y-m-d') // TODO
$bis = date('Y-m-d') // TODO
$db = new DB_Model();
$stpldevsql = '
SELECT *,
(SELECT beginn FROM lehre.tbl_stunde WHERE stunde=tbl_stundenplandev.stunde) as beginn,
(SELECT ende FROM lehre.tbl_stunde WHERE stunde=tbl_stundenplandev.stunde) as ende
FROM
lehre.tbl_stundenplandev WHERE datum>=? and datum<=? ORDER BY datum, stunde, unr';
$stpldev = $db->execReadOnlyQuery($stpldevsql, array($von, $bis));
if (hasData($stpldev))
{
// Pruefen ob der Eintrag schon in Sync Tabelle vorhanden ist
// Wenn neuere Änderungen vorhanden dann Update
// Wenn keine Änderungen seit leztem Sync dann Ueberspringen
// Wenn noch nicht vorhanden neu anlegen
// Danach ggf pruefen welceh Eintraege in der zwischenzeit geloescht wurden und
// in der neuen Tabelle auch archivieren oder loeschen
$data = getData($stpldev);
foreach($data as $rowstpl)
{
$SyncResult = $this->SyncModel->loadWhere(
array('stundenplandev_id' => $rowstpl->stundenplandev_id)
);
if(hasData($SyncResult))
{
//bereits vorhanden
// TODO Update
}
else
{
// Neuen Eintrag anlegen
$von = $rowstpl->datum.' '.$rowstpl->beginn;
$bis = $rowstpl->datum.' '.$rowstpl->ende;
$typ = 'lehreinheit';
$status = 'visible_student';
$insertamum = $rowstpl->insertamum;
$insertvon = $rowstpl->insertvon;
$updateamum = $rowstpl->updateamum;
$updatevon = $rowstpl->updatevon;
$resultKalenderInsert = $this->KalenderModel->insert(
array(
'von' => $von,
'bis' => $bis,
'typ' => $typ,
'status_kurzbz' => $status,
'vorgaenger_kalender_id' => null,
'insertamum' => $insertamum,
'insertvon' => $insertvon,
'updateamum' => $updateamum,
'updatevon' => $updatevon
)
);
if(isSuccess($resultKalenderInsert))
{
$kalender_id = getData($resultKalenderInsert);
$resultKalenderInsert = $this->KalenderLehreinheitModel->insert(
array(
'kalender_id' => $kalender_id,
'lehreinheit_id' => $rowstpl->lehreinheit_id,
)
);
$resultKalenderInsert = $this->KalenderOrtModel->insert(
array(
'kalender_id' => $kalender_id,
'ort_kurzbz' => $rowstpl->ort_kurzbz,
)
);
$resultSyncInsert = $this->SyncModel->insert(
array(
'stundenplandev_id' => $rowstpl->stundenplandev_id,
'kalender_id' => $kalender_id,
'lastupdate' => date('Y-m-d H:i:s')
)
);
}
}
}
}
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
if (! defined("BASEPATH")) exit("No direct script access allowed");
class KalenderLib
{
/**
* Loads model OrganisationseinheitModel
*/
public function __construct()
{
$this->ci =& get_instance();
$this->ci->load->model('ressource/Kalender_model', 'KalenderModel');
$this->ci->load->model('ressource/Kalender_Lehreinheit_model', 'KalenderLehreinheitModel');
$this->ci->load->model('ressource/Kalender_Ort_model', 'KalenderOrtModel');
$this->ci->load->model('education/Lehreinheit_model', 'LehreinheitModel');
$this->ci->load->model('education/Lehrveranstaltung_model', 'LehrveranstaltungModel');
$this->ci->load->model('education/LehreinheitMitarbeiter_model', 'LehreinheitMitarbeiterModel');
}
public function getRoomData($ort_kurzbz, $start_date, $end_date)
{
$data = $this->ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'kalender_id');
$data = $this->ci->KalenderModel->loadWhere(array(
'von >=' => $start_date,
'bis <= '=>$end_date,
'ort_kurzbz'=>$ort_kurzbz
));
$stundenplan_data = array();
if(isSuccess($data) && hasData($data))
{
$data = getData($data);
foreach($data as $rowstpl)
{
$obj = new stdClass();
$obj->type='lehreinheit';
$von = new DateTime($rowstpl->von);
$bis = new DateTime($rowstpl->bis);
$obj->beginn = $von->format('H:i:s');
$obj->ende = $bis->format('H:i:s');
$obj->datum = $von->format('Y-m-d');
$obj->topic = 'undefined';
$obj->lektor = array();
$obj->gruppe = array();
$obj->isostart = $von->format('c');
$obj->isoend = $bis->format('c');
$obj->tooltip = 'tip';
$obj->lehreinheit_id = array();
$lehreinheiten = $this->ci->KalenderLehreinheitModel->loadWhere(array('kalender_id'=>$rowstpl->kalender_id));
if(isSuccess($lehreinheiten) && hasData($lehreinheiten))
{
$lehreinheiten = getData($lehreinheiten);
foreach($lehreinheiten as $le)
{
$obj->lehreinheit_id[] = $le->lehreinheit_id;
$lehreinheitdata = $this->ci->LehreinheitModel->loadWhere(array('lehreinheit_id'=>$le->lehreinheit_id));
if(isSuccess($lehreinheitdata) && hasData($lehreinheitdata))
{
$ledata = getData($lehreinheitdata)[0];
$lvid = $ledata->lehrveranstaltung_id;
$lehrfach_id = $ledata->lehrfach_id;
$obj->lehrform = $ledata->lehrform_kurzbz;
$lehreinheitmitarbeiterdata = $this->ci->LehreinheitMitarbeiterModel->loadWhere(array('lehreinheit_id'=>$le->lehreinheit_id));
$lemitarbeiterdata = getData($lehreinheitmitarbeiterdata);
foreach($lemitarbeiterdata as $rowma)
{
$obj->lektor[] = array(
"mitarbeiter_uid"=> $rowma->mitarbeiter_uid,
"vorname"=>$rowma->mitarbeiter_uid,
"nachname"=>$rowma->mitarbeiter_uid,
"kurzbz"=>$rowma->mitarbeiter_uid
);
}
}
else
{
// TODO
}
}
}
$lehrfachdata = $this->ci->LehrveranstaltungModel->loadWhere(array('lehrveranstaltung_id' => $lehrfach_id));
$lfdata = getData($lehrfachdata)[0];
$lehrveranstaltungdata = $this->ci->LehrveranstaltungModel->loadWhere(array('lehrveranstaltung_id' => $lvid));
$lvdata = getData($lehrveranstaltungdata)[0];
$obj->topic = $lfdata->kurzbz.' '.$obj->lehrform;
$orte = $this->ci->KalenderOrtModel->loadWhere(array('kalender_id'=>$rowstpl->kalender_id));
$obj->ort_kurzbz = '';
if(isSuccess($orte) && hasData($orte))
{
$ortedata = getdata($orte);
foreach($ortedata as $ort);
{
$obj->ort_kurzbz .= $ort->ort_kurzbz;
}
}
$obj->titel = '';
$obj->lehrfach = $lfdata->kurzbz;
$obj->lehrfach_bez = $lfdata->bezeichnung;
$obj->organisationseinheit = $lvdata->oe_kurzbz;
$obj->farbe = $lfdata->farbe;
$obj->lehrveranstaltung_id = $lvid;
$obj->kalender_id = $rowstpl->kalender_id;
$stundenplan_data[] = $obj;
}
}
return $stundenplan_data;
}
public function addKalenderEvent($user, $ort_kurzbz, $start_date, $end_date, $lehreinheit_id)
{
$kalenderresult = $this->ci->KalenderModel->insert(array(
'von' => $start_date,
'bis' => $end_date,
'typ' => 'lehreinheit',
'status_kurzbz' => 'planning',
'insertvon' => $user,
'insertamum' => date('Y-m-d H:i:s')
));
if(isSuccess($kalenderresult) && hasData($kalenderresult))
{
$kalender_id = getData($kalenderresult);
$kalenderlehreinheitresult = $this->ci->KalenderLehreinheitModel->insert(array(
'kalender_id' => $kalender_id,
'lehreinheit_id' => $lehreinheit_id
));
if(isSuccess($kalenderlehreinheitresult))
{
$kalenderOrtresult = $this->ci->KalenderOrtModel->insert(array(
'kalender_id'=>$kalender_id,
'ort_kurzbz'=>$ort_kurzbz
));
}
}
}
public function updateKalenderEvent($user, $kalender_id, $ort_kurzbz, $start_date, $end_date)
{
/*TODO Checks:
Von-Tag muss gleich dem Bis-Tag sein
Bis darf nicht vor von liegen
History erstellen
Sync Status setzen
*/
$this->ci->KalenderModel->update($kalender_id,
array(
'von'=>$start_date,
'updateamum'=>date('Y-m-d H:i:s'),
'updatevon' => $user
)
);
return success();
}
}
@@ -0,0 +1,15 @@
<?php
class Kalender_Lehreinheit_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'lehre.tbl_kalender_lehreinheit';
$this->pk = array('kalender_id','lehreinheit_id');
$this->hasSequence = false;
}
}
@@ -0,0 +1,14 @@
<?php
class Kalender_Ort_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'lehre.tbl_kalender_ort';
$this->pk = 'kalender_ort_id';
}
}
@@ -0,0 +1,14 @@
<?php
class Kalender_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'lehre.tbl_kalender';
$this->pk = 'kalender_id';
}
}
@@ -0,0 +1,14 @@
<?php
class Stundenplandev_Kalender_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'sync.tbl_stundenplandev_kalender';
$this->pk = 'stundenplandev_kalender_id';
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
$includesArray = array(
'title' => 'Tempus',
'axios027' => true,
'bootstrap5' => true,
'fontawesome6' => true,
'vue3' => true,
'vuedatepicker11' => true,
'primevue3' => true,
'tabulator5' => true,
'phrases' => array(
'global',
'ui',
'notiz',
),
'customCSSs' => [
'public/css/components/vue-datepicker.css',
'public/css/components/primevue.css',
'public/css/components/calendar.css',
'public/css/Tempus.css'
],
'customJSs' => [
#'vendor/npm-asset/primevue/tree/tree.min.js',
#'vendor/npm-asset/primevue/toast/toast.min.js'
'vendor/moment/luxonjs/luxon.min.js'
],
'customJSModules' => [
'public/js/apps/Tempus.js'
]
);
$this->load->view('templates/FHC-Header', $includesArray);
?>
<div id="main">
<router-view
default-semester="<?= $variables['semester_aktuell']; ?>"
active-addons="<?= defined('ACTIVE_ADDONS') ? ACTIVE_ADDONS : ''; ?>"
tempus-root="<?= site_url('Tempus'); ?>"
cis-root="<?= CIS_ROOT; ?>"
:permissions="<?= htmlspecialchars(json_encode($permissions)); ?>"
:config="<?= htmlspecialchars(json_encode($variables)); ?>"
>
</router-view>
</div>
<?php $this->load->view('templates/FHC-Footer', $includesArray); ?>
+130
View File
@@ -0,0 +1,130 @@
@import './Fhc.css';
@import './components/searchbar/searchbar.css';
@import './components/verticalsplit.css';
@import './components/FilterComponent.css';
@import './components/Tabs.css';
@import './components/Notiz.css';
html {
font-size: .875em;
height: 100%;
}
body {
/*display: flex;*/
height: 100%;
}
.heightfull {
height: 95%;
}
.navbar-dark .navbar-brand:focus {
box-shadow: 0 0 0 .25rem rgba(13,110,253,.25);
z-index: 3;
}
#main {
height: 100%;
}
.tempus {
height: 100%;
}
.searchbar {
margin-right: 0!important;
}
.searchbar > .input-group {
margin-right: 0!important;
}
.searchbar > .input-group > * {
border-radius: 0!important;
}
#sidebarMenu {
width: 0%;
}
.tabulator-row.disabled.tabulator-row-odd .tabulator-cell {
color: var(--gray-400);
}
.tabulator-row.disabled.tabulator-row-even .tabulator-cell {
color: var(--gray-500);
}
/* Dropdown Toolbar Interessent, submenu */
.dropend .dropdown-toggle.d-flex::after {
height: 0;
}
@media (min-width: 768px) {
#sidebarMenu {
visibility: visible!important;
transform: none;
position: inherit;
z-index: 1;
}
}
.toast.toast-success {
color: #0f5132;
background-color: #d1e7dd!important;
border-color: #badbcc!important;
}
.toast.toast-danger {
color: #842029;
background-color: #f8d7da!important;
border-color: #f5c2c7!important;
}
.has-filter .fa-filter {
color: var(--bs-success);
}
#parkinglot {
border: 1px dashed;
width: 300px;
height: 100px;
color: #AAAAAA;
text-align: center;
font-size: large;
margin-top: 20px;
}
#coursechooser {
width: 300px;
min-height: 100px;
font-size: large;
margin-top: 50px;
border: 1px solid #ccc;
background: #eee;
text-align: left;
padding-left: 15px;
}
#coursechooserheader {
font-weight: bold;
font-size: medium;
}
#coursechooserfooter {
font-size: small;
color: #AAAAAA;
}
.eckerl {
width: 200px;
margin: 10px 0;
padding: 2px 4px;
background: #00649c;
color: #fff;
font-size: .85em;
cursor: pointer;
border-radius: 2px;
box-shadow: 3px 3px 3px #bbb;
}
:root{
--fhc-calendar-pane-height: calc(100vh - 120px);
}
.eckerltest {
box-shadow: 3px 3px 3px #ccc;
}
+10
View File
@@ -0,0 +1,10 @@
export default {
getCourses(searchfilter) {
return {
method: 'get',
url: '/api/frontend/v1/tempus/getCourses',
params: { searchfilter }
};
}
};
+60
View File
@@ -0,0 +1,60 @@
export default {
getRoomplan(ort_kurzbz, start_date, end_date) {
return {
method: 'get',
url: '/api/frontend/v1/Kalender/getRoomplan',
params: { ort_kurzbz, start_date, end_date }
};
},
getStundenplan(start_date, end_date) {
return {
method: 'get',
url: '/api/frontend/v1/Kalender/getStundenplan',
params: { start_date, end_date}
};
},
getStunden() {
return {
method: 'get',
url: '/api/frontend/v1/Kalender/Stunden',
params: {}
};
},
getOrtReservierungen(ort_kurzbz, start_date, end_date) {
return {
method: 'get',
url: `/api/frontend/v1/Kalender/Reservierungen/${ort_kurzbz}`,
params: { start_date, end_date}
};
},
getStundenplanReservierungen(start_date, end_date) {
return {
method: 'get',
url: '/api/frontend/v1/Kalender/Reservierungen',
params: { start_date, end_date }
};
},
getLehreinheitStudiensemester(lehreinheit_id) {
return {
method: 'get',
url: `/api/frontend/v1/Kalender/getLehreinheitStudiensemester/${lehreinheit_id}`,
params: {}
};
},
updateKalenderEvent(kalender_id, ort_kurzbz, start_date, end_date) {
return {
method: 'post',
url: '/api/frontend/v1/Kalender/updateKalenderEvent',
params: { kalender_id, ort_kurzbz, start_date, end_date}
};
},
addKalenderEvent(lehreinheit_id, ort_kurzbz, start_date, end_date) {
return {
method: 'post',
url: '/api/frontend/v1/Kalender/addKalenderEvent',
params: { lehreinheit_id, ort_kurzbz, start_date, end_date}
};
},
};
+43
View File
@@ -0,0 +1,43 @@
/**
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/
import FhcTempus from "../components/Tempus/Tempus.js";
import fhcapifactory from "./api/fhcapifactory.js";
import Phrasen 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(),
routes: [
{ path: `/${ciPath}/Tempus`, component: FhcTempus },
]
});
const app = Vue.createApp();
app
.use(router)
.use(primevue.config.default, {
zIndex: {
overlay: 1100
}
})
.use(Phrasen)
.mount('#main');
+168
View File
@@ -0,0 +1,168 @@
import FhcCalendar from "./Base.js";
import ApiLvPlan from '../../api/factory/lvPlan.js';
import { useEventLoader } from '../../composables/EventLoader.js';
import ModeDay from './Mode/Day.js';
import ModeWeek from './Mode/Week.js';
import ModeMonth from './Mode/Month.js';
export default {
name: "CalendarTempus",
components: {
FhcCalendar
},
inject: [
"renderers"
],
props: {
timezone: {
type: String,
required: true
},
date: {
type: [Date, String, Number, luxon.DateTime],
default: luxon.DateTime.local()
},
mode: {
type: String,
default: 'Week'
},
getPromiseFunc: {
type: Function,
required: true
}
},
emits: [
"update:date",
"update:mode",
"update:range",
"drop"
],
data() {
return {
modes: {
week: Vue.markRaw(ModeWeek),
month: Vue.markRaw(ModeMonth)
},
modeOptions: {
day: {
emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')),
emptyMessageDetails: Vue.computed(() => this.$p.t('lehre/noLvFound'))
},
week: {
collapseEmptyDays: false
}
},
teachingunits: null
};
},
computed: {
backgrounds() {
let now = luxon.DateTime.now().setZone(this.timezone);
if (this.mode == 'Month')
return [
{
class: 'background-past',
end: now.startOf('day')
}
];
return [
{
class: 'background-past',
end: now,
label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false })
}
];
}
},
methods: {
eventStyle(event) {
if (!event.farbe)
return undefined;
return '--event-bg:#' + event.farbe;
},
updateRange(rangeInterval) {
this.rangeInterval = rangeInterval;
this.$emit('update:range', rangeInterval);
},
ondrop(e, start, end){
this.$emit('drop', e, start, end);
}
},
setup(props, context) {
const rangeInterval = Vue.ref(null);
const { events, lv } = useEventLoader(rangeInterval, props.getPromiseFunc);
Vue.watch(lv, newValue => {
context.emit('update:lv', newValue);
});
return {
rangeInterval,
events,
lv
};
},
created() {
this.$api
.call(ApiLvPlan.getStunden())
.then(res => {
return this.teachingunits = res.data.map(el => ({
id: el.stunde,
start: el.beginn,
end: el.ende
}));
});
},
template: /* html */`
<fhc-calendar
ref="calendar"
class="fhc-calendar-lvplan"
:date="date"
:modes="modes"
:mode-options="modeOptions"
:mode="mode"
:timezone="timezone"
:locale="$p.user_locale.value"
:events="events || []"
:backgrounds="backgrounds"
:time-grid="teachingunits"
show-btns
@drop="ondrop"
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
@update:mode="(newMode, newDate) => $emit('update:mode', newMode, newDate)"
@update:range="updateRange"
>
<template v-slot="{ event, mode }">
<div
:class="'event-type-' + event.type + ' ' + mode + 'PageContainer'"
:type="mode == 'day' ? 'button' : undefined"
:style="eventStyle(event)"
>
<component
v-if="mode == 'event'"
:is="renderers[event.type]?.modalContent"
:event="event"
></component>
<component
v-else-if="mode == 'eventheader'"
:is="renderers[event.type]?.modalTitle"
:event="event"
></component>
<component
v-else
:is="renderers[event.type]?.calendarEvent"
:event="event"
></component>
</div>
</template>
<template #actions>
<slot />
</template>
</fhc-calendar>`
}
@@ -0,0 +1,89 @@
import ApiCoursePicker from '../../api/factory/coursepicker.js';
export default {
components: {
},
provide() {
return {
};
},
data() {
return {
courses: null
}
},
props: {
},
computed: {
},
methods: {
loadCourses: function(){
Promise.allSettled([
this.$api.call(ApiCoursePicker.getCourses("test")),
]).then((result) => {
let promise_events = [];
result.forEach((promise_result) => {
if (promise_result.status === 'fulfilled' && promise_result.value.meta.status === "success") {
let data = promise_result.value.data;
if (data && data.forEach) {
data.forEach((entry, i) => {
entry.showname = entry.studiengang_kurzbz+entry.semester+' - ' + entry.kurzbz + ' ' + entry.lektoren.toString();
entry.tag = '';
entry.mode = 'single';
});
}
promise_events = promise_events.concat(data);
}
})
this.courses = promise_events;
});
},
dragstart: function(evt, course) {
const transferdata = {
type: 'lehreinheit',
id: course.lehreinheit_id,
mode: course.mode
};
event.dataTransfer.setData('text', JSON.stringify(transferdata));
},
keydown: function(evt, course) {
switch(evt.key)
{
case "1":
course.tag = '#Singleweek';
course.mode = 'single';
break
case "2":
course.tag = '#Multiweek';
course.mode = 'multi';
break
}
}
},
created() {
this.loadCourses();
//document.addEventListener('keydown', function () { console.log("a"); });
},
mounted() {
},
template: /*html*/`
<div ref="container" class="fhc-coursechooser">
<div id="coursechooser">
<span id="coursechooserheader">Course</span>
<input type="text" placeholder="Search"/>
<div v-for="course in courses" class="eckerl" draggable="true" @dragstart="dragstart(event, course)" tabindex="0" @keyup="keydown($event, course)">
{{course.showname}}
{{course.tag}}
</div>
<span id="coursechooserfooter">Drag & Drop on Calender</span>
</div>
</div>`
}
+287
View File
@@ -0,0 +1,287 @@
/**
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/
import VueDatePicker from '../vueDatepicker.js.php'
import CoreSearchbar from "../searchbar/searchbar.js";
import VerticalSplit from "../verticalsplit/verticalsplit.js";
import FhcCalendar from "../Calendar/Tempus.js";
import FhcCoursepicker from "../Tempus/Coursepicker.js";
import ApiKalender from '../../api/factory/kalender.js';
import ApiSearchbar from "../../api/factory/searchbar.js";
import ApiRenderers from '../../api/factory/renderers.js';
export default {
name: "Tempus",
components: {
CoreSearchbar,
VerticalSplit,
FhcCalendar,
FhcCoursepicker
},
props: {
defaultSemester: String,
config: Object,
permissions: Object,
tempusRoot: String,
cisRoot: String,
activeAddons: String, // semicolon separated list of active addons
viewData: Object,
},
provide() {
return {
cisRoot: this.cisRoot,
defaultSemester: this.defaultSemester,
$reloadList: () => {
this.$refs.stvList.reload();
},
renderers: Vue.computed(() => this.renderers),
}
},
data() {
return {
selected: [],
searchbaroptions: {
origin: 'tempus',
cssclass: "position-relative",
calcheightonly: true,
types: [
//"student",
"raum",
//"mitarbeiter"
],
actions: {
raum: {
defaultaction: {
type: "function",
action: this.setOrt
},
childactions: [
]
}
}
},
lv_id: null,
events: null,
minimized: false,
calendarDate: luxon.DateTime.local(), //new CalendarDate(new Date()),
currentlySelectedEvent: null,
//currentDay: new Date(),
studiensemesterKurzbz: this.defaultSemester,
lists: {
nations: [],
sprachen: [],
geschlechter: []
},
renderers: null,
ort_kurzbz: 'EDV_A5.08',
}
},
methods: {
setOrt: function(data)
{
// Wenn bei der Suche ein Ort ausgewaehlt wird, dann wir der Ort gesetzt und ein Reload getriggert durch den watcher
this.ort_kurzbz = data.ort_kurzbz;
},
handleChangeDate() {
},
handleChangeMode() {
},
searchfunction(params) {
return this.$api.call(ApiSearchbar.search(params));
},
getPromiseFunc(start, end) {
return [
this.$api.call(ApiKalender.getRoomplan(this.ort_kurzbz, '2025-10-01','2025-10-30')),//start.toISODate(), end.toISODate())),
];
},
parkingdrop: function(evt)
{
evt.preventDefault();
var data = JSON.parse(evt.dataTransfer.getData("text"));
alert('parked Data:'+data.id);
console.log(data);
},
dropHandler: function(event, start, end)
{
let day = start.date.toFormat('yyyy-MM-dd');
let time = start.date.toFormat('hh:mm');
let dropdata = JSON.parse(event.dataTransfer.getData('text'))
if(dropdata.type=='kalender')
{
let kalender_id = dropdata.id;
Promise.allSettled([
this.$api.call(ApiKalender.updateKalenderEvent(kalender_id, this.ort_kurzbz, day+' '+time, null))
]).then((result) => {
let promise_events = [];
result.forEach((promise_result) => {
if (promise_result.status === 'fulfilled' && promise_result.value.meta.status === "success")
{
// TODO - reload
}
})
});
}
else if(dropdata.type=='lehreinheit')
{
// TODO Calculate end time
let lehreinheit_id = dropdata.id;
let start_time = day+' '+time;
let end_time = start.date.plus({ minutes: 45 }).toFormat('yyyy-MM-dd hh:mm');
alert("mode:"+dropdata.mode);
Promise.allSettled([
this.$api.call(ApiKalender.addKalenderEvent(lehreinheit_id, this.ort_kurzbz, start_time, end_time))
]).then((result) => {
let promise_events = [];
result.forEach((promise_result) => {
if (promise_result.status === 'fulfilled' && promise_result.value.meta.status === "success") {
// TODO - reload
}
})
});
}
else
{
alert("Unbekannte Daten gedroppt");
}
},
onRightClick: function(evt) {
this.$refs.EventContextMenu.show(evt);
}
},
watch: {
ort_kurzbz: function (newValue, oldValue) {
// Raumansicht laden wenn der Ort geaendert wird
}
},
computed: {
currentDay() {
return luxon.DateTime.now().setZone(this.config.timezone).toISODate();
},
currentMode() {
return 'week';
},
},
async created()
{
await this.$api
.call(ApiRenderers.loadRenderers())
.then(res => res.data)
.then(data => {
for (let rendertype of Object.keys(data)) {
let modalTitle = null;
let modalContent = null;
let calendarEvent = null;
if (data[rendertype].modalTitle)
modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalTitle)));
if (data[rendertype].modalContent)
modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].modalContent)));
if (data[rendertype].calendarEvent)
calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(data[rendertype].calendarEvent)));
if (data[rendertype].calendarEventStyles){
var head = document.head;
if(!head.querySelector(`link[href="${data[rendertype].calendarEventStyles}"]`)){
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = data[rendertype].calendarEventStyles;
head.appendChild(link);
}
}
if(this.renderers === null) {
this.renderers = {};
}
if (!this.renderers[rendertype]) {
this.renderers[rendertype] = {}
}
this.renderers[rendertype].modalTitle = modalTitle;
this.renderers[rendertype].modalContent = modalContent;
this.renderers[rendertype].calendarEvent = calendarEvent;
}
});
},
mounted() {
},
template: `
<div class="tempus">
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-4 col-lg-3 col-xl-2 me-0 px-3">Tempus</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="#">Config</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Issues</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Reports
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">AZG Verletzungen</a></li>
<li><a class="dropdown-item" href="#">Raumauslastung</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">LektorInnenliste</a></li>
</ul>
</li>
</ul>
</div>
<core-searchbar :searchoptions="searchbaroptions" :searchfunction=searchfunction class="searchbar w-100"></core-searchbar>
</header>
<div class="container-fluid overflow-hidden heightfull">
<div class="row h-100">
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<div style="float: left">
<fhc-coursepicker></fhc-coursepicker>
<div id="parkinglot" ondragover="event.preventDefault();" @drop="parkingdrop">
<br />
<i class="fa-solid fa-square-parking"></i><br />
<span>Drag here to park</span>
</div>
<br />
Raum <input type="text" v-model="ort_kurzbz">
</div>
</nav>
<main class="col-md-8 ms-sm-auto col-lg-9 col-xl-10">
<fhc-calendar
ref="calendar"
:timezone="config.timezone"
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
@drop="dropHandler"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
class="responsive-calendar"
/>
</main>
</div>
</div>
</div>`
};
+1
View File
@@ -84,6 +84,7 @@ 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');
require_once('dbupdate_3.4/46975_tempus.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
+134
View File
@@ -0,0 +1,134 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
// Kalender Tabelle fuer neues Tempus
if(!$result = @$db->db_query("SELECT kalender_id FROM lehre.tbl_kalender LIMIT 1"))
{
$qry = "CREATE TABLE lehre.tbl_kalender (
kalender_id bigserial NOT NULL,
von timestamp NOT NULL,
bis timestamp NOT NULL,
typ character varying(32),
status_kurzbz character varying(32),
vorgaenger_kalender_id bigint,
insertamum timestamp DEFAULT now(),
insertvon character varying(32),
updateamum timestamp DEFAULT now(),
updatevon character varying(32),
CONSTRAINT tbl_kalender_pk PRIMARY KEY (kalender_id)
);
COMMENT ON TABLE lehre.tbl_kalender IS 'Schedule Calendar Events';
CREATE TABLE lehre.tbl_kalender_typ (
typ character varying(32) NOT NULL,
CONSTRAINT tbl_kalender_typ_pk PRIMARY KEY (typ)
);
COMMENT ON TABLE lehre.tbl_kalender_typ IS 'Type of Calendar Events';
INSERT INTO lehre.tbl_kalender_typ (typ) VALUES (E'lehreinheit');
INSERT INTO lehre.tbl_kalender_typ (typ) VALUES (E'reservierung');
INSERT INTO lehre.tbl_kalender_typ (typ) VALUES (E'event');
CREATE TABLE lehre.tbl_kalender_lehreinheit (
lehreinheit_id integer NOT NULL,
kalender_id bigint NOT NULL,
CONSTRAINT tbl_kalender_lehreinheit_pk PRIMARY KEY (lehreinheit_id,kalender_id)
);
COMMENT ON TABLE lehre.tbl_kalender_lehreinheit IS 'Connects Calender Events to Courses';
ALTER TABLE lehre.tbl_kalender_lehreinheit ADD CONSTRAINT tbl_lehreinheit_fk FOREIGN KEY (lehreinheit_id)
REFERENCES lehre.tbl_lehreinheit (lehreinheit_id) MATCH FULL
ON DELETE RESTRICT ON UPDATE CASCADE;
CREATE TABLE lehre.tbl_kalender_ort (
kalender_ort_id bigserial NOT NULL,
location text,
ort_kurzbz character varying(32),
kalender_id bigint,
CONSTRAINT tbl_kalender_ort_pk PRIMARY KEY (kalender_ort_id)
);
COMMENT ON TABLE lehre.tbl_kalender_ort IS E'Connects one Calendar Entry to multiple Rooms';
COMMENT ON COLUMN lehre.tbl_kalender_ort.location IS E'Text Description if not a physical inhouse Room (External Location, Conference Link, etc)';
ALTER TABLE lehre.tbl_kalender_ort ADD CONSTRAINT tbl_kalender_fk FOREIGN KEY (kalender_id)
REFERENCES lehre.tbl_kalender (kalender_id) MATCH FULL
ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE lehre.tbl_kalender_ort ADD CONSTRAINT tbl_ort_fk FOREIGN KEY (ort_kurzbz)
REFERENCES public.tbl_ort (ort_kurzbz) MATCH FULL
ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE lehre.tbl_kalender_lehreinheit ADD CONSTRAINT tbl_kalender_fk FOREIGN KEY (kalender_id)
REFERENCES lehre.tbl_kalender (kalender_id) MATCH FULL
ON DELETE RESTRICT ON UPDATE CASCADE;
CREATE TABLE lehre.tbl_kalender_status (
status_kurzbz character varying(32) NOT NULL,
bezeichnung text,
CONSTRAINT tbl_kalender_status_pk PRIMARY KEY (status_kurzbz)
);
COMMENT ON TABLE lehre.tbl_kalender_status IS 'Calender visibility Status';
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'planning', E'planning');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'tosync', E'tosync');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'todelete', E'todelete');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'visible_lektor', E'Sichtbar für Lektoren');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'deleted', E'deleted');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'archived', E'archived');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'visible_student', E'Sichtbar für Studierende');
ALTER TABLE lehre.tbl_kalender ADD CONSTRAINT tbl_kalender_status_fk FOREIGN KEY (status_kurzbz)
REFERENCES lehre.tbl_kalender_status (status_kurzbz) MATCH FULL
ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE lehre.tbl_kalender ADD CONSTRAINT tbl_kalender_typ_fk FOREIGN KEY (typ)
REFERENCES lehre.tbl_kalender_typ (typ) MATCH FULL
ON DELETE RESTRICT ON UPDATE CASCADE;
GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender to vilesci;
GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender to web;
GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender_status to vilesci;
GRANT SELECT ON lehre.tbl_kalender_status to web;
GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender_lehreinheit to vilesci;
GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender_lehreinheit to web;
GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender_ort to vilesci;
GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender_ort to web;
GRANT SELECT, UPDATE, INSERT, DELETE ON lehre.tbl_kalender_typ to vilesci;
GRANT SELECT ON lehre.tbl_kalender_typ to web;
CREATE TABLE sync.tbl_stundenplandev_kalender(
stundenplandev_kalender_id bigserial NOT NULL,
stundenplandev_id integer NOT NULL,
kalender_id bigint NOT NULL,
lastupdate timestamp,
CONSTRAINT tbl_stundenplandev_kalender_pk PRIMARY KEY (stundenplandev_kalender_id)
);
GRANT SELECT, UPDATE, INSERT, DELETE ON sync.tbl_stundenplandev_kalender to vilesci;
COMMENT ON TABLE sync.tbl_stundenplandev_kalender IS 'Migration from old Stundenplan to new Kalender Table';
GRANT USAGE ON lehre.tbl_kalender_kalender_id_seq TO vilesci;
GRANT USAGE ON lehre.tbl_kalender_kalender_id_seq TO web;
GRANT USAGE ON sync.tbl_stundenplandev_kalender_stundenplandev_kalender_id_seq TO vilesci;
GRANT USAGE ON lehre.tbl_kalender_ort_kalender_ort_id_seq TO vilesci;
CREATE INDEX idx_kalender_ort_kalender_id ON lehre.tbl_kalender_ort USING btree (kalender_id);
CREATE INDEX idx_kalender_ort_kalender_id_ort_kurzbz ON lehre.tbl_kalender_ort USING btree (ort_kurzbz, kalender_id);
CREATE INDEX idx_kalender_von ON lehre.tbl_kalender USING btree (von);
";
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_kalender: '.$db->db_last_error().'</strong><br>';
else
echo '<br>lehre.tbl_kalender: neue Tabellen hinzugefuegt';
}