mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-16 22:42:16 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61982b1199 | |||
| d50590aefb | |||
| c40ee917df | |||
| 8126665acd | |||
| 5826bf5b70 | |||
| fc5ba954fb | |||
| 4e9bf3676d | |||
| 31410f0bb9 | |||
| 1136d9dca1 | |||
| 967f53d73c | |||
| 8c24292195 | |||
| aba680cd52 |
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Configs for the Long Run Tasks
|
||||
*/
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
// Maximum LRTs for a single user in parallel
|
||||
$config['lrt_max_number_single_user'] = 10;
|
||||
|
||||
// Maximum LRTs for the whole system in parallel
|
||||
$config['lrt_max_number_system'] = 100;
|
||||
|
||||
// Maximum running time in hours for a single LRT before killing it
|
||||
$config['lrt_max_run_timeout'] = 24;
|
||||
|
||||
// List of existing LRT types
|
||||
$config['lrt_types'] = array('LRTDummy');
|
||||
|
||||
@@ -170,13 +170,6 @@ $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']
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
@@ -9,10 +9,9 @@ class Detailheader extends FHCAPI_Controller
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct([
|
||||
'getHeader' => self::PERM_LOGGED,
|
||||
'getPersonAbteilung' => self::PERM_LOGGED,
|
||||
'getLeitungOrg' => self::PERM_LOGGED,
|
||||
'getSemesterStati' => self::PERM_LOGGED,
|
||||
'getHeader' => ['vertrag/mitarbeiter:r'],
|
||||
'getPersonAbteilung' => ['vertrag/mitarbeiter:r'],
|
||||
'getLeitungOrg' => ['vertrag/mitarbeiter:r'],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -49,17 +48,6 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -246,12 +246,12 @@ class Abschlusspruefung extends FHCAPI_Controller
|
||||
{
|
||||
$searchString = $this->input->get('searchString') ?? '';
|
||||
|
||||
$this->load->model('person/Person_model', 'PersonModel');
|
||||
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
|
||||
|
||||
$result = $this->PersonModel->searchPerson($searchString, 'mitMaUid');
|
||||
$result = $this->MitarbeiterModel->searchMitarbeiter($searchString, 'ohneMaUid');
|
||||
|
||||
if (isError($result)) {
|
||||
$this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
|
||||
$this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
|
||||
}
|
||||
|
||||
$this->terminateWithSuccess($result ?: []);
|
||||
|
||||
@@ -144,7 +144,6 @@ 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');
|
||||
|
||||
@@ -439,8 +439,6 @@ 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);
|
||||
|
||||
@@ -558,7 +556,6 @@ 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');
|
||||
@@ -758,7 +755,6 @@ 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);
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
if (!defined("BASEPATH")) exit("No direct script access allowed");
|
||||
|
||||
/**
|
||||
* - This controller acts as interface of the LongRunTaskLib that contains
|
||||
* all the needed functionalities to operate with the Long Run Task system
|
||||
* that is built on top of the Jobs Queue System
|
||||
* - This is a Job Queue Worker that gets scheduled LRTs from the queue and executes them
|
||||
* - Once all the LRTs have been started checks if there are LRTs that are running for too long and kills them
|
||||
*/
|
||||
class LongRunTaskExecJob extends JOB_Controller
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Changes the needed configs for LogLib
|
||||
$this->LogLibJob->setConfigs(
|
||||
array(
|
||||
'dbExecuteUser' => get_class($this),
|
||||
'requestId' => 'LRT'
|
||||
)
|
||||
);
|
||||
|
||||
// Loads LongRunTaskLib library
|
||||
$this->load->library('LongRunTaskLib');
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all the new LRTs
|
||||
*/
|
||||
public function execEmAll()
|
||||
{
|
||||
$this->logInfo('Execute long run tasks started');
|
||||
|
||||
// Get all the LRTs that is possible to execute now
|
||||
$lrtsResult = $this->longruntasklib->getNewLRTs();
|
||||
// If an error occurred then return it
|
||||
if (isError($lrtsResult)) return $lrtsResult;
|
||||
|
||||
if (hasData($lrtsResult))
|
||||
{
|
||||
// For each LRT
|
||||
foreach (getData($lrtsResult) as $lrt)
|
||||
{
|
||||
// Execute the task
|
||||
$executeLrtResult = $this->longruntasklib->executeLrt($lrt);
|
||||
if (isError($executeLrtResult)) $this->logError($executeLrtResult);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logInfo('Execute long run tasks ended');
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills all the hanging LRTs
|
||||
*/
|
||||
public function killHangingLRTs()
|
||||
{
|
||||
$this->logInfo('Kill hanging LRTs started');
|
||||
|
||||
// Get all the LRTs that is possible to execute now
|
||||
$lrtsResult = $this->longruntasklib->getHangingLRTs();
|
||||
// If an error occurred then return it
|
||||
if (isError($lrtsResult)) return $lrtsResult;
|
||||
|
||||
if (hasData($lrtsResult))
|
||||
{
|
||||
// For each LRT
|
||||
foreach (getData($lrtsResult) as $lrt)
|
||||
{
|
||||
// Kill the task
|
||||
$killLrtResult = $this->longruntasklib->killLrt($lrt);
|
||||
if (isError($killLrtResult)) $this->logError($killLrtResult);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logInfo('Kill hanging LRTs ended');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function checkExecution()
|
||||
{
|
||||
$this->logInfo('Check execution long run tasks started');
|
||||
|
||||
// Get the related LRT data from the queue
|
||||
$lrtsResult = $this->longruntasklib->getRunningLRTs();
|
||||
// If an error occurred then return it
|
||||
if (isError($lrtsResult)) return $lrtsResult;
|
||||
|
||||
// If there are running LRTs
|
||||
if (hasData($lrtsResult))
|
||||
{
|
||||
// For each LRT
|
||||
foreach (getData($lrtsResult) as $lrt)
|
||||
{
|
||||
// Check the LRT execution
|
||||
$checkExecutionResult = $this->longruntasklib->checkExecution($lrt);
|
||||
if (isError($checkExecutionResult)) $this->logError($checkExecutionResult);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logInfo('Check execution long run tasks ended');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
if (!defined("BASEPATH")) exit("No direct script access allowed");
|
||||
|
||||
/**
|
||||
* Testing LRT to check if the LRT system is working properly
|
||||
* This will be called by the LongRunTaskExecJob
|
||||
*/
|
||||
class LRTDummy extends LRT_Controller
|
||||
{
|
||||
/**
|
||||
* This method must be implemented!
|
||||
*/
|
||||
public function run($jobid)
|
||||
{
|
||||
$this->logInfo('Long run tasks '.get_class($this).' started');
|
||||
|
||||
$this->_doIt($jobid);
|
||||
|
||||
$this->logInfo('Long run tasks '.get_class($this).' ended');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops on the number of seconds provided by the LRT input
|
||||
* Sleeps every time 1 sec
|
||||
* Writes the progress
|
||||
* Writes the output
|
||||
*/
|
||||
private function _doIt($jobid)
|
||||
{
|
||||
// Get the LRT record related to the provided jobid
|
||||
$lrtResult = $this->getLrt($jobid);
|
||||
|
||||
// If an error occurred or the record has not been found
|
||||
if (isError($lrtResult))
|
||||
{
|
||||
$this->logError($lrtResult);
|
||||
return;
|
||||
}
|
||||
if (!hasData($lrtResult))
|
||||
{
|
||||
$this->logError('LRT not found in database');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the record
|
||||
$lrt = getData($lrtResult)[0];
|
||||
|
||||
// Get and check the input
|
||||
$input = json_decode($lrt->{LongRunTaskLib::PROPERTY_INPUT});
|
||||
if ($input == null)
|
||||
{
|
||||
$this->logError('LRT input is not a valid json');
|
||||
return;
|
||||
}
|
||||
|
||||
// Operation
|
||||
for ($i = 0; $i < (int)$input->sleep; $i++)
|
||||
{
|
||||
sleep(1);
|
||||
// Set the progress
|
||||
$setProgressResult = $this->setProgress($jobid, (($i + 1) / (int)$input->sleep) * 100);
|
||||
if (isError($setProgressResult))
|
||||
{
|
||||
$this->logError($setProgressResult);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$sleepMsg = 'The user '.$lrt->{LongRunTaskLib::PROPERTY_UID}.' slept for '.$input->sleep.' seconds';
|
||||
|
||||
$this->logInfo($sleepMsg);
|
||||
|
||||
// Set the output
|
||||
$setOutputResult = $this->setOutput($jobid, $sleepMsg);
|
||||
if (isError($setOutputResult))
|
||||
{
|
||||
$this->logError($setOutputResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,19 +198,7 @@ class Gradelist extends Auth_Controller
|
||||
if (!isset($row_noten->found))
|
||||
{
|
||||
$result_lv = $this->LehrveranstaltungModel->load($row_noten->lehrveranstaltung_id);
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
$result_stg = $this->StudiengangModel->load($result_lv->retval[0]->studiengang_kz);
|
||||
$courses['semester'][$row_noten->studiensemester_kurzbz]['lvs_nonstpl'][] = array(
|
||||
'lehrveranstaltung_id' => $row_noten->lehrveranstaltung_id,
|
||||
'lehrtyp_kurzbz' => $result_lv->retval[0]->lehrtyp_kurzbz,
|
||||
@@ -224,8 +212,8 @@ class Gradelist extends Auth_Controller
|
||||
'semester' => $result_lv->retval[0]->semester,
|
||||
'note' => $row_noten->note,
|
||||
'datum' => $row_noten->benotungsdatum,
|
||||
'studiengang_kurzbz' => $studiengang_kz,
|
||||
'zugeordnet' => true
|
||||
'zugeordnet' => true,
|
||||
'studiengang_kurzbz' => $result_stg->retval[0]->kurzbzlang
|
||||
);
|
||||
if(!isset($courses['semester'][$row_noten->studiensemester_kurzbz]['data']['ectssumme_nonstpl']))
|
||||
$courses['semester'][$row_noten->studiensemester_kurzbz]['data']['ectssumme_nonstpl'] = 0;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
if (!defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
use \stdClass as stdClass;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class LRTTest extends Auth_Controller
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'system/developer:r',
|
||||
'lrt1min' => 'system/developer:r',
|
||||
)
|
||||
);
|
||||
|
||||
// Loads LongRunTaskLib library
|
||||
$this->load->library('LongRunTaskLib');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
/**
|
||||
* Everything has a beginning
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->load->view('system/lrtTest.php');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function lrt1min()
|
||||
{
|
||||
$lrtInput = new stdClass();
|
||||
$lrtInput->sleep = 1; // Sleep for 1 min
|
||||
|
||||
$this->outputJsonSuccess(
|
||||
$this->longruntasklib->addNewLrtToQueue(
|
||||
'LRTDummy', // LRT type
|
||||
getAuthUID(), // UID executer
|
||||
$lrtInput // LRT input
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
if (!defined("BASEPATH")) exit("No direct script access allowed");
|
||||
|
||||
/**
|
||||
* Long Run Task
|
||||
*
|
||||
* - This controller acts as interface of the LongRunTaskLib that contains
|
||||
* all the needed functionalities to operate with the Long Run Task system
|
||||
* that is built on top of the Jobs Queue System
|
||||
* - This is an abstract class that provide basic functionalities,
|
||||
* it has to be extended to broaden its logic
|
||||
* - Any implementation of a Long Run Task should extends this class to
|
||||
* properly operate with the LRT system
|
||||
*/
|
||||
abstract class LRT_Controller extends JOB_Controller
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Changes the needed configs for LogLib
|
||||
$this->LogLibJob->setConfigs(
|
||||
array(
|
||||
'dbExecuteUser' => get_class($this),
|
||||
'requestId' => 'LRT'
|
||||
)
|
||||
);
|
||||
|
||||
// Loads LongRunTaskLib library
|
||||
$this->load->library('LongRunTaskLib');
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
abstract public function run($jobid);
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Protected methods
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function getLrt($jobid)
|
||||
{
|
||||
return $this->longruntasklib->getLrt($jobid);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setProgress($jobid, $progress)
|
||||
{
|
||||
return $this->longruntasklib->setProgress($jobid, $progress);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setOutput($jobid, $output)
|
||||
{
|
||||
return $this->longruntasklib->setOutuput($jobid, $output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
if (!defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
use \stdClass as stdClass;
|
||||
|
||||
/**
|
||||
* Library that contains all the needed functionalities to operate with the Jobs Queue System
|
||||
*/
|
||||
@@ -24,12 +26,12 @@ class JobsQueueLib
|
||||
const PROPERTY_END_TIME = 'endtime';
|
||||
const PROPERTY_ERROR = 'error';
|
||||
|
||||
private $_ci; // CI instance
|
||||
protected $_ci; // CI instance
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($authenticate = true)
|
||||
public function __construct()
|
||||
{
|
||||
// Gets CI instance
|
||||
$this->_ci =& get_instance();
|
||||
@@ -62,14 +64,29 @@ class JobsQueueLib
|
||||
*/
|
||||
public function getOldestJobs($type, $maxAmount = null)
|
||||
{
|
||||
$this->_ci->JobsQueueModel->resetQuery();
|
||||
|
||||
$this->_ci->JobsQueueModel->addOrder('creationtime', 'ASC');
|
||||
$types = $type; // default is array
|
||||
$limit = 0; // default query limit
|
||||
|
||||
// If the maximum amount of jobs to retrieve have been specified
|
||||
if (is_numeric($maxAmount)) $this->_ci->JobsQueueModel->addLimit($maxAmount);
|
||||
if (is_numeric($maxAmount)) $limit = $maxAmount;
|
||||
|
||||
return $this->_ci->JobsQueueModel->loadWhere(array('status' => self::STATUS_NEW, 'type' => $type));
|
||||
// If it is a string then include it into an array
|
||||
if (!isEmptyString($type) && is_string($type)) $types = array($type);
|
||||
|
||||
// Return the result of the query
|
||||
return $this->_ci->JobsQueueModel->execReadOnlyQuery('
|
||||
SELECT jq.*
|
||||
FROM system.tbl_jobsqueue jq
|
||||
WHERE jq.type IN ?
|
||||
AND jq.status = ?
|
||||
ORDER BY jq.creationtime DESC
|
||||
LIMIT ?',
|
||||
array(
|
||||
$types,
|
||||
self::STATUS_NEW,
|
||||
$limit
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,11 +94,23 @@ class JobsQueueLib
|
||||
*/
|
||||
public function getJobsByTypeStatus($type, $status)
|
||||
{
|
||||
$this->_ci->JobsQueueModel->resetQuery();
|
||||
$types = $type; // default is array
|
||||
|
||||
$this->_ci->JobsQueueModel->addOrder('creationtime', 'DESC');
|
||||
// If it is a string then include it into an array
|
||||
if (!isEmptyString($type) && is_string($type)) $types = array($type);
|
||||
|
||||
return $this->_ci->JobsQueueModel->loadWhere(array('status' => $status, 'type' => $type));
|
||||
// Return the result of the query
|
||||
return $this->_ci->JobsQueueModel->execReadOnlyQuery('
|
||||
SELECT jq.*
|
||||
FROM system.tbl_jobsqueue jq
|
||||
WHERE jq.type IN ?
|
||||
AND jq.status = ?
|
||||
ORDER BY jq.creationtime DESC',
|
||||
array(
|
||||
$types,
|
||||
$status
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,8 +282,30 @@ class JobsQueueLib
|
||||
return array($job);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Protected methods
|
||||
|
||||
/**
|
||||
* Checks if the given job contains a valid type
|
||||
*/
|
||||
protected function _checkJobType($type, $types)
|
||||
{
|
||||
return $this->_inArray($type, $types, self::PROPERTY_TYPE);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* Drop not allowed properties from the given job
|
||||
*/
|
||||
private function _dropNotAllowedPropertiesNewJob(&$job)
|
||||
{
|
||||
unset($job->{self::PROPERTY_JOBID});
|
||||
unset($job->{self::PROPERTY_CREATIONTIME});
|
||||
unset($job->{self::PROPERTY_TYPE});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the job object structure when needed for insert
|
||||
@@ -281,34 +332,6 @@ class JobsQueueLib
|
||||
return false; // better sorry than wrong
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the job object structure when needed for update
|
||||
*/
|
||||
private function _checkUpdateJobStructure(&$job)
|
||||
{
|
||||
// If job is a valid object
|
||||
if (is_object($job) && property_exists($job, self::PROPERTY_JOBID)) return true; // it is valid!
|
||||
|
||||
// If not object then object it!
|
||||
if (!is_object($job)) $job = new stdClass();
|
||||
|
||||
// If an error property was not already previously stored then store an error message in job object
|
||||
if (!property_exists($job, self::PROPERTY_ERROR))
|
||||
{
|
||||
$job->{self::PROPERTY_ERROR} = 'The structure of the provided job is not valid';
|
||||
}
|
||||
|
||||
return false; // better sorry than wrong
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given job contains a valid type
|
||||
*/
|
||||
private function _checkJobType($type, $types)
|
||||
{
|
||||
return $this->_inArray($type, $types, self::PROPERTY_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given job contains a valid status
|
||||
*/
|
||||
@@ -333,6 +356,26 @@ class JobsQueueLib
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the job object structure when needed for update
|
||||
*/
|
||||
private function _checkUpdateJobStructure(&$job)
|
||||
{
|
||||
// If job is a valid object
|
||||
if (is_object($job) && property_exists($job, self::PROPERTY_JOBID)) return true; // it is valid!
|
||||
|
||||
// If not object then object it!
|
||||
if (!is_object($job)) $job = new stdClass();
|
||||
|
||||
// If an error property was not already previously stored then store an error message in job object
|
||||
if (!property_exists($job, self::PROPERTY_ERROR))
|
||||
{
|
||||
$job->{self::PROPERTY_ERROR} = 'The structure of the provided job is not valid';
|
||||
}
|
||||
|
||||
return false; // better sorry than wrong
|
||||
}
|
||||
|
||||
/**
|
||||
* Search in an array the given value
|
||||
* The elements of the given array are objects
|
||||
@@ -354,16 +397,6 @@ class JobsQueueLib
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop not allowed properties from the given job
|
||||
*/
|
||||
private function _dropNotAllowedPropertiesNewJob(&$job)
|
||||
{
|
||||
unset($job->{self::PROPERTY_JOBID});
|
||||
unset($job->{self::PROPERTY_CREATIONTIME});
|
||||
unset($job->{self::PROPERTY_TYPE});
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop not allowed properties from the given job
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
if (!defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
require_once 'JobsQueueLib.php';
|
||||
|
||||
/**
|
||||
* Library that contains all the needed functionalities to operate with the Long Run Tasks
|
||||
*/
|
||||
class LongRunTaskLib extends JobsQueueLib
|
||||
{
|
||||
// Config names
|
||||
const CFG_LRT_MAX_NUMBER_SYSTEM = 'lrt_max_number_system';
|
||||
const CFG_LRT_TYPES = 'lrt_types';
|
||||
const CFG_LRT_MAX_RUN = 'lrt_max_run_timeout';
|
||||
const CFG_LRT_MAX_NUMBER_USER = 'lrt_max_number_single_user';
|
||||
|
||||
// LRT object properties
|
||||
const PROPERTY_UID = 'uid';
|
||||
const PROPERTY_PID = 'pid';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Loads the Long Run Tasks configs
|
||||
$this->_ci->config->load('lrt');
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods used by the LongRunTaskExecJob
|
||||
|
||||
/**
|
||||
* Get the oldest added LRTs to the queue having status = new
|
||||
* The maximum number of returned queued LRTs is limited by:
|
||||
* number of currently running LRTs - maximum allowed number of LRTs for the system
|
||||
*/
|
||||
public function getNewLRTs()
|
||||
{
|
||||
// Get all the running LRTs
|
||||
$runningLrtsResult = $this->getJobsByTypeStatus($this->_ci->config->item(self::CFG_LRT_TYPES), JobsQueueLib::STATUS_RUNNING);
|
||||
|
||||
if (isError($runningLrtsResult)) return $runningLrtsResult;
|
||||
|
||||
// The number of the currently running LRTs - the maximum LRTs for the system
|
||||
$max_number_of_lrts = $this->_ci->config->item(self::CFG_LRT_MAX_NUMBER_SYSTEM) - count(getData($runningLrtsResult));
|
||||
|
||||
// Get the oldest LRTs added to the queue
|
||||
return $this->getOldestJobs($this->_ci->config->item(self::CFG_LRT_TYPES), $max_number_of_lrts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the LRT that are running more then CFG_LRT_MAX_RUN hours
|
||||
*/
|
||||
public function getHangingLRTs()
|
||||
{
|
||||
// Return the result of the query
|
||||
return $this->_ci->JobsQueueModel->execReadOnlyQuery('
|
||||
SELECT jq.*
|
||||
FROM system.tbl_jobsqueue jq
|
||||
WHERE jq.type IN ?
|
||||
AND jq.status = ?
|
||||
AND jq.starttime < NOW() - INTERVAL \''.$this->_ci->config->item(self::CFG_LRT_MAX_RUN).' hours\'
|
||||
ORDER BY jq.creationtime DESC',
|
||||
array(
|
||||
$this->_ci->config->item(self::CFG_LRT_TYPES),
|
||||
JobsQueueLib::STATUS_RUNNING
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the LRT that are currently running
|
||||
*/
|
||||
public function getRunningLRTs()
|
||||
{
|
||||
// Return the result of the query
|
||||
return $this->_ci->JobsQueueModel->execReadOnlyQuery('
|
||||
SELECT jq.*
|
||||
FROM system.tbl_jobsqueue jq
|
||||
WHERE jq.type IN ?
|
||||
AND jq.status = ?
|
||||
ORDER BY jq.creationtime DESC',
|
||||
array(
|
||||
$this->_ci->config->item(self::CFG_LRT_TYPES),
|
||||
JobsQueueLib::STATUS_RUNNING
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a LRT in background
|
||||
* - Checks if the wanted LRT exists in the applcation/controllers/lrts directory
|
||||
* - Then executes it in background via CI CLI
|
||||
* - Stores the LRT pid into the database
|
||||
*/
|
||||
public function executeLrt($lrt)
|
||||
{
|
||||
$output = array();
|
||||
|
||||
// If does _not_ exist a LRT implementation for this LRT type, then return an error
|
||||
if (!file_exists(APPPATH.'controllers/lrts/'.$lrt->{self::PROPERTY_TYPE}.'.php'))
|
||||
{
|
||||
return error('The required LRT implementation has not been found');
|
||||
}
|
||||
|
||||
// Execute the LRT implementation (a CI controller from CLI) providing as parameter the jobid
|
||||
exec(
|
||||
// Command
|
||||
sprintf(
|
||||
'/usr/bin/php %s/../index.ci.php lrts/%s/run %s > /dev/null 2>&1 & echo $!',
|
||||
APPPATH,
|
||||
$lrt->{self::PROPERTY_TYPE},
|
||||
$lrt->{self::PROPERTY_JOBID}
|
||||
),
|
||||
$output // Here goes the output from the standard output and error
|
||||
);
|
||||
|
||||
// If a pid has not been returned
|
||||
if (isEmptyArray($output) || !is_numeric($output[0])) return error('Not a valid pid has been returned');
|
||||
|
||||
// Set the pid, status and starttime of this LRT into the database
|
||||
$updateLrtResult = $this->_ci->JobsQueueModel->update(
|
||||
$lrt->{self::PROPERTY_JOBID},
|
||||
array(
|
||||
'pid' => $output[0],
|
||||
'starttime' => 'NOW()',
|
||||
'status' => self::STATUS_RUNNING
|
||||
)
|
||||
);
|
||||
if (isError($updateLrtResult)) return $updateLrtResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the provided LRT
|
||||
* To avoid to kill a process that is not this LRT,
|
||||
* since the same PID can be assigned to another process once this ended
|
||||
*/
|
||||
public function killLrt($lrt)
|
||||
{
|
||||
// Try to get the pid of this LRT from the system
|
||||
$pid = exec(
|
||||
sprintf(
|
||||
'ps -eo pid,cmd | grep "index.ci.php lrts/%s/run %s" | grep -v grep | awk \'{print $1}\'',
|
||||
$lrt->{self::PROPERTY_TYPE},
|
||||
$lrt->{self::PROPERTY_JOBID}
|
||||
)
|
||||
);
|
||||
|
||||
// If the pid is the same then kill the process with a SIGKILL
|
||||
if ($pid == $lrt->{self::PROPERTY_PID}) exec('kill -9 '.$lrt->{self::PROPERTY_PID}.' > /dev/null 2>&1');
|
||||
|
||||
// Set the LRT as failed in any case
|
||||
$lrtExecFailedResult = $this->_ci->JobsQueueModel->update(
|
||||
$lrt->{self::PROPERTY_JOBID},
|
||||
array(
|
||||
'endtime' => 'NOW()',
|
||||
'status' => self::STATUS_FAILED
|
||||
)
|
||||
);
|
||||
// If an error occurred then return it
|
||||
if (isError($lrtExecFailedResult)) return $lrtExecFailedResult;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function checkExecution($lrt)
|
||||
{
|
||||
// If the LRT stopped running
|
||||
if (!$this->_isRunning($lrt))
|
||||
{
|
||||
// Loads MessageLib library
|
||||
$this->_ci->load->library('MessageLib');
|
||||
// Load the BenutzerModel
|
||||
$this->_ci->load->model('person/Benutzer_model', 'BenutzerModel');
|
||||
|
||||
// Get the benutzer for this uid
|
||||
$benutzerResult = $this->_ci->BenutzerModel->loadWhere(array('uid' => $lrt->{LongRunTaskLib::PROPERTY_UID}));
|
||||
// If an error occurred then return it
|
||||
if (isError($benutzerResult)) return $benutzerResult;
|
||||
// If no benutzer has been found
|
||||
if (!hasData($benutzerResult)) return error('No benutzer found, uid: '.$lrt->{LongRunTaskLib::PROPERTY_UID});
|
||||
|
||||
$benutzer = getData($benutzerResult)[0];
|
||||
|
||||
// Sends a message to the user
|
||||
$messageResult = $this->_ci->messagelib->sendMessageUser(
|
||||
$benutzer->person_id,
|
||||
'Long run task ended',
|
||||
'The long run task '.$lrt->{self::PROPERTY_TYPE}.' ended, output: '.$lrt->{self::PROPERTY_OUTPUT}
|
||||
);
|
||||
// If an error occurred then return it
|
||||
if (isError($messageResult)) return $messageResult;
|
||||
|
||||
// Set the LRT as done
|
||||
$lrtExecOverResult = $this->_ci->JobsQueueModel->update(
|
||||
$lrt->{self::PROPERTY_JOBID},
|
||||
array(
|
||||
'endtime' => 'NOW()',
|
||||
'status' => self::STATUS_DONE
|
||||
)
|
||||
);
|
||||
// If an error occurred then return it
|
||||
if (isError($lrtExecOverResult)) return $lrtExecOverResult;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods used by the front end applications (standard controllers/end points, ex. controllers/system/LRTTest.php)
|
||||
|
||||
/**
|
||||
* Add a single LRT to the queue
|
||||
*/
|
||||
public function addNewLrtToQueue($type, $uid, $lrtInput)
|
||||
{
|
||||
// Checks parameters
|
||||
if (isEmptyString($type)) return error('The provided type parameter is not a valid string');
|
||||
if (isEmptyString($uid)) return error('The provided uid parameter is not a valid string');
|
||||
|
||||
// Get all the running task for this uid
|
||||
// Return the result of the query
|
||||
$runningLRTbyUIDResult = $this->_ci->JobsQueueModel->execReadOnlyQuery('
|
||||
SELECT jq.*
|
||||
FROM system.tbl_jobsqueue jq
|
||||
WHERE jq.status IN ?
|
||||
AND jq.uid = ?
|
||||
',
|
||||
array(
|
||||
array(
|
||||
self::STATUS_NEW,
|
||||
self::STATUS_RUNNING
|
||||
),
|
||||
$uid
|
||||
)
|
||||
);
|
||||
|
||||
// If an error occurred then return it
|
||||
if (isError($runningLRTbyUIDResult)) return $runningLRTbyUIDResult;
|
||||
|
||||
// If the running tasks for this user are too many
|
||||
if (count(getData($runningLRTbyUIDResult)) >= $this->_ci->config->item(self::CFG_LRT_MAX_NUMBER_USER))
|
||||
{
|
||||
return error('Too many running tasks for this user');
|
||||
}
|
||||
|
||||
// Convert input to JSON and check it
|
||||
$jsonLrtInput = json_encode($lrtInput);
|
||||
if ($jsonLrtInput == null) return error('The provided LRT input is not valid');
|
||||
|
||||
// Get all the job types
|
||||
$dbResult = $this->_ci->JobTypesModel->load();
|
||||
if (isError($dbResult)) return $dbResult;
|
||||
$types = getData($dbResult);
|
||||
|
||||
// If the given type is not present in database
|
||||
if (!$this->_checkJobType($type, $types)) return error('The provided type parameter is not valid');
|
||||
|
||||
// Get all the job statuses
|
||||
$dbResult = $this->_ci->JobStatusesModel->load();
|
||||
if (isError($dbResult)) return $dbResult;
|
||||
$statuses = getData($dbResult);
|
||||
|
||||
// Create an object that represent the new tbl_jobsqueue record with the provided input
|
||||
$lrt = $this->generateJobs(self::STATUS_NEW, $jsonLrtInput)[0];
|
||||
|
||||
// What you asked is what you get!
|
||||
$lrt->{self::PROPERTY_TYPE} = $type;
|
||||
$lrt->{self::PROPERTY_UID} = $uid;
|
||||
|
||||
// Try to insert the single lrt into database
|
||||
$dbNewLrtResult = $this->_ci->JobsQueueModel->insert($lrt);
|
||||
|
||||
// If an error occurred during while inserting in database
|
||||
if (isError($dbNewLrtResult)) return $dbNewLrtResult;
|
||||
|
||||
return success('LRT added to the queue');
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Public methods used by the LRT implementation (controllers/ltrs/*, ex. controllers/ltrs/LRTDummy)
|
||||
|
||||
/**
|
||||
* Return a single record from the
|
||||
*/
|
||||
public function getLrt($jobid)
|
||||
{
|
||||
$this->_ci->JobsQueueModel->resetQuery();
|
||||
|
||||
return $this->_ci->JobsQueueModel->loadWhere(array('jobid' => $jobid));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function setProgress($jobid, $progress)
|
||||
{
|
||||
return $this->_ci->JobsQueueModel->update($jobid, array('progress' => $progress));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function setOutuput($jobid, $output)
|
||||
{
|
||||
return $this->_ci->JobsQueueModel->update($jobid, array('output' => json_encode($output)));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* Return true if the LRT is still running
|
||||
*/
|
||||
private function _isRunning($lrt)
|
||||
{
|
||||
// Try to get the pid of this LRT from the system
|
||||
$pid = exec(
|
||||
sprintf(
|
||||
'ps -eo pid,cmd | grep "index.ci.php lrts/%s/run %s" | grep -v grep | awk \'{print $1}\'',
|
||||
$lrt->{self::PROPERTY_TYPE},
|
||||
$lrt->{self::PROPERTY_JOBID}
|
||||
)
|
||||
);
|
||||
|
||||
// If the pid is the same then the LRT is still running
|
||||
return $pid == $lrt->{self::PROPERTY_PID};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,13 +72,6 @@ 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 = 'Interessent'
|
||||
THEN pls.ausbildungssemester
|
||||
ELSE s.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
|
||||
|
||||
@@ -122,47 +122,6 @@ class Studiensemester_model extends DB_Model
|
||||
return $this->execQuery($query, array($studiensemester_kurzbz, $limit));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current study semester and the previous semesters of the same type (SS/WS).
|
||||
*
|
||||
* Example:
|
||||
* - SS2026, limit 3: SS2026, SS2025, SS2024
|
||||
* - WS2025, limit 2: WS2025, WS2024
|
||||
*
|
||||
* @param $studiensemester_kurzbz
|
||||
* @param $limit
|
||||
*/
|
||||
public function getPreviousSameSemesterFrom($studiensemester_kurzbz, $limit = 1)
|
||||
{
|
||||
$qry = '
|
||||
SELECT
|
||||
studiensemester_kurzbz,
|
||||
start,
|
||||
ende
|
||||
FROM
|
||||
public.tbl_studiensemester
|
||||
WHERE
|
||||
SUBSTRING(studiensemester_kurzbz FROM 1 FOR 2) = SUBSTRING(? FROM 1 FOR 2)
|
||||
AND start <= (
|
||||
SELECT start
|
||||
FROM public.tbl_studiensemester
|
||||
WHERE studiensemester_kurzbz = ?
|
||||
)
|
||||
ORDER BY
|
||||
start DESC
|
||||
LIMIT ?
|
||||
';
|
||||
|
||||
return $this->execQuery($qry,
|
||||
[
|
||||
$studiensemester_kurzbz,
|
||||
$studiensemester_kurzbz,
|
||||
$limit
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* getNearest
|
||||
*/
|
||||
|
||||
@@ -260,6 +260,7 @@ class Benutzerfunktion_model extends DB_Model
|
||||
return $this->execQuery($query, $parameters_array);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get active Kompetenzfeldleitung bei UID.
|
||||
*
|
||||
@@ -295,6 +296,7 @@ class Benutzerfunktion_model extends DB_Model
|
||||
return $this->execQuery($query, $parameters_array);
|
||||
}
|
||||
|
||||
|
||||
public function insertBenutzerfunktion($Json)
|
||||
{
|
||||
unset($Json['benutzerfunktion_id']);
|
||||
|
||||
@@ -149,7 +149,7 @@ class Person_model extends DB_Model
|
||||
* @param $filter Term to search for.
|
||||
* @return DB-result
|
||||
*/
|
||||
public function searchPerson($filter, $mode=null)
|
||||
public function searchPerson($filter)
|
||||
{
|
||||
$this->addSelect('vorname, nachname, gebdatum, person_id, titelpre, titelpost');
|
||||
$this->addSelect("CASE
|
||||
@@ -161,26 +161,6 @@ 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.'%')."
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
$includesArray = array(
|
||||
'title' => 'LRT Test Page',
|
||||
'axios027' => true,
|
||||
'bootstrap5' => true,
|
||||
'fontawesome6' => true,
|
||||
'vue3' => true,
|
||||
'navigationcomponent' => true,
|
||||
'phrases' => array(
|
||||
'global',
|
||||
'ui'
|
||||
),
|
||||
'customJSModules' => array('public/js/apps/LRTTest.js'),
|
||||
);
|
||||
|
||||
$this->load->view('templates/FHC-Header', $includesArray);
|
||||
?>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<!-- Navigation component -->
|
||||
<core-navigation-cmpt></core-navigation-cmpt>
|
||||
|
||||
<div id="content"></div>
|
||||
</div>
|
||||
|
||||
<?php $this->load->view('templates/FHC-Footer', $includesArray); ?>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
@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';
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
: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;
|
||||
}
|
||||
+1
-2
@@ -69,7 +69,6 @@
|
||||
|
||||
.tag_limette {
|
||||
background-color: #D3FFCE;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.tag_done {
|
||||
@@ -112,4 +111,4 @@
|
||||
.copy-btn {
|
||||
float: right;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
addNew1MinLrt()
|
||||
{
|
||||
return {
|
||||
method: 'post',
|
||||
url: '/system/LRTTest/lrt1min'
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -34,10 +34,4 @@ 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,
|
||||
};
|
||||
},
|
||||
}
|
||||
@@ -35,6 +35,9 @@ 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/');
|
||||
},
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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 PluginsPhrasen from '../plugins/Phrasen.js';
|
||||
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js'
|
||||
|
||||
import ApiLrt from "../api/LRTTEst.js";
|
||||
|
||||
const lrtTestApp = Vue.createApp({
|
||||
data: function() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
components: {
|
||||
CoreNavigationCmpt,
|
||||
},
|
||||
methods: {
|
||||
addNew1MinLrt() {
|
||||
this.$api.call(ApiLrt.addNew1MinLrt())
|
||||
.then(result => {
|
||||
this.dropdowns.studiensemester_array = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<button type="button" class="btn btn-primary" @click="addNew1MinLrt()">Add a 1 min LRT</button>
|
||||
`
|
||||
});
|
||||
|
||||
FhcApps.makeExtendable(lrtTestApp);
|
||||
|
||||
lrtTestApp.use(PluginsPhrasen).mount('#main');
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
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";
|
||||
|
||||
export default {
|
||||
name: 'DetailHeader',
|
||||
components: {
|
||||
ModalUploadFoto,
|
||||
PvSkeleton
|
||||
ModalUploadFoto
|
||||
},
|
||||
props: {
|
||||
headerData: {
|
||||
@@ -40,14 +38,6 @@ export default {
|
||||
'mitarbeiter',
|
||||
].includes(value)
|
||||
}
|
||||
},
|
||||
currentSemester: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isLoading: { //if true, then parent isLoading
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -70,7 +60,8 @@ export default {
|
||||
},
|
||||
hasTileUIDSlot() {
|
||||
return !!this.$slots.uid
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
created(){
|
||||
if (this.typeHeader === 'student') {
|
||||
@@ -80,7 +71,7 @@ export default {
|
||||
} 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 required.'
|
||||
'[DetailHeader] "person_id", "mitarbeiter_uid", and "domain" are requried.'
|
||||
)
|
||||
}
|
||||
this.loadHeaderData(this.person_id, this.mitarbeiter_uid);
|
||||
@@ -95,26 +86,13 @@ export default {
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
headerData: {
|
||||
handler(newVal) {
|
||||
if (this.typeHeader === 'student' && newVal?.length) {
|
||||
this.getSemesterStati(newVal[0].prestudent_id);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
headerDataMa: {},
|
||||
departmentData: {},
|
||||
leitungData: {},
|
||||
isFetchingIssues: false,
|
||||
noCurrentStatus: false,
|
||||
semesterStatiLoading: false,
|
||||
leitungOrgLoading: false,
|
||||
departmentDataLoading: false,
|
||||
headerDataMaLoading: false
|
||||
isFetchingIssues: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -130,40 +108,29 @@ 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)
|
||||
.finally(() => {
|
||||
this.headerDataMaLoading = false;
|
||||
});
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
loadDepartmentData(mitarbeiter_uid) {
|
||||
this.departmentDataLoading = true;
|
||||
return this.$api
|
||||
.call(ApiDetailHeader.getPersonAbteilung(mitarbeiter_uid))
|
||||
.then(result => {
|
||||
this.departmentData = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => {
|
||||
this.departmentDataLoading = false;
|
||||
});
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
getLeitungOrg(oekurzbz){
|
||||
this.leitungOrgLoading = true;
|
||||
return this.$api
|
||||
.call(ApiDetailHeader.getLeitungOrg(oekurzbz))
|
||||
.then(result => {
|
||||
this.leitungData = result.data;
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError)
|
||||
.finally(() => {
|
||||
this.leitungOrgLoading = false;
|
||||
});
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
async goToLeitung() {
|
||||
this.loadHeaderData(this.leitungData.person_id, this.leitungData.uid);
|
||||
@@ -212,33 +179,6 @@ 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;
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
@@ -260,6 +200,7 @@ export default {
|
||||
</modal-upload-foto>
|
||||
|
||||
<template v-if="typeHeader==='student'">
|
||||
|
||||
<div
|
||||
v-for="person in headerData"
|
||||
:key="person.person_id"
|
||||
@@ -295,100 +236,62 @@ export default {
|
||||
<small class="text-muted">{{person.uid}}</small>
|
||||
</div>
|
||||
|
||||
<div v-if="headerData.length == 1">
|
||||
<div v-if="!isLoading" class="d-flex align-items-center gap-3">
|
||||
<h2 class="h4">
|
||||
{{headerData[0].titelpre}}
|
||||
{{headerData[0].vorname}}
|
||||
{{headerData[0].nachname}}
|
||||
<span v-if="headerData[0].titelpost">, </span>
|
||||
{{headerData[0].titelpost}}
|
||||
</h2>
|
||||
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
|
||||
</div>
|
||||
<div v-else class="d-flex align-items-center gap-3">
|
||||
<pv-skeleton width="15rem" height="2rem" borderRadius="16px"></pv-skeleton>
|
||||
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
|
||||
</div>
|
||||
<div v-if="headerData.length == 1">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<h2 class="h4">
|
||||
{{headerData[0].titelpre}}
|
||||
{{headerData[0].vorname}}
|
||||
{{headerData[0].nachname}}
|
||||
<span v-if="headerData[0].titelpost">, </span>
|
||||
{{headerData[0].titelpost}}
|
||||
</h2>
|
||||
<h6 v-if="headerData[0].unruly" class="badge" :class="'bg-unruly rounded-0'"><strong>unruly</strong></h6>
|
||||
</div>
|
||||
|
||||
<h5 class="h6 d-flex align-items-center flex-wrap gap-1">
|
||||
<strong class="text-muted">{{$p.t('lehre', 'studiengang')}} </strong>
|
||||
<span v-if="!isLoading">
|
||||
{{headerData[0].stg_bezeichnung}} ({{headerData[0].studiengang}})
|
||||
</span>
|
||||
<span v-else>
|
||||
<pv-skeleton width="10rem"></pv-skeleton>
|
||||
</span>
|
||||
<template v-if="!semesterStatiLoading">
|
||||
<strong v-if="headerData[0].semester != null" class="text-muted"> | {{$p.t('lehre', 'semester')}} </strong>
|
||||
{{headerData[0].semester}}
|
||||
<strong v-if="headerData[0].gruppe !== null && headerData[0].verband != ' '" class="text-muted"> | {{$p.t('lehre', 'verband')}}</strong>
|
||||
{{headerData[0].verband}}
|
||||
<strong v-if="headerData[0].gruppe !== null && headerData[0].gruppe != ' '" class="text-muted"> | {{$p.t('lehre', 'gruppe')}} </strong>
|
||||
{{headerData[0].gruppe}}
|
||||
</template>
|
||||
<template v-else>
|
||||
<strong class="text-muted"> | {{$p.t('lehre', 'semester')}} </strong>
|
||||
<pv-skeleton size="1rem" class="mr-2"></pv-skeleton>
|
||||
<strong class="text-muted"> | {{$p.t('lehre', 'verband')}}</strong>
|
||||
<pv-skeleton size="1rem" class="mr-2"></pv-skeleton>
|
||||
<strong class="text-muted"> | {{$p.t('lehre', 'gruppe')}} </strong>
|
||||
<pv-skeleton size="1rem" class="mr-2"></pv-skeleton>
|
||||
</template>
|
||||
</h5>
|
||||
<h5 class="h6">
|
||||
<strong class="text-muted">{{$p.t('lehre', 'studiengang')}} </strong>
|
||||
{{headerData[0].stg_bezeichnung}} ({{headerData[0].studiengang}})
|
||||
<strong v-if="headerData[0].semester" class="text-muted"> | {{$p.t('lehre', 'semester')}} </strong>
|
||||
{{headerData[0].semester}}
|
||||
<strong v-if="headerData[0].verband" class="text-muted"> | {{$p.t('lehre', 'verband')}}</strong>
|
||||
{{headerData[0].verband}}
|
||||
<strong v-if="headerData[0].gruppe" class="text-muted"> | {{$p.t('lehre', 'gruppe')}} </strong>
|
||||
{{headerData[0].gruppe}}
|
||||
</h5>
|
||||
|
||||
<h5 class="h6 d-flex align-items-center flex-wrap gap-1">
|
||||
<strong class="text-muted">Email </strong>
|
||||
<span v-if="!isLoading">
|
||||
<a :href="'mailto:'+headerData[0]?.mail_intern">{{headerData[0].mail_intern}}</a>
|
||||
</span>
|
||||
<span v-else>
|
||||
<pv-skeleton width="10rem"></pv-skeleton>
|
||||
</span>
|
||||
<strong class="text-muted"> | Status </strong>
|
||||
<span v-if="noCurrentStatus">
|
||||
<strong class="text-danger">{{$p.t('lehre', 'textNoStatusInSem', { sem: currentSemester}) }}</strong>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{headerData[0].statusofsemester}}
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div v-if="headerData.length == 1" class="col-md-1 d-flex flex-column align-items-end justify-content-start ms-auto">
|
||||
<div class="d-flex py-1">
|
||||
<div class="px-2" style="min-width: 100px;">
|
||||
<slot name="issues"></slot>
|
||||
</div>
|
||||
<div v-if="hasTileGammaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleGammaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueGammaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileBetaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleBetaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueBetaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileAlphaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleAlphaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueAlphaTile"></slot>
|
||||
</h6>
|
||||
</div>
|
||||
<div v-if="hasTileUIDSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center">UID</h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="uid"></slot>
|
||||
</h6>
|
||||
<h5 class="h6">
|
||||
<strong class="text-muted">Email </strong>
|
||||
<span>
|
||||
<a :href="'mailto:'+headerData[0]?.mail_intern">{{headerData[0].mail_intern}}</a>
|
||||
</span>
|
||||
<strong v-if="headerData[0].statusofsemester" class="text-muted"> | Status </strong>
|
||||
{{headerData[0].statusofsemester}}
|
||||
</h5>
|
||||
|
||||
</div>
|
||||
<div v-if="headerData.length == 1" class="col-md-1 d-flex flex-column align-items-end justify-content-start ms-auto">
|
||||
<div class="d-flex py-1">
|
||||
<div class="px-2" style="min-width: 100px;">
|
||||
<slot name="issues"></slot>
|
||||
</div>
|
||||
<div v-if="hasTileGammaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleGammaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueGammaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileBetaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleBetaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueBetaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileAlphaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleAlphaTile"></slot></h4>
|
||||
<h6 class="text-muted text-center"><slot name="valueAlphaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileUIDSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center">UID</h4>
|
||||
<h6 class="text-muted text-center"><slot name="uid"></slot></h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
@@ -427,51 +330,27 @@ export default {
|
||||
|
||||
<!--show Ma-Details-->
|
||||
<div class="col-md-9 text-nowrap mt-2">
|
||||
<h4 v-if="!headerDataMaLoading">{{headerDataMa.titelpre}} {{headerDataMa.vorname}} {{headerDataMa.nachname}}<span v-if="headerDataMa?.titelpost">, </span> {{headerDataMa.titelpost}}</h4>
|
||||
<h4 v-else><pv-skeleton width="15rem" height="2rem" borderRadius="16px"></pv-skeleton></h4>
|
||||
<div class="d-flex align-items-center flex-wrap gap-1">
|
||||
<strong class="text-muted">{{departmentData.organisationseinheittyp_kurzbz}}</strong>
|
||||
<span v-if="!departmentDataLoading">
|
||||
{{departmentData.bezeichnung}}
|
||||
</span>
|
||||
<span v-else>
|
||||
<pv-skeleton width="12rem"></pv-skeleton>
|
||||
</span>
|
||||
<span v-if="leitungData.uid"> | </span>
|
||||
<strong v-if="leitungData.uid" class="text-muted">Vorgesetzte*r </strong>
|
||||
<span v-if="!leitungOrgLoading">
|
||||
<a href="#" @click.prevent="goToLeitung">
|
||||
{{leitungData.titelpre}} {{leitungData.vorname}} {{leitungData.nachname}}
|
||||
<h4>{{headerDataMa.titelpre}} {{headerDataMa.vorname}} {{headerDataMa.nachname}}<span v-if="headerDataMa?.titelpost">, </span> {{headerDataMa.titelpost}}</h4>
|
||||
<strong class="text-muted">{{departmentData.organisationseinheittyp_kurzbz}}</strong>
|
||||
{{departmentData.bezeichnung}}
|
||||
<span v-if="leitungData.uid"> | </span>
|
||||
<strong v-if="leitungData.uid" class="text-muted">Vorgesetzte*r </strong>
|
||||
<a href="#" @click.prevent="goToLeitung">
|
||||
{{leitungData.titelpre}} {{leitungData.vorname}} {{leitungData.nachname}}
|
||||
</a>
|
||||
<p>
|
||||
<strong class="text-muted">Email </strong>
|
||||
<span v-if="headerDataMa && (headerDataMa.alias === undefined || headerDataMa.alias === null || headerDataMa.alias === '')">
|
||||
<a :href="'mailto:' + mitarbeiter_uid + '@' + domain">
|
||||
{{ mitarbeiter_uid }}@{{ domain }}
|
||||
</a>
|
||||
</span>
|
||||
<span v-else>
|
||||
<pv-skeleton width="5rem"></pv-skeleton>
|
||||
<a :href="'mailto:'+headerDataMa?.alias+'@'+domain">{{headerDataMa.alias}}@{{domain}}</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 flex-nowrap">
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<strong class="text-muted">Email</strong>
|
||||
<template v-if="!headerDataMaLoading">
|
||||
<a :href="'mailto:' + (headerDataMa?.alias || mitarbeiter_uid) + '@' + domain">
|
||||
{{ (headerDataMa?.alias || mitarbeiter_uid) + '@' + domain }}
|
||||
</a>
|
||||
</template>
|
||||
<pv-skeleton v-else width="10rem"></pv-skeleton>
|
||||
</div>
|
||||
|
||||
<div v-if="headerDataMa?.telefonklappe" class="d-flex align-items-center gap-1">
|
||||
<span>|</span>
|
||||
<strong class="text-muted">DW</strong>
|
||||
<template v-if="!headerDataMaLoading">
|
||||
{{ headerDataMa.telefonklappe }}
|
||||
</template>
|
||||
<pv-skeleton v-else width="4rem"></pv-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span v-if="headerDataMa?.telefonklappe" class="mb-2"> | <strong class="text-muted">DW </strong>{{headerDataMa?.telefonklappe}}</span>
|
||||
</p>
|
||||
<slot name="tag"></slot>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-1 d-flex flex-column align-items-end justify-content-start ms-auto">
|
||||
@@ -481,33 +360,20 @@ export default {
|
||||
</div>
|
||||
<div v-if="hasTileGammaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleGammaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueGammaTile"></slot>
|
||||
</h6>
|
||||
<h6 class="text-muted text-center"><slot name="valueGammaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileBetaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleBetaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueBetaTile"></slot>
|
||||
</h6>
|
||||
<h6 class="text-muted text-center"><slot name="valueBetaTile" :valueBetaTile="valueBetaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileAlphaSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center"><slot name="titleAlphaTile"></slot></h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="valueAlphaTile"></slot>
|
||||
</h6>
|
||||
<h6 class="text-muted text-center"><slot name="valueAlphaTile"></slot></h6>
|
||||
</div>
|
||||
<div v-if="hasTileUIDSlot" class="px-2" style="border-left: 1px solid #EEE">
|
||||
<h4 class="mb-1 text-center">UID</h4>
|
||||
<h6 class="text-muted d-flex align-items-center justify-content-center flex-wrap gap-1">
|
||||
<pv-skeleton v-if="isLoading" width="4rem"></pv-skeleton>
|
||||
<slot v-else name="uid"></slot>
|
||||
</h6>
|
||||
<h6 class="text-muted text-center"><slot name="uid"></slot></h6>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ export default {
|
||||
label: String,
|
||||
// NOTE(chris): remove these from $attrs array to prevent doubled event listeners
|
||||
onInput: [Array, Function],
|
||||
'onUpdate:modelValue': [Array, Function],
|
||||
titleActionButton: String
|
||||
'onUpdate:modelValue': [Array, Function]
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -318,7 +317,6 @@ export default {
|
||||
:id="idCmp"
|
||||
:name="name"
|
||||
:class="validationClass"
|
||||
:titleActionButton="titleActionButton"
|
||||
@update:model-value="clearValidationForThisName"
|
||||
>
|
||||
<slot></slot>
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
export default {
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'actionbutton-clicked'
|
||||
'update:modelValue'
|
||||
],
|
||||
props: {
|
||||
modelValue: String,
|
||||
titleActionButton: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
modelValue: String
|
||||
},
|
||||
computed: {
|
||||
valueAsBase64DataString() {
|
||||
@@ -33,9 +28,6 @@ export default {
|
||||
},
|
||||
deleteImage() {
|
||||
this.$emit('update:modelValue', '');
|
||||
},
|
||||
emitAction(){
|
||||
this.$emit('actionbutton-clicked', this.modelValue);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
@@ -50,7 +42,6 @@ export default {
|
||||
<button type="button" class="btn btn-outline-dark btn-sm" @click="openUploadDialog">
|
||||
<i class="fa fa-pen"></i>
|
||||
</button>
|
||||
<button v-if="titleActionButton" class="btn btn-outline-dark btn-sm" @click="emitAction">{{titleActionButton}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
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";
|
||||
@@ -38,7 +37,6 @@ export default {
|
||||
CoreSearchbar,
|
||||
NavLanguage,
|
||||
VerticalSplit,
|
||||
HorizontalSplit,
|
||||
AppMenu,
|
||||
AppConfig,
|
||||
StvVerband,
|
||||
@@ -194,44 +192,7 @@ 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;
|
||||
},
|
||||
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
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'url_studiensemester_kurzbz': function (newVal, oldVal) {
|
||||
@@ -251,7 +212,6 @@ export default {
|
||||
'url_studiengang': function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.checkUrlStudiengang();
|
||||
this.$refs.stvList.clearSelection();
|
||||
}
|
||||
},
|
||||
'url_mode': function () {
|
||||
@@ -275,10 +235,6 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sidebarCollapsed(newVal) {
|
||||
if(newVal) this.$refs.hSplit.collapseLeft()
|
||||
else this.$refs.hSplit.showBoth()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -475,9 +431,6 @@ export default {
|
||||
},
|
||||
deleteCustomFilter(){
|
||||
this.$refs.stvList.resetFilter();
|
||||
},
|
||||
showAlertNoSelectedStudent(){
|
||||
this.$fhcAlert.alertError(this.$p.t('ui', 'alert_chooseStudent'));
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -676,69 +629,26 @@ export default {
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropend">
|
||||
<a
|
||||
class="dropdown-toggle"
|
||||
href="#"
|
||||
role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
:class="{ disabled: !appMenuExtraItems.length }"
|
||||
data-bs-popper-config='{"strategy":"fixed"}'
|
||||
>
|
||||
{{ $p.t('stv/lvplanung') }}
|
||||
</a>
|
||||
<ul class="dropdown-menu p-0">
|
||||
<li
|
||||
v-for="(item, key) in appMenuLvPlanungItems"
|
||||
:key="key"
|
||||
>
|
||||
<a class="dropdown-item" :href="item.link" target="_blank">
|
||||
{{ $p.t(item.description) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a :href="linkRt" target="_blank">
|
||||
{{ $p.t('stv/RTVerwaltung') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a v-if="selected_uid" :href="linkGradeList" target="_blank">
|
||||
{{ $p.t('stv/studienverlauf') }}
|
||||
</a>
|
||||
<a v-else href="#" @click.prevent="showAlertNoSelectedStudent">
|
||||
{{ $p.t('stv/studienverlauf') }}
|
||||
</a>
|
||||
</li>
|
||||
</app-menu>
|
||||
</div>
|
||||
</aside>
|
||||
<horizontal-split ref="hSplit" :defaultRatio="[15, 85]">
|
||||
<template #left>
|
||||
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 w-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>
|
||||
<stv-verband :preselectedKey="studiengangKz ? '' + studiengangKz : null" :endpoint="verbandEndpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
|
||||
<stv-studiensemester v-model:studiensemester-kurzbz="studiensemesterKurzbz" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
|
||||
</nav>
|
||||
</template>
|
||||
<template #right>
|
||||
<main>
|
||||
<vertical-split :defaultRatio="[50, 50]">
|
||||
<template #top>
|
||||
<stv-list ref="stvList" v-model:selected="selected" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz" @filterActive="handleCustomFilter"></stv-list>
|
||||
</template>
|
||||
<template #bottom>
|
||||
<stv-details ref="details" :students="selected" @reload="reloadList"></stv-details>
|
||||
</template>
|
||||
</vertical-split>
|
||||
</main>
|
||||
</template>
|
||||
</horizontal-split>
|
||||
|
||||
<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>
|
||||
<stv-verband :preselectedKey="studiengangKz ? '' + studiengangKz : null" :endpoint="verbandEndpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
|
||||
<stv-studiensemester v-model:studiensemester-kurzbz="studiensemesterKurzbz" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
|
||||
</nav>
|
||||
<main class="col-md-8 ms-sm-auto col-lg-9 col-xl-10">
|
||||
<vertical-split>
|
||||
<template #top>
|
||||
<stv-list ref="stvList" v-model:selected="selected" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz" @filterActive="handleCustomFilter"></stv-list>
|
||||
</template>
|
||||
<template #bottom>
|
||||
<stv-details ref="details" :students="selected" @reload="reloadList"></stv-details>
|
||||
</template>
|
||||
</vertical-split>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<app-config ref="config" v-model="appconfig" :endpoints="configEndpoints"></app-config>
|
||||
|
||||
@@ -2,18 +2,12 @@ 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
|
||||
@@ -21,9 +15,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
configStudent: {},
|
||||
configStudents: {},
|
||||
activeTab: null,
|
||||
localStudent: null
|
||||
configStudents: {}
|
||||
};
|
||||
},
|
||||
props: {
|
||||
@@ -48,9 +40,6 @@ 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;
|
||||
@@ -68,21 +57,6 @@ export default {
|
||||
'$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: {
|
||||
@@ -100,28 +74,10 @@ 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');
|
||||
},
|
||||
@@ -136,12 +92,10 @@ export default {
|
||||
</div>
|
||||
<div v-else-if="configStudent && configStudents" class="d-flex flex-column h-100">
|
||||
<fhc-header
|
||||
:headerData="localStudent || students"
|
||||
:currentSemester="currentSemester"
|
||||
:headerData="students"
|
||||
typeHeader="student"
|
||||
@reload="reloadList"
|
||||
fotoEditable
|
||||
:isLoading="isLoading"
|
||||
>
|
||||
<template #uid>{{students[0].uid}}</template>
|
||||
<template #titleAlphaTile>PersID</template>
|
||||
@@ -153,16 +107,16 @@ export default {
|
||||
</fhc-header>
|
||||
<fhc-tabs
|
||||
v-if="students.length == 1"
|
||||
ref="tabs"
|
||||
ref="tabs"
|
||||
:useprimevue="true"
|
||||
:modelValue="(Array.isArray(localStudent) && localStudent[0]) || students[0]"
|
||||
:modelValue="students[0]"
|
||||
:config="config"
|
||||
:default="activeTab ?? $route.params.tab"
|
||||
:default="$route.params.tab"
|
||||
style="flex: 1 1 0%; height: 0%"
|
||||
@changed="handleTabChanged"
|
||||
@changed="reload"
|
||||
>
|
||||
</fhc-tabs>
|
||||
<fhc-tabs v-else ref="tabs" :useprimevue="true" :modelValue="students" :config="config" :default="activeTab ?? $route.params.tab" style="flex: 1 1 0%; height: 0%" @changed="handleTabChanged"></fhc-tabs>
|
||||
<fhc-tabs v-else ref="tabs" :useprimevue="true" :modelValue="students" :config="config" :default="$route.params.tab" style="flex: 1 1 0%; height: 0%" @changed="reload"></fhc-tabs>
|
||||
</div>
|
||||
<div v-else>
|
||||
Loading...
|
||||
|
||||
+36
-160
@@ -238,16 +238,6 @@ 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)
|
||||
{
|
||||
@@ -297,50 +287,10 @@ export default {
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
actionNewAbschlusspruefung() {
|
||||
this.setDefaultFormData();
|
||||
this.resetForm();
|
||||
this.statusNew = true;
|
||||
|
||||
//prepare local Storage
|
||||
let STORAGE_KEY = 'finalExamDefaultData';
|
||||
const id = '20260224_02';
|
||||
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;
|
||||
|
||||
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();
|
||||
this.setDefaultFormData();
|
||||
},
|
||||
actionEditAbschlusspruefung(abschlusspruefung_id) {
|
||||
this.resetForm();
|
||||
@@ -355,23 +305,20 @@ export default {
|
||||
};
|
||||
if (data.p1_person_id) {
|
||||
this.selectedPruefer1 = {
|
||||
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
|
||||
label: this.getPersonLabel(data.p1_titelpre, data.p1_nachname, data.p1_vorname, data.p1_titelpost),
|
||||
person_id: data.p1_person_id
|
||||
};
|
||||
}
|
||||
if (data.p2_person_id) {
|
||||
this.selectedPruefer2 = {
|
||||
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
|
||||
label: this.getPersonLabel(data.p2_titelpre, data.p2_nachname, data.p2_vorname, data.p2_titelpost),
|
||||
person_id: data.p2_person_id
|
||||
}
|
||||
};
|
||||
if (data.p3_person_id) {
|
||||
this.selectedPruefer3= {
|
||||
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
|
||||
label: this.getPersonLabel(data.p3_titelpre, data.p3_nachname, data.p3_vorname, data.p3_titelpost),
|
||||
person_id: data.p3_person_id
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -390,29 +337,6 @@ export default {
|
||||
.then(this.deleteAbschlusspruefung)
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
},
|
||||
saveOrUpdateLocalStorage(){
|
||||
let STORAGE_KEY = 'finalExamDefaultData';
|
||||
|
||||
const id = '20260224_02';
|
||||
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
|
||||
|
||||
stored[id] = {
|
||||
pruefungstyp_kurzbz: this.formData.pruefungstyp_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,
|
||||
@@ -423,8 +347,6 @@ 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();
|
||||
})
|
||||
@@ -433,26 +355,6 @@ export default {
|
||||
this.reload();
|
||||
});
|
||||
},
|
||||
async addNewAbschlusspruefungMulti(){
|
||||
try {
|
||||
for (const student of this.studentUids) {
|
||||
|
||||
await this.$refs.formFinalExam.call(
|
||||
ApiStvAbschlusspruefung.addNewAbschlusspruefung({
|
||||
uid: student,
|
||||
formData: this.formData
|
||||
})
|
||||
);
|
||||
}
|
||||
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
|
||||
//save formData fields in LocalStorage
|
||||
this.saveOrUpdateLocalStorage();
|
||||
this.hideModal('finalexamModal');
|
||||
this.resetForm();
|
||||
} catch (error) {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
},
|
||||
hideModal(modalRef){
|
||||
this.$refs[modalRef].hide();
|
||||
},
|
||||
@@ -474,9 +376,6 @@ 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 => {
|
||||
@@ -518,6 +417,7 @@ export default {
|
||||
this.selectedPruefer1 = null;
|
||||
this.selectedPruefer2 = null;
|
||||
this.selectedPruefer3 = null;
|
||||
|
||||
},
|
||||
setDefaultFormData() {
|
||||
|
||||
@@ -571,29 +471,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.uid
|
||||
),
|
||||
person_id: person.person_id,
|
||||
mitarbeiter_uid: person.uid
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
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
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
@@ -626,7 +526,7 @@ export default {
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
this.$api
|
||||
.call(ApiStvAbschlusspruefung.getAkadGrade(this.stg_kz))
|
||||
.call(ApiStvAbschlusspruefung.getAkadGrade(this.student.studiengang_kz))
|
||||
.then(result => {
|
||||
this.arrAkadGrad = result.data;
|
||||
})
|
||||
@@ -647,8 +547,7 @@ export default {
|
||||
<div class="stv-details-abschlusspruefung h-100 pb-3">
|
||||
<h4>{{this.$p.t('stv','tab_finalexam')}}</h4>
|
||||
|
||||
<div v-if="this.student.length" class="d-flex gap-2">
|
||||
<button class="btn btn-primary" @click="actionNewAbschlusspruefung()"> + {{$p.t('stv', 'tab_finalexam')}}</button>
|
||||
<div v-if="this.student.length">
|
||||
<abschlusspruefung-dropdown
|
||||
:showAllFormats="showAllFormats"
|
||||
:studentUids="studentUids"
|
||||
@@ -680,14 +579,12 @@ export default {
|
||||
<template #title>
|
||||
<p v-if="statusNew" class="fw-bold mt-3">{{$p.t('abschlusspruefung', 'abschluessPruefungAnlegen')}}</p>
|
||||
<p v-else class="fw-bold mt-3">{{$p.t('abschlusspruefung', 'abschluessPruefungBearbeiten')}}</p>
|
||||
<small v-if="this.student.length" class="text-muted">{{studentNames}}</small>
|
||||
</template>
|
||||
|
||||
<form-form ref="formFinalExam" @submit.prevent>
|
||||
<form-form v-if="!this.student.length" ref="formFinalExam" @submit.prevent>
|
||||
|
||||
<legend>{{this.$p.t('global','details')}}</legend>
|
||||
<p v-if="statusNew">[{{$p.t('ui', 'neu')}}]</p>
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
container-class="col-6 stv-details-abschlusspruefung-typ"
|
||||
@@ -705,7 +602,6 @@ export default {
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="!this.student.length"
|
||||
container-class="col-6 stv-details-abschlusspruefung-note"
|
||||
:label="$p.t('abschlusspruefung', 'notekommpruefung')"
|
||||
type="select"
|
||||
@@ -774,10 +670,9 @@ export default {
|
||||
>
|
||||
</form-input>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row mb-3">
|
||||
<form-input
|
||||
v-if="!this.student.length"
|
||||
container-class="col-6 stv-details-abschlusspruefung-abschlussbeurteilung_kurzbz"
|
||||
:label="$p.t('abschlusspruefung', 'abschlussbeurteilung')"
|
||||
type="select"
|
||||
@@ -803,23 +698,7 @@ export default {
|
||||
optionValue="person_id"
|
||||
dropdown
|
||||
forceSelection
|
||||
:suggestions="filteredPersons"
|
||||
@complete="searchPerson"
|
||||
:min-length="3"
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="this.student.length"
|
||||
type="autocomplete"
|
||||
container-class="col-6 stv-details-abschlusspruefung-pruefer3"
|
||||
:label="$p.t('abschlusspruefung', 'pruefer3')"
|
||||
name="pruefer3"
|
||||
v-model="selectedPruefer3"
|
||||
optionLabel="label"
|
||||
optionValue="person_id"
|
||||
dropdown
|
||||
forceSelection
|
||||
:suggestions="filteredPersons"
|
||||
:suggestions="filteredPersons"
|
||||
@complete="searchPerson"
|
||||
:min-length="3"
|
||||
>
|
||||
@@ -843,7 +722,6 @@ export default {
|
||||
</option>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="!this.student.length"
|
||||
type="autocomplete"
|
||||
container-class="col-6 stv-details-abschlusspruefung-pruefer3"
|
||||
:label="$p.t('abschlusspruefung', 'pruefer3')"
|
||||
@@ -853,7 +731,7 @@ export default {
|
||||
optionValue="person_id"
|
||||
dropdown
|
||||
forceSelection
|
||||
:suggestions="filteredPersons"
|
||||
:suggestions="filteredPersons"
|
||||
@complete="searchPerson"
|
||||
:min-length="3"
|
||||
>
|
||||
@@ -901,7 +779,6 @@ export default {
|
||||
>
|
||||
</form-input>
|
||||
<form-input
|
||||
v-if="!this.student.length"
|
||||
container-class="col-6 stv-details-abschlusspruefung-protokoll"
|
||||
:label="$p.t('abschlusspruefung', 'protokoll')"
|
||||
type="textarea"
|
||||
@@ -913,7 +790,7 @@ export default {
|
||||
</form-input>
|
||||
</div>
|
||||
|
||||
<div v-if="!this.student.length" class="row mb-3 col-6">
|
||||
<div class="row mb-3 col-6">
|
||||
<div class="col">
|
||||
<p >{{$p.t('abschlusspruefung', 'zurBeurteilung')}}</p>
|
||||
</div>
|
||||
@@ -930,9 +807,8 @@ export default {
|
||||
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{$p.t('ui', 'abbrechen')}}</button>
|
||||
<button v-if="statusNew && !this.student.length" class="btn btn-primary" @click="addNewAbschlusspruefung()"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-else-if="statusNew && this.student.length" class="btn btn-primary" @click="addNewAbschlusspruefungMulti(studentUids)"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-else class="btn btn-primary" @click="updateAbschlusspruefung(formData.abschlusspruefung_id)"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-if="statusNew" class="btn btn-primary" @click="addNewAbschlusspruefung()"> {{$p.t('ui', 'speichern')}}</button>
|
||||
<button v-else class="btn btn-primary" @click="updateAbschlusspruefung(formData.abschlusspruefung_id)"> {{$p.t('ui', 'speichern')}}</button>
|
||||
</template>
|
||||
|
||||
</bs-modal>
|
||||
|
||||
@@ -158,16 +158,6 @@ 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() {
|
||||
@@ -396,10 +386,8 @@ 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"
|
||||
>
|
||||
<img alt="No Image" :src="noImageSrc" class="w-100">
|
||||
</form-input>
|
||||
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
}
|
||||
},
|
||||
{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_berechnet", headerFilter: "list", headerFilterParams: {valuesLookup:true, listOnEmpty:true, autocomplete:true, sort:"asc"}},
|
||||
{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"}},
|
||||
@@ -171,7 +171,7 @@ export default {
|
||||
selectableRows: true,
|
||||
selectableRowsRangeMode: 'click',
|
||||
index: 'prestudent_id',
|
||||
persistenceID: 'stv-list-20260223_01'
|
||||
persistenceID: 'stv-list',
|
||||
},
|
||||
tabulatorEvents: [
|
||||
{
|
||||
@@ -195,15 +195,7 @@ export default {
|
||||
},
|
||||
{
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
handler: data => this.count = data.length
|
||||
},
|
||||
{
|
||||
event: 'dataFiltered',
|
||||
@@ -245,8 +237,7 @@ export default {
|
||||
dragSource: [],
|
||||
oldScrollUrl: '',
|
||||
oldScrollLeft: 0,
|
||||
oldScrollTop: 0,
|
||||
allPrestudents: []
|
||||
oldScrollTop: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -283,7 +274,6 @@ export default {
|
||||
};
|
||||
});
|
||||
},
|
||||
//TODO(Manu) check: replace download or additional entry?
|
||||
downloadConfig() {
|
||||
return {
|
||||
csv: {
|
||||
@@ -301,21 +291,6 @@ export default {
|
||||
.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(";");
|
||||
},
|
||||
},
|
||||
created: function() {
|
||||
if(this.tagsEnabled) {
|
||||
@@ -355,7 +330,7 @@ export default {
|
||||
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')),
|
||||
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')),
|
||||
@@ -416,17 +391,15 @@ export default {
|
||||
actionNewPrestudent() {
|
||||
this.$refs.new.open();
|
||||
},
|
||||
rowSelectionChanged(data, rows, selected, deselected) {
|
||||
rowSelectionChanged(data, rows) {
|
||||
this.selectedcount = data.length;
|
||||
|
||||
if(selected.length > 0 || deselected.length > 0){
|
||||
this.lastSelected = this.selected;
|
||||
this.$emit('update:selected', data);
|
||||
}
|
||||
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){
|
||||
@@ -573,10 +546,6 @@ export default {
|
||||
this.changeFocus(this.focusObj, el);
|
||||
}
|
||||
},
|
||||
clearSelection(){
|
||||
this.lastSelected = [];
|
||||
this.$emit('update:selected',[]);
|
||||
},
|
||||
//methods tags
|
||||
addedTag(addedTag)
|
||||
{
|
||||
@@ -631,7 +600,7 @@ export default {
|
||||
// TODO(chris): focusin, focusout, keydown and tabindex should be in the filter component
|
||||
// TODO(chris): filter component column chooser has no accessibilty features
|
||||
template: `
|
||||
<div class="stv-list h-100 pt-3">
|
||||
<div class="stv-list h-100 pt-3">
|
||||
<div
|
||||
class="tabulator-container d-flex flex-column h-100"
|
||||
:class="{'has-filter': filter.length}"
|
||||
@@ -658,27 +627,6 @@ export default {
|
||||
@headerFilterOn="handleHeaderFilter"
|
||||
>
|
||||
|
||||
<!--
|
||||
<template #actions>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-outline-success sm mb-1"
|
||||
:title="'Export ' + selectedPrestudents.length + ' prestudent(s) to Excel'"
|
||||
>
|
||||
<i class="fas fa-file-excel fa-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
-->
|
||||
|
||||
<template #additional>
|
||||
<div class="pe-2">
|
||||
<a :href="linkXLS" target="_blank">
|
||||
<i class="fas fa-file-excel fa-xl text-success" :title="$p.t('stv', 'text_exportXLS', { count: selectedPrestudents.length })"></i>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<core-tag ref="tagComponent"
|
||||
v-if="tagsEnabled"
|
||||
|
||||
@@ -218,23 +218,15 @@ export const CoreFilterCmpt = {
|
||||
},
|
||||
columnsForFilter() {
|
||||
if (!this.filteredColumns || !this.datasetMetadata) return [];
|
||||
|
||||
const localizedColumnTitles = this.tabulator.getLang().columns;
|
||||
const isTabulatorLocalized = !!this.$props.tabulatorOptions.locale;
|
||||
|
||||
let filterTitles;
|
||||
if (isTabulatorLocalized) {
|
||||
const localizedColumnTitles = this.tabulator.getLang().columns;
|
||||
filterTitles = this.filteredColumns.reduce((a, c) => {
|
||||
a[c.field] = localizedColumnTitles[c.field];
|
||||
return a;
|
||||
}, {});
|
||||
} else {
|
||||
filterTitles = this.filteredColumns.reduce((a, c) => {
|
||||
a[c.field] = c.title;
|
||||
return a;
|
||||
}, {});
|
||||
}
|
||||
|
||||
const filterTitles = this.filteredColumns.reduce((a, c) => {
|
||||
a[c.field] =
|
||||
isTabulatorLocalized && localizedColumnTitles[c.field]
|
||||
? localizedColumnTitles[c.field]
|
||||
: c.title;
|
||||
return a;
|
||||
}, {});
|
||||
return this.datasetMetadata.map((el) => ({
|
||||
...el,
|
||||
...{ title: filterTitles[el.name] },
|
||||
@@ -902,7 +894,6 @@ export const CoreFilterCmpt = {
|
||||
<span class="fa-solid fa-xl fa-table-columns"></span>
|
||||
</a>
|
||||
<table-download class="btn btn-link px-0 fhc-text" :tabulator="tabulator" :config="download"></table-download>
|
||||
<slot name="additional"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
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: `
|
||||
<div ref="horizontalsplit" class="horizontalsplit-container">
|
||||
<div ref="leftpanel" class="horizontalsplitted"
|
||||
:style="{ width: this.leftwidthcss }">
|
||||
<slot name="left">
|
||||
<p>Left Panel</p>
|
||||
</slot>
|
||||
</div>
|
||||
<div ref="hsplitter" class="horizontalsplitter"
|
||||
:class="this.leftOrRightClass" @mousedown="this.dragStart">
|
||||
<div class="splitactions horizontal" :class="this.leftOrRightClass">
|
||||
<span @click="this.collapseRight" class="splitaction">
|
||||
<i class="fas fa-angle-right text-muted"></i>
|
||||
</span>
|
||||
<span @dblclick="this.showBoth" class="splitaction resize">
|
||||
<i class="fas fa-grip-lines-vertical text-muted"></i>
|
||||
</span>
|
||||
<span @click="this.collapseLeft" class="splitaction">
|
||||
<i class="fas fa-angle-left text-muted"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="rightpanel" class="horizontalsplitted"
|
||||
:style="{ width: this.rightwidthcss }">
|
||||
<slot name="right">
|
||||
<p/>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
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';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,11 +1,5 @@
|
||||
export default {
|
||||
name: 'VerticalSplit',
|
||||
props: {
|
||||
defaultRatio: {
|
||||
type: Array,
|
||||
default: () => [50, 50]
|
||||
}
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
availHeight: 0,
|
||||
@@ -56,22 +50,17 @@ 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 * (this.defaultRatio[0] / 100));
|
||||
this.topheight = Math.floor(this.availHeight/2);
|
||||
} else {
|
||||
this.topheight = Math.floor( ((((this.topheight * 100) / oldavailHeight) / 100) * this.availHeight) );
|
||||
}
|
||||
|
||||
this.bottomheight = this.availHeight - this.topheight;
|
||||
},
|
||||
collapseTop: function() {
|
||||
@@ -85,8 +74,8 @@ export default {
|
||||
this.bottomheight = 0;
|
||||
},
|
||||
showBoth: function() {
|
||||
this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100));
|
||||
this.bottomheight = this.availHeight - this.topheight
|
||||
this.topheight = Math.floor(this.availHeight/2);
|
||||
this.bottomheight = Math.floor(this.availHeight/2);
|
||||
},
|
||||
isCollapsed: function() {
|
||||
if( this.topheight === 0 ) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 79 KiB |
@@ -94,6 +94,7 @@ require_once('dbupdate_3.4/71399_dashboard_update_widget_paths.php');
|
||||
require_once('dbupdate_3.4/71645_studvw_messagetab_ladezeit.php');
|
||||
require_once('dbupdate_3.4/71566_studienordnungsdokument_neuer_organisationseinheitstyp_programm.php');
|
||||
require_once('dbupdate_3.4/70376_lohnguide.php');
|
||||
require_once('dbupdate_3.4/76203_Asynchrone_Tasks.php');
|
||||
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');
|
||||
@@ -433,7 +434,7 @@ $tabellen=array(
|
||||
"system.tbl_log" => array("log_id","person_id","zeitpunkt","app","oe_kurzbz","logtype_kurzbz","logdata","insertvon","taetigkeit_kurzbz"),
|
||||
"system.tbl_logtype" => array("logtype_kurzbz", "data_schema"),
|
||||
"system.tbl_filters" => array("filter_id","app","dataset_name","filter_kurzbz","person_id","description","sort","default_filter","filter","oe_kurzbz","statistik_kurzbz"),
|
||||
"system.tbl_jobsqueue" => array("jobid", "type", "creationtime", "status", "input", "output", "starttime", "endtime", "insertvon", "insertamum"),
|
||||
"system.tbl_jobsqueue" => array("jobid", "type", "creationtime", "status", "input", "output", "starttime", "endtime", "insertvon", "insertamum", "pid", "uid", "progress"),
|
||||
"system.tbl_jobstatuses" => array("status"),
|
||||
"system.tbl_jobtriggers" => array("type", "status", "following_type"),
|
||||
"system.tbl_jobtypes" => array("type", "description"),
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
// Add column pid to system.tbl_jobsqueue
|
||||
if (!$result = @$db->db_query('SELECT "pid" FROM "system"."tbl_jobsqueue" LIMIT 1'))
|
||||
{
|
||||
$qry = 'ALTER TABLE "system"."tbl_jobsqueue" ADD "pid" INT NULL DEFAULT NULL;';
|
||||
if (!$db->db_query($qry))
|
||||
echo '<strong>system.tbl_jobsqueue: '.$db->db_last_error().'</strong><br>';
|
||||
else
|
||||
echo '<br>Added column pid to table system.tbl_jobsqueue';
|
||||
}
|
||||
|
||||
// Add column uid to system.tbl_jobsqueue
|
||||
if (!$result = @$db->db_query('SELECT "uid" FROM "system"."tbl_jobsqueue" LIMIT 1'))
|
||||
{
|
||||
$qry = 'ALTER TABLE "system"."tbl_jobsqueue" ADD "uid" VARCHAR(32) NULL DEFAULT NULL;';
|
||||
if (!$db->db_query($qry))
|
||||
echo '<strong>system.tbl_jobsqueue: '.$db->db_last_error().'</strong><br>';
|
||||
else
|
||||
echo '<br>Added column uid to table system.tbl_jobsqueue';
|
||||
}
|
||||
|
||||
// Add column progress to system.tbl_jobsqueue
|
||||
if (!$result = @$db->db_query('SELECT "progress" FROM "system"."tbl_jobsqueue" LIMIT 1'))
|
||||
{
|
||||
$qry = 'ALTER TABLE "system"."tbl_jobsqueue" ADD "progress" NUMERIC(2,1) NULL DEFAULT 0;';
|
||||
if (!$db->db_query($qry))
|
||||
echo '<strong>system.tbl_jobsqueue: '.$db->db_last_error().'</strong><br>';
|
||||
else
|
||||
echo '<br>Added column progress to table system.tbl_jobsqueue';
|
||||
}
|
||||
|
||||
// Add foreign key fk_jobsqueue_benutzer_uid on system.tbl_jobsqueue.uid with public.tbl_benutzer.uid
|
||||
if ($result = $db->db_query("SELECT conname FROM pg_constraint WHERE conname = 'fk_jobsqueue_benutzer_uid'"))
|
||||
{
|
||||
if ($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = 'ALTER TABLE "system"."tbl_jobsqueue" ADD CONSTRAINT "fk_jobsqueue_benutzer_uid" FOREIGN KEY ("uid") REFERENCES "public"."tbl_benutzer" ("uid") ON DELETE RESTRICT ON UPDATE CASCADE;';
|
||||
if (!$db->db_query($qry))
|
||||
echo '<strong>system.tbl_jobsqueue: '.$db->db_last_error().'</strong><br>';
|
||||
else
|
||||
echo '<br>Created foreign key fk_jobsqueue_benutzer_uid';
|
||||
}
|
||||
}
|
||||
|
||||
// Add new webservice type in system.tbl_webservicetyp
|
||||
if ($result = @$db->db_query("SELECT 1 FROM system.tbl_webservicetyp WHERE webservicetyp_kurzbz = 'lrt';"))
|
||||
{
|
||||
if ($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = "INSERT INTO system.tbl_webservicetyp(webservicetyp_kurzbz, beschreibung) VALUES('lrt', 'Long Run Task');";
|
||||
|
||||
if (!$db->db_query($qry))
|
||||
echo '<strong>system.tbl_webservicetyp '.$db->db_last_error().'</strong><br>';
|
||||
else
|
||||
echo ' system.tbl_webservicetyp: Added webservice type "lrt"<br>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1638,6 +1638,84 @@ $filters = array(
|
||||
}',
|
||||
'oe_kurzbz' => null,
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'dataset_name' => 'logs',
|
||||
'filter_kurzbz' => 'lrts24hours',
|
||||
'description' => '{Last 24 hours LRTs logs}',
|
||||
'sort' => 2,
|
||||
'default_filter' => false,
|
||||
'filter' => '
|
||||
{
|
||||
"name": "All Long Run Tasks logs from the last 24 hours",
|
||||
"columns": [
|
||||
{"name": "RequestId"},
|
||||
{"name": "ExecutionTime"},
|
||||
{"name": "ExecutedBy"},
|
||||
{"name": "Description"},
|
||||
{"name": "Data"}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"name": "WebserviceType",
|
||||
"operation": "contains",
|
||||
"condition": "job"
|
||||
},
|
||||
{
|
||||
"name": "RequestId",
|
||||
"operation": "contains",
|
||||
"condition": "LRT"
|
||||
},
|
||||
{
|
||||
"name": "ExecutionTime",
|
||||
"operation": "lt",
|
||||
"condition": "24",
|
||||
"option": "hours"
|
||||
}
|
||||
]
|
||||
}
|
||||
',
|
||||
'oe_kurzbz' => null,
|
||||
),
|
||||
array(
|
||||
'app' => 'core',
|
||||
'dataset_name' => 'logs',
|
||||
'filter_kurzbz' => 'lrts48hours',
|
||||
'description' => '{Last 48 hours LRTs logs}',
|
||||
'sort' => 2,
|
||||
'default_filter' => false,
|
||||
'filter' => '
|
||||
{
|
||||
"name": "All Long Run Tasks logs from the last 48 hours",
|
||||
"columns": [
|
||||
{"name": "RequestId"},
|
||||
{"name": "ExecutionTime"},
|
||||
{"name": "ExecutedBy"},
|
||||
{"name": "Description"},
|
||||
{"name": "Data"}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"name": "WebserviceType",
|
||||
"operation": "contains",
|
||||
"condition": "job"
|
||||
},
|
||||
{
|
||||
"name": "RequestId",
|
||||
"operation": "contains",
|
||||
"condition": "LRT"
|
||||
},
|
||||
{
|
||||
"name": "ExecutionTime",
|
||||
"operation": "lt",
|
||||
"condition": "48",
|
||||
"option": "hours"
|
||||
}
|
||||
]
|
||||
}
|
||||
',
|
||||
'oe_kurzbz' => null,
|
||||
),
|
||||
);
|
||||
|
||||
// Loop through the filters array
|
||||
|
||||
+71
-609
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user