Compare commits

...

22 Commits

Author SHA1 Message Date
Ivymaster f3e1208f27 BUG: add fix for weekdays format for time slots db 2026-05-20 13:46:54 +02:00
Ivymaster 13684e638f Add styling changes 2026-05-19 09:52:25 +02:00
Ivymaster 55be235fbe BUG: Add fix for drop higlight of first part body element in selector component 2026-05-18 18:13:33 +02:00
Ivymaster ec782d168c BUG: Fixed issue with org unit filtering in Validity period modal component 2026-05-18 17:59:03 +02:00
Ivymaster ddfcb68d62 BUG: Fixed issues with pointer event listeners on calendar selector 2026-05-18 17:45:08 +02:00
Ivymaster 3cf7543586 Add upper resize element to overlays in "ClassScheduleCalendarSelector" component 2026-05-12 17:59:42 +02:00
Ivymaster c6d438c032 Add minor refactor of "ClasscheduleCalendarSelector" component 2026-05-12 13:45:20 +02:00
Ivymaster 6560b27de9 Add removal of multiple weeks per validation period, and other minor changes 2026-05-11 18:45:44 +02:00
Ivymaster fe5931e6c7 BUG: add fix for wrong lang class schedule type display in ClassSchedule overlay selector component 2026-05-07 14:14:59 +02:00
Ivymaster a7c4571d25 Add removal of console logs in ClassSchedule components 2026-05-07 14:06:34 +02:00
Ivymaster 8d5588de02 Add link to ClassSchedule overview in navigation config 2026-05-06 12:56:44 +02:00
Ivymaster 6a813499d1 Add navigation menu to classSchedule page 2026-05-05 15:56:13 +02:00
Ivymaster 2c75fb7827 BUG: Fix issue with constant refetch upon orgUnit dropdown filtering 2026-04-29 13:24:46 +02:00
Ivymaster 9f8e292f49 Disable deleting of in-use ClassTimeSlotType db entries 2026-04-27 18:26:48 +02:00
Ivymaster 11f93f0c91 Added additional filters to 'classScheduleOverview' component 2026-04-27 18:09:57 +02:00
Ivymaster 7307c1fa44 add prefiltering on validity period form component 2026-04-24 14:08:11 +02:00
Ivymaster 869a7e3335 Create and implement "ClassScheduleCalendarSelector" in "ClassScheduleOverview" component 2026-04-21 14:43:58 +02:00
Ivymaster 010b370914 add class schedule controllers, components, and updated phrases 2026-04-14 16:21:46 +02:00
Ivymaster a29e0aeb62 add frontened calls to endpoints for fetching all study plans and all degree programes 2026-04-14 16:20:13 +02:00
Ivymaster fe68d46831 add endpoint for fetching all study plans 2026-04-14 16:17:38 +02:00
Ivymaster d8f5c412c5 add endpoint for fetching all degree programes 2026-04-14 16:17:17 +02:00
Ivymaster 59d531ed0f add necessary models and DB tables for the class schedule feature 2026-04-14 16:14:54 +02:00
38 changed files with 7222 additions and 18 deletions
+17
View File
@@ -45,6 +45,14 @@ $config['navigation_header'] = array(
'expand' => true,
'sort' => 30,
'requiredPermissions' => 'admin:w'
),
'classScheduleOverview' => array(
'link' => site_url('lehre/ClassSchedule'),
'icon' => '',
'description' => 'Unterrichtszeiten der Studiengänge',
'expand' => true,
'sort' => 50,
'requiredPermissions' => 'lehre/unterrichtszeiten_gk:r'
)
)
),
@@ -383,3 +391,12 @@ $config['navigation_menu']['apps'] = [
'requiredPermissions' => array('lehre/lehrauftrag_bestellen:r', 'lehre/lehrauftrag_erteilen:r')
]
];
$config['navigation_menu']['lehre/ClassSchedule/index'] = array(
'classScheduleOverview' => array(
'link' => site_url('lehre/ClassSchedule'),
'description' => 'Unterrichtszeiten der Studiengänge',
'icon' => '',
'sort' => 1
)
);
+3
View File
@@ -119,6 +119,9 @@ $route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/prestudent/(:num)'] = 'a
// // (studiensemester_kurzbz)/person/(person_id)
$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/person/(:num)'] = 'api/frontend/v1/stv/Students/getPerson/$1/$2';
$route['lehre/ClassSchedule/.*'] = 'lehre/ClassSchedule/index';
// load routes from extensions, also look for environment-specific configs
$subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions'];
@@ -0,0 +1,666 @@
<?php
/**
* Copyright (C) 2024 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
class ClassScheduleApi extends FHCAPI_Controller
{
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'getAllClassTimeValidityPeriods'=> array('lehre/unterrichtszeiten_gk:r'),
'getAllClassTimeValidityPeriodsPerOrganizationalUnit' => array('lehre/unterrichtszeiten_gk:r'),
'getClassTimeValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'createClassTimeSlotValidityPeriod' => array('lehre/unterrichtszeiten_gk:rw'),
'updateClassTimeSlotValidityPeriod' => array('lehre/unterrichtszeiten_gk:rw'),
'deleteClassTimeSlotValidityPeriod' => array('lehre/unterrichtszeiten_gk:rw'),
'getClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'createClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'updateClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'deleteClassTimeSlotsForValidityPeriod' => array('lehre/unterrichtszeiten_gk:r'),
'getAllClassScheduleTypes' => array('lehre/unterrichtszeiten_typ:r'),
'createClassTimeSlotType' => array('lehre/unterrichtszeiten_typ:rw'),
'updateClassTimeSlotType' => array('lehre/unterrichtszeiten_typ:rw'),
'deleteClassTimeSlotType' => array('lehre/unterrichtszeiten_typ:rw'),
]);
$this->load->library('form_validation');
$this->load->model('education/ClassTimeSlotValidityPeriod_model', "ClassTimeSlotValidityPeriodModel");
$this->load->model('education/ClassTimeSlot_model', "ClassTimeSlotModel");
$this->load->model('education/ClassTimeSlotType_model', "ClassTimeSlotTypeModel");
// Loads phrases system
$this->loadPhrases([
'global',
'ui',
]);
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function getAllClassTimeValidityPeriods()
{
$entitledOrganizationalUnitsShortCodes = $this->permissionlib->getOE_isEntitledFor('lehre/unterrichtszeiten_gk');
$organizationalUnitShortCode = $this->input->get('organizationalUnitShortCode');
$validityPeriodFrom = $this->input->get('validityPeriodFrom');
$validityPeriodTo = $this->input->get('validityPeriodTo');
$this->ClassTimeSlotValidityPeriodModel->db->where_in('lehre.tbl_unterrichtszeiten_gueltigkeit.oe_kurzbz', $entitledOrganizationalUnitsShortCodes);
$this->ClassTimeSlotValidityPeriodModel->addSelect(
'lehre.tbl_unterrichtszeiten_gueltigkeit.*,' .
'public.tbl_organisationseinheit.bezeichnung as organisationseinheit_bezeichnung,' .
'public.tbl_organisationseinheit.organisationseinheittyp_kurzbz as organisationseinheit_organisationseinheittyp_kurzbz,' .
'lehre.tbl_studienplan.bezeichnung as studienplan_bezeichnung,' .
'lehre.tbl_unterrichtszeiten_typ.bezeichnung_mehrsprachig as unterrichtszeiten_typ_bezeichnung_mehrsprachig, '
);
$this->ClassTimeSlotValidityPeriodModel->addJoin('lehre.tbl_studienplan', 'lehre.tbl_studienplan.studienplan_id=lehre.tbl_unterrichtszeiten_gueltigkeit.studienplan_id', 'LEFT');
$this->ClassTimeSlotValidityPeriodModel->addJoin('public.tbl_organisationseinheit', 'public.tbl_organisationseinheit.oe_kurzbz=lehre.tbl_unterrichtszeiten_gueltigkeit.oe_kurzbz', 'LEFT');
$this->ClassTimeSlotValidityPeriodModel->addJoin('lehre.tbl_unterrichtszeiten_typ', 'lehre.tbl_unterrichtszeiten_typ.unterrichtszeitentyp_kurzbz=lehre.tbl_unterrichtszeiten_gueltigkeit.unterrichtszeitentyp_kurzbz', 'LEFT');
$this->ClassTimeSlotValidityPeriodModel->addOrder('gueltig_von', 'DESC');
if ($organizationalUnitShortCode) {
$this->ClassTimeSlotValidityPeriodModel->db->where('lehre.tbl_unterrichtszeiten_gueltigkeit.oe_kurzbz', $organizationalUnitShortCode);
}
if ($validityPeriodFrom) {
$this->ClassTimeSlotValidityPeriodModel->db
->where('lehre.tbl_unterrichtszeiten_gueltigkeit.gueltig_von >=', date('Y-m-d', strtotime($validityPeriodFrom)));
}
if ($validityPeriodTo) {
$this->ClassTimeSlotValidityPeriodModel->db
->where('lehre.tbl_unterrichtszeiten_gueltigkeit.gueltig_bis <=', date('Y-m-d', strtotime($validityPeriodTo)));
}
$class_time_slot_validity_period_res = $this->ClassTimeSlotValidityPeriodModel->load();
$class_time_slot_validity_period_res = $this->getDataOrTerminateWithError($class_time_slot_validity_period_res);
$this->terminateWithSuccess($class_time_slot_validity_period_res);
}
public function getAllClassTimeValidityPeriodsPerOrganizationalUnit($organizationUnitId)
{
if (!$this->isUserEntitledForOrganizationalUnit($organizationUnitId, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->ClassTimeSlotValidityPeriodModel->addSelect(
'lehre.tbl_unterrichtszeiten_gueltigkeit.*,' .
'public.tbl_organisationseinheit.bezeichnung as organisationseinheit_bezeichnung,' .
'public.tbl_organisationseinheit.organisationseinheittyp_kurzbz as organisationseinheit_organisationseinheittyp_kurzbz,' .
'lehre.tbl_studienplan.bezeichnung as studienplan_bezeichnung,' .
'lehre.tbl_unterrichtszeiten_typ.bezeichnung_mehrsprachig as unterrichtszeiten_typ_bezeichnung_mehrsprachig, '
);
$this->ClassTimeSlotValidityPeriodModel->addJoin('lehre.tbl_studienplan', 'lehre.tbl_studienplan.studienplan_id=lehre.tbl_unterrichtszeiten_gueltigkeit.studienplan_id', 'LEFT');
$this->ClassTimeSlotValidityPeriodModel->addJoin('public.tbl_organisationseinheit', 'public.tbl_organisationseinheit.oe_kurzbz=lehre.tbl_unterrichtszeiten_gueltigkeit.oe_kurzbz', 'LEFT');
$this->ClassTimeSlotValidityPeriodModel->addJoin('lehre.tbl_unterrichtszeiten_typ', 'lehre.tbl_unterrichtszeiten_typ.unterrichtszeitentyp_kurzbz=lehre.tbl_unterrichtszeiten_gueltigkeit.unterrichtszeitentyp_kurzbz', 'LEFT');
$this->ClassTimeSlotValidityPeriodModel->addOrder('gueltig_von', 'DESC');
$class_time_slot_validity_period_res = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['lehre.tbl_unterrichtszeiten_gueltigkeit.oe_kurzbz' => $organizationUnitId]);
$class_time_slot_validity_period_res = $this->getDataOrTerminateWithError($class_time_slot_validity_period_res);
$this->terminateWithSuccess($class_time_slot_validity_period_res);
}
public function getClassTimeValidityPeriod($classTimeSlotValidityPeriodId)
{
$this->ClassTimeSlotValidityPeriodModel->addSelect('
lehre.tbl_unterrichtszeiten_gueltigkeit.*,
public.tbl_organisationseinheit.oe_kurzbz as oe_kurzbz,
public.tbl_organisationseinheit.bezeichnung as oe_bezeichnung,
public.tbl_organisationseinheit.organisationseinheittyp_kurzbz as oe_organisationseinheittyp_kurzbz,
lehre.tbl_studienplan.studienplan_id,
lehre.tbl_studienplan.bezeichnung as studienplan_bezeichnung,
');
$this->ClassTimeSlotValidityPeriodModel->addJoin('public.tbl_organisationseinheit', 'public.tbl_organisationseinheit.oe_kurzbz=lehre.tbl_unterrichtszeiten_gueltigkeit.oe_kurzbz', 'LEFT');
$this->ClassTimeSlotValidityPeriodModel->addJoin('lehre.tbl_studienplan', 'lehre.tbl_studienplan.studienplan_id=lehre.tbl_unterrichtszeiten_gueltigkeit.studienplan_id', 'LEFT');
$class_time_slot_validity_period_res = $this->ClassTimeSlotValidityPeriodModel->load($classTimeSlotValidityPeriodId);
$class_time_slot_validity_period_res = $this->getDataOrTerminateWithError($class_time_slot_validity_period_res);
if (!$class_time_slot_validity_period_res || count($class_time_slot_validity_period_res) === 0) {
$this->terminateWithError($this->p->t('ui', 'classTimeSlotValidityPeriodNotFound'));
}
if (!$this->isUserEntitledForOrganizationalUnit($class_time_slot_validity_period_res[0]->oe_kurzbz, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->terminateWithSuccess($class_time_slot_validity_period_res);
}
public function createClassTimeSlotValidityPeriod()
{
$this->form_validation->set_rules('validityPeriodFrom', 'Validity Period From', 'required|is_valid_date[Y-m-d]', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_validityPeriodFrom')]),
'is_valid_date' => $this->p->t('ui', 'error_fieldInvalidDate', ['field' => $this->p->t('ui', 'field_validityPeriodFrom')])
]);
$this->form_validation->set_rules('validityPeriodTo', 'Validity Period To', 'required|is_valid_date[Y-m-d]|callback_date_greater_equal[validityPeriodFrom]');
$this->form_validation->set_rules('organizationalUnitShortCode', 'Organizational Unit Shortcode', 'required|max_length[32]', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_organizationalUnit')]),
'max_length' => $this->p->t('ui', 'error_fieldMaxLength', ['field' => $this->p->t('ui', 'field_organizationalUnit'), 'max' => 32])
]);
$this->form_validation->set_rules('semester', 'Semester', 'is_natural_no_zero|less_than_equal_to[8]', [
'is_natural_no_zero' => $this->p->t('ui', 'error_fieldInvalid', ['field' => $this->p->t('ui', 'field_semester')]),
'less_than_equal_to' => $this->p->t('ui', 'error_fieldMaxValue', ['field' => $this->p->t('ui', 'field_semester'), 'max' => 8])
]);
$this->form_validation->set_rules('classTimeSlotTypeShortcode', 'Class Time Slot Type Shortcode', 'max_length[32]');
$this->form_validation->set_rules('studyPlanId', 'Study Plan ID', 'is_natural_no_zero');
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
if (!$this->isUserEntitledForOrganizationalUnit($this->input->post('organizationalUnitShortCode'), 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->db->trans_start();
$result = $this->ClassTimeSlotValidityPeriodModel->insert([
'gueltig_von' => $this->input->post('validityPeriodFrom'),
'gueltig_bis' => $this->input->post('validityPeriodTo'),
'oe_kurzbz' => $this->input->post('organizationalUnitShortCode'),
'ausbildungssemester' => $this->input->post('semester'),
'anmerkung' => $this->input->post('description'),
'unterrichtszeitentyp_kurzbz' => $this->input->post('classTimeSlotTypeShortcode') ?? null,
'studienplan_id' => $this->input->post('studyPlanId'),
'insertamum' => date('c'),
'insertvon' => getAuthUid(),
'updateamum' => date('c'),
'updatevon' => getAuthUid(),
]);
$data = $this->getDataOrTerminateWithError($result);
$this->db->trans_complete();
$this->terminateWithSuccess(true);
}
public function updateClassTimeSlotValidityPeriod($classTimeSlotValidityPeriodId)
{
$this->form_validation->set_rules('validityPeriodFrom', 'Validity Period From', 'required|is_valid_date[Y-m-d]', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_validityPeriodFrom')]),
'is_valid_date' => $this->p->t('ui', 'error_fieldInvalidDate', ['field' => $this->p->t('ui', 'field_validityPeriodFrom')])
]);
$this->form_validation->set_rules('validityPeriodTo', 'Validity Period To', 'required|is_valid_date[Y-m-d]|callback_date_greater_equal[validityPeriodFrom]');
$this->form_validation->set_rules('organizationalUnitShortCode', 'Organizational Unit Shortcode', 'required|max_length[32]', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_organizationalUnit')]),
'max_length' => $this->p->t('ui', 'error_fieldMaxLength', ['field' => $this->p->t('ui', 'field_organizationalUnit'), 'max' => 32])
]);
$this->form_validation->set_rules('semester', 'Semester', 'is_natural_no_zero|less_than_equal_to[8]', [
'is_natural_no_zero' => $this->p->t('ui', 'error_fieldInvalid', ['field' => $this->p->t('ui', 'field_semester')]),
'less_than_equal_to' => $this->p->t('ui', 'error_fieldMaxValue', ['field' => $this->p->t('ui', 'field_semester'), 'max' => 8])
]);
$this->form_validation->set_rules('classTimeSlotTypeShortcode', 'Class Time Slot Type Shortcode', 'max_length[32]');
$this->form_validation->set_rules('studyPlanId', 'Study Plan ID', 'is_natural_no_zero');
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
if (!$this->isUserEntitledForOrganizationalUnit($this->input->post('organizationalUnitShortCode'), 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->db->trans_start();
$result = $this->ClassTimeSlotValidityPeriodModel->update($classTimeSlotValidityPeriodId, [
'gueltig_von' => $this->input->post('validityPeriodFrom'),
'gueltig_bis' => $this->input->post('validityPeriodTo'),
'oe_kurzbz' => $this->input->post('organizationalUnitShortCode'),
'ausbildungssemester' => $this->input->post('semester'),
'anmerkung' => $this->input->post('description'),
'unterrichtszeitentyp_kurzbz' => $this->input->post('classTimeSlotTypeShortcode') ?? null,
'studienplan_id' => $this->input->post('studyPlanId'),
'updateamum' => date('c'),
'updatevon' => getAuthUid(),
]);
$this->db->trans_complete();
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess(true);
}
public function deleteClassTimeSlotValidityPeriod($classTimeSlotValidityPeriodId)
{
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$validityPeriod = $this->getDataOrTerminateWithError($validityPeriodResult)[0];
if (!$this->isUserEntitledForOrganizationalUnit($validityPeriod->oe_kurzbz, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->db->trans_start();
$result = $this->ClassTimeSlotModel->delete(['unterrichtszeitengueltigkeit_id'=> $classTimeSlotValidityPeriodId]);
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$result = $this->ClassTimeSlotValidityPeriodModel->delete($classTimeSlotValidityPeriodId);
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->db->trans_complete();
$this->terminateWithSuccess(true);
}
public function getClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
{
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$validityPeriodData = $this->getDataOrTerminateWithError($validityPeriodResult);
if (!$validityPeriodData || count($validityPeriodData) === 0) {
$this->terminateWithError($this->p->t('ui', 'classTimeSlotValidityPeriodNotFound'));
}
$validityPeriod = $validityPeriodData[0];
if (!$this->isUserEntitledForOrganizationalUnit($validityPeriod->oe_kurzbz, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->ClassTimeSlotModel->addOrder('insertamum', 'DESC');
$class_time_slots_res = $this->ClassTimeSlotModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$class_time_slots_res = $this->getDataOrTerminateWithError($class_time_slots_res);
$this->terminateWithSuccess($class_time_slots_res);
}
public function createClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
{
$this->form_validation->set_rules('unterrichtszeiten', 'Class Time Slots', 'callback_validate_items_in_class_time_slots');
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$validityPeriod = $this->getDataOrTerminateWithError($validityPeriodResult)[0];
if (!$this->isUserEntitledForOrganizationalUnit($validityPeriod->oe_kurzbz, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->db->trans_start();
foreach ($this->input->post('unterrichtszeiten') as $timeSlot) {
$result = $this->ClassTimeSlotModel->insert([
'wochentag' => $timeSlot['wochentag'],
'uhrzeit_von' => $timeSlot['startTime'],
'uhrzeit_bis' => $timeSlot['endTime'],
'unterrichtszeitentyp_kurzbz' => $timeSlot['classTimeSlotTypeShortcode'],
'unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId,
'insertamum' => date('c'),
'insertvon' => getAuthUid(),
'updateamum' => date('c'),
'updatevon' => getAuthUid(),
]);
$this->getDataOrTerminateWithError($result);
}
$this->db->trans_complete();
$this->terminateWithSuccess(true);
}
public function updateClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
{
$this->form_validation->set_rules('unterrichtszeiten', 'Class Time Slots', 'callback_validate_items_in_class_time_slots');
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$validityPeriod = $this->getDataOrTerminateWithError($validityPeriodResult)[0];
if (!$this->isUserEntitledForOrganizationalUnit($validityPeriod->oe_kurzbz, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->db->trans_start();
$currentTimeSlotsResult = $this->ClassTimeSlotModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$currentTimeSlots = $this->getDataOrTerminateWithError($currentTimeSlotsResult);
$currentTimeSlotIds = array_column($currentTimeSlots, 'unterrichtszeit_id');
$removedTimeSlotIds = array_values(array_diff($currentTimeSlotIds, array_column($this->input->post('unterrichtszeiten'), 'id')));
if (count($removedTimeSlotIds) > 0) {
$query = 'DELETE FROM lehre.tbl_unterrichtszeiten WHERE unterrichtszeit_id IN ?';
$result = $this->db->query($query, [ $removedTimeSlotIds ]);
}
foreach ($this->input->post('unterrichtszeiten') as $timeSlot) {
$data = [
'wochentag' => $timeSlot['wochentag'],
'uhrzeit_von' => $timeSlot['startTime'],
'uhrzeit_bis' => $timeSlot['endTime'],
'unterrichtszeitentyp_kurzbz' => $timeSlot['classTimeSlotTypeShortcode'],
'unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId,
'updateamum' => date('c'),
'updatevon' => getAuthUid(),
];
if (isset($timeSlot['id'])) {
$result = $this->ClassTimeSlotModel->update($timeSlot['id'], $data);
} else {
$result = $this->ClassTimeSlotModel->insert(array_merge($data, [
'insertvon' => getAuthUid(),
'updateamum' => date('c'),
]));
}
$this->getDataOrTerminateWithError($result);
}
$this->db->trans_complete();
$this->terminateWithSuccess(true);
}
public function deleteClassTimeSlotsForValidityPeriod($classTimeSlotValidityPeriodId)
{
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitengueltigkeit_id' => $classTimeSlotValidityPeriodId]);
$validityPeriod = $this->getDataOrTerminateWithError($validityPeriodResult)[0];
if (!$this->isUserEntitledForOrganizationalUnit($validityPeriod->oe_kurzbz, 'lehre/unterrichtszeiten_gk')) {
$this->terminateWithError($this->p->t('ui', 'keineBerechtigung'));
}
$this->db->trans_start();
$result = $this->ClassTimeSlotModel->delete(['unterrichtszeitengueltigkeit_id'=> $classTimeSlotValidityPeriodId]);
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->db->trans_complete();
$this->terminateWithSuccess(true);
}
public function getAllClassScheduleTypes()
{
$filter = $this->input->get('filter');
if ($filter) {
if (isset($filter['aktiv'])) {
$this->ClassTimeSlotTypeModel->db->where('aktiv', $filter['aktiv']);
}
}
$class_schedule_types_res = $this->ClassTimeSlotTypeModel->load();
$class_schedule_types_res = $this->getDataOrTerminateWithError($class_schedule_types_res);
$this->terminateWithSuccess($class_schedule_types_res);
}
public function createClassTimeSlotType()
{
$this->form_validation->set_rules('shortCode', 'Short Code', 'required|max_length[32]', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_classTimeSlotTypeShortCode')]),
'max_length' => $this->p->t('ui', 'error_fieldMaxLength', ['field' => $this->p->t('ui', 'field_classTimeSlotTypeShortCode'), 'max' => 32])
]);
$this->form_validation->set_rules('descriptions', 'Descriptions', 'callback_validate_descriptions_array');
$this->form_validation->set_rules('backgroundColor', 'Background Color', 'required|regex_match[/^#([0-9a-fA-F]{3}){1,2}$/]', [
'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_backgroundColor')]),
'regex_match' => $this->p->t('ui', 'error_fieldInvalid', ['field' => $this->p->t('ui', 'field_backgroundColor')]),
]);
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
$this->db->trans_start();
$descriptions = $this->input->post('descriptions');
$pgArray = $this->arrayToPgArray($descriptions);
$query = 'INSERT INTO lehre.tbl_unterrichtszeiten_typ (
unterrichtszeitentyp_kurzbz,
bezeichnung_mehrsprachig,
hintergrundfarbe,
aktiv,
insertamum,
insertvon,
updateamum,
updatevon) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
$result = $this->db->query($query, [
$this->input->post('shortCode'),
$pgArray,
$this->input->post('backgroundColor'),
$this->input->post('isActive'),
date('c'),
getAuthUid(),
date('c'),
getAuthUid(),
]);
$this->db->trans_complete();
$this->terminateWithSuccess(true);
}
public function updateClassTimeSlotType($classTimeSlotTypeId)
{
$this->form_validation->set_rules('descriptions', 'Descriptions', 'callback_validate_descriptions_array');
if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array());
$parsedClassTimeSlotTypeId = urldecode($classTimeSlotTypeId);
$descriptions = $this->input->post('descriptions');
$pgArray = $this->arrayToPgArray($descriptions);
$query = 'UPDATE lehre.tbl_unterrichtszeiten_typ SET bezeichnung_mehrsprachig = ?, hintergrundfarbe = ?, aktiv = ?, updateamum = ?, updatevon = ? WHERE unterrichtszeitentyp_kurzbz = ?';
$result = $this->db->query($query, [
$pgArray,
$this->input->post('backgroundColor'),
$this->input->post('isActive'),
date('c'),
getAuthUid(),
$parsedClassTimeSlotTypeId,
]);
$this->db->trans_complete();
$this->terminateWithSuccess(true);
}
public function deleteClassTimeSlotType($classTimeSlotTypeId)
{
$isClassTimeSlotDeletable = true;
$parsedClassTimeSlotTypeId = urldecode($classTimeSlotTypeId);
$validityPeriodResult = $this->ClassTimeSlotValidityPeriodModel->loadWhere(['unterrichtszeitentyp_kurzbz' => $parsedClassTimeSlotTypeId]);
$validityPeriod = $this->getDataOrTerminateWithError($validityPeriodResult);
if ($validityPeriod && count($validityPeriod) > 0) {
$isClassTimeSlotDeletable = false;
}
$classTimeSlotResult = $this->ClassTimeSlotModel->loadWhere(['unterrichtszeitentyp_kurzbz' => $parsedClassTimeSlotTypeId]);
$classTimeSlot = $this->getDataOrTerminateWithError($classTimeSlotResult);
if ($classTimeSlot && count($classTimeSlot) > 0) {
$isClassTimeSlotDeletable = false;
}
$this->db->trans_start();
if (!$isClassTimeSlotDeletable) {
$result = $this->ClassTimeSlotTypeModel->update($parsedClassTimeSlotTypeId, ['aktiv' => false, 'updateamum' => date('c'), 'updatevon' => getAuthUid()]);
} else {
$result = $this->ClassTimeSlotTypeModel->delete(['unterrichtszeitentyp_kurzbz' => $parsedClassTimeSlotTypeId]);
}
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
$this->db->trans_complete();
$this->terminateWithSuccess(true);
}
//------------------------------------------------------------------------------------------------------------------
// Private methods
private function arrayToPgArray(array $assoc) {
$flat = [null, null];
foreach ($assoc as $assocItem) {
if ($assocItem['lang'] === 'de') {
$flat[0] = $assocItem['value'];
} else if ($assocItem['lang'] === 'en') {
$flat[1] = $assocItem['value'];
} else {
$flat[] = $assocItem['value'];
}
}
$escaped = array_map(function ($v) {
return '"' . addslashes($v) . '"';
}, $flat);
return '{' . implode(',', $escaped) . '}';
}
public function date_greater_equal($toDate, $fromField)
{
$fromDate = $this->input->post($fromField);
if (!$fromDate) {
$this->form_validation->set_message(
'date_greater_equal',
$this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_validityPeriodTo')])
);
return false;
}
if (!strtotime($toDate) || !strtotime($fromDate)) {
$this->form_validation->set_message(
'date_greater_equal',
$this->p->t('ui', 'error_fieldInvalidDate', ['field' => $this->p->t('ui', 'field_validityPeriodTo')])
);
return false;
}
if (strtotime($toDate) < strtotime($fromDate)) {
$this->form_validation->set_message(
'date_greater_equal',
$this->p->t('ui', 'error_fieldDateGreaterEqual', ['field' => $this->p->t('ui', 'field_validityPeriodTo'), 'otherField' => $this->p->t('ui', 'field_validityPeriodFrom')])
);
return false;
}
return true;
}
public function validate_items_in_class_time_slots($unterrichtszeiten)
{
if (!is_array($this->input->post('unterrichtszeiten')) || count($this->input->post('unterrichtszeiten')) === 0) {
$this->form_validation->set_message(
'validate_items_in_class_time_slots',
$this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_classTimeSlot')])
);
return false;
}
foreach ($this->input->post('unterrichtszeiten') as $index => $timeSlot) {
if (!isset($timeSlot['wochentag'], $timeSlot['startTime'], $timeSlot['endTime'], $timeSlot['classTimeSlotTypeShortcode'])) {
$this->form_validation->set_message(
'validate_items_in_class_time_slots',
$this->p->t('ui', 'error_fieldClassTimeSlotContentInvalid', ['field' => $this->p->t('ui', 'field_classTimeSlot')])
);
return false;
}
if (!in_array($timeSlot['wochentag'], [1, 2, 3, 4, 5, 6, 0])) {
$this->form_validation->set_message(
'validate_items_in_class_time_slots',
$this->p->t('ui', 'error_fieldWeekdayInvalid', ['field' => $this->p->t('ui', 'field_classTimeSlot')])
);
return false;
}
if (!strtotime($timeSlot['startTime']) || !strtotime($timeSlot['endTime'])) {
$this->form_validation->set_message(
'validate_items_in_class_time_slots',
$this->p->t('ui', 'error_fieldClassTimeSlotTimeInvalid', ['field' => $this->p->t('ui', 'field_classTimeSlot')])
);
return false;
}
if (strtotime($timeSlot['endTime']) <= strtotime($timeSlot['startTime'])) {
$this->form_validation->set_message(
'validate_items_in_class_time_slots',
$this->p->t('ui', 'error_fieldDateGreaterEqual', ['field' => $this->p->t('ui', 'field_classTimeSlotEndTime'), 'otherField' => $this->p->t('ui', 'field_classTimeSlotStartTime')])
);
return false;
}
}
$slotsByDay = [];
foreach ($this->input->post('unterrichtszeiten') as $timeSlot) {
$slotsByDay[$timeSlot['wochentag']][] = $timeSlot;
}
foreach ($slotsByDay as $day => $slots) {
usort($slots, function ($a, $b) {
return strtotime($a['startTime']) - strtotime($b['startTime']);
});
for ($i = 1; $i < count($slots); $i++) {
if (strtotime($slots[$i]['startTime']) < strtotime($slots[$i - 1]['endTime'])) {
$this->form_validation->set_message(
'validate_items_in_class_time_slots',
$this->p->t('ui', 'error_fieldClassTimeSlotOverlap', ['field' => $this->p->t('ui', 'field_classTimeSlot')])
);
return false;
}
}
}
return true;
}
public function validate_descriptions_array($descriptions)
{
$descriptions = $this->input->post('descriptions');
if (!is_array($descriptions) || count($descriptions) === 0) {
$this->form_validation->set_message(
'validate_descriptions_array',
$this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'field_descriptions')])
);
return false;
}
foreach ($descriptions as $index => $description) {
if (!isset($description['lang'], $description['value'])) {
$this->form_validation->set_message(
'validate_descriptions_array',
$this->p->t('ui', 'error_fieldDescriptionContentInvalid')
);
return false;
}
if (empty($description['lang']) || empty($description['value'])) {
$this->form_validation->set_message(
'validate_descriptions_array',
$this->p->t('ui', 'error_fieldDescriptionContentInvalid')
);
return false;
}
}
return true;
}
private function isUserEntitledForOrganizationalUnit($organizationalUnitShortCode, $requiredPermission)
{
$entitledOrganizationalUnitsShortCodes = $this->permissionlib->getOE_isEntitledFor($requiredPermission);
return in_array($organizationalUnitShortCode, $entitledOrganizationalUnitsShortCodes);
}
}
@@ -0,0 +1,43 @@
<?php
/**
* Copyright (C) 2024 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
class ClassroomHourApi extends FHCAPI_Controller
{
public function __construct()
{
parent::__construct([
'getAllClassroomHours' => self::PERM_LOGGED,
]);
$this->load->model('ressource/Stunde_model', "ClassroomHourModel");
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function getAllClassroomHours()
{
$this->ClassroomHourModel->addOrder('stunde', 'ASC');
$result = $this->ClassroomHourModel->load();
$this->terminateWithSuccess($this->getDataOrTerminateWithError($result));
}
}
@@ -26,7 +26,8 @@ class Studgang extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
'getStudiengangInfo'=> self::PERM_LOGGED,
'getAllStudiengange'=> self::PERM_LOGGED,
'getAllDegreePrograms'=> self::PERM_LOGGED,
]);
@@ -43,6 +44,14 @@ class Studgang extends FHCAPI_Controller
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function getAllDegreePrograms()
{
$this->load->model('organisation/Studiengang_model', "StudiengangModel");
$class_schedule_res = $this->StudiengangModel->load();
$class_schedule_res = $this->getDataOrTerminateWithError($class_schedule_res);
$this->terminateWithSuccess($class_schedule_res);
}
public function getStudiengangInfo(){
$isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID());
$isMitarbeiter = $this->getDataOrTerminateWithError($isMitarbeiter);
@@ -0,0 +1,56 @@
<?php
/**
* Copyright (C) 2024 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
if (! defined('BASEPATH')) exit('No direct script access allowed');
class OrganizationalUnitApi extends FHCAPI_Controller
{
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'getAllOrganizationalUnits'=> array('basis/organisationseinheit:r'),
]);
$this->load->library('form_validation');
$this->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel');
// Loads phrases system
$this->loadPhrases([
'global'
]);
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function getAllOrganizationalUnits()
{
$entitledOrganizationalUnitsShortCodes = $this->permissionlib->getOE_isEntitledFor('basis/organisationseinheit');
$this->OrganisationseinheitModel->db->where_in('oe_kurzbz', $entitledOrganizationalUnitsShortCodes);
$result = $this->OrganisationseinheitModel->load();
$organization_units_result = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($organization_units_result);
}
}
@@ -20,10 +20,41 @@ class Studienplan extends FHCAPI_Controller
{
// TODO(chris): access!
parent::__construct([
'getBySemester' => self::PERM_LOGGED
'getAllStudyPlans' => self::PERM_LOGGED,
'getStudyPlansByOrganizationalUnitAndSemesterDates' => self::PERM_LOGGED,
'getBySemester' => self::PERM_LOGGED,
'getStudyPlan' => self::PERM_LOGGED,
]);
}
public function getAllStudyPlans()
{
$this->load->model('organisation/Studienplan_model', 'StudienplanModel');
$result = $this->StudienplanModel->load();
$studien_plan_result = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($studien_plan_result);
}
public function getStudyPlansByOrganizationalUnitAndSemesterDates($organizationalUnitShortCode)
{
$this->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel');
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->load->model('organisation/Studienordnung_model', 'StudienordnungModel');
$this->load->model('organisation/Studienplan_model', 'StudienplanModel');
$startDate = date('Y-m-d', strtotime($this->input->get('filter[startDate]')));
$endDate = date('Y-m-d', strtotime($this->input->get('filter[endDate]')));
if (!$startDate || !$endDate) {
return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Start- oder Enddatum']), self::ERROR_TYPE_GENERAL);
}
$studyPlansResponse = $this->StudienplanModel->getStudyPlansForOrganizationalUnitAndDatesQueryResponse($organizationalUnitShortCode, $startDate, $endDate);
if (isError($studyPlansResponse)) $this->terminateWithError(getError($studyPlansResponse), self::ERROR_TYPE_DB);
if (!hasData($studyPlansResponse)) return $this->terminateWithSuccess(null);
return $this->terminateWithSuccess($this->getDataOrTerminateWithError($studyPlansResponse));
}
public function getBySemester()
{
$this->load->model('organisation/Studienplan_model', 'StudienplanModel');
@@ -66,4 +97,15 @@ class Studienplan extends FHCAPI_Controller
$this->terminateWithSuccess(hasData($result) ? getData($result) : []);
}
public function getStudyPlan($id)
{
$this->load->model('organisation/Studienplan_model', 'StudienplanModel');
$result = $this->StudienplanModel->loadWhere(['studienplan_id' => $id]);
if (isError($result)) $this->terminateWithError(getError($result), self::ERROR_TYPE_DB);
if (!hasData($result)) return $this->terminateWithSuccess(null);
$this->terminateWithSuccess(getData($result)[0]);
}
}
@@ -26,11 +26,16 @@ class Studiensemester extends FHCAPI_Controller
'getAll' => self::PERM_LOGGED,
'getAktNext' => self::PERM_LOGGED,
'getStudienjahrByStudiensemester' => self::PERM_LOGGED,
'getAllStudiensemesterAndAktOrNext' => self::PERM_LOGGED
'getAllStudiensemesterAndAktOrNext' => self::PERM_LOGGED,
'getStudySemestersByOrganizationalUnitAndDates' => self::PERM_LOGGED,
'getStudySemestersByStudyPlanAndDates' => self::PERM_LOGGED,
)
);
// Load model StudiensemesterModel
$this->load->model('organisation/studiensemester_model', 'StudiensemesterModel');
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->load->model('organisation/Studienordnung_model', 'StudienordnungModel');
$this->load->model('organisation/Studienplan_model', 'StudienplanModel');
}
/**
@@ -166,4 +171,47 @@ class Studiensemester extends FHCAPI_Controller
$this->terminateWithSuccess(array($studiensemester, $aktuell));
}
public function getStudySemestersByOrganizationalUnitAndDates($organizationalUnitShortCode)
{
$startDate = date('Y-m-d', strtotime($this->input->get('filter[startDate]')));
$endDate = date('Y-m-d', strtotime($this->input->get('filter[endDate]')));
if (!$startDate || !$endDate) {
return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Start- oder Enddatum']), self::ERROR_TYPE_GENERAL);
}
$studyPlansResponse = $this->StudienplanModel->getStudyPlansForOrganizationalUnitQueryResponse($organizationalUnitShortCode);
if (isError($studyPlansResponse)) $this->terminateWithError(getError($studyPlansResponse), self::ERROR_TYPE_DB);
if (!hasData($studyPlansResponse)) return $this->terminateWithSuccess(null);
$studyPlans = $this->getDataOrTerminateWithError($studyPlansResponse);
$studyPlansIds = array_map(function ($studyPlan) { return $studyPlan->studienplan_id; }, $studyPlans);
$studySemestersResponse = $this->StudiensemesterModel->getStudySemestersByStudyPlansAndDatesQueryResponse($studyPlansIds, $startDate, $endDate);
if (isError($studySemestersResponse)) $this->terminateWithError(getError($studySemestersResponse), self::ERROR_TYPE_DB);
if (!hasData($studySemestersResponse)) return $this->terminateWithSuccess([]);
$studySemesters = $this->getDataOrTerminateWithError($studySemestersResponse);
return $this->terminateWithSuccess($studySemesters);
}
public function getStudySemestersByStudyPlanAndDates()
{
$studyPlansId = $this->input->get('filter[studyPlanId]');
$startDate = date('Y-m-d', strtotime($this->input->get('filter[startDate]')));
$endDate = date('Y-m-d', strtotime($this->input->get('filter[endDate]')));
if (!$startDate || !$endDate) {
return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Start- oder Enddatum']), self::ERROR_TYPE_GENERAL);
}
$studySemestersResponse = $this->StudiensemesterModel->getStudySemestersByStudyPlansAndDatesQueryResponse([$studyPlansId], $startDate, $endDate);
if (isError($studySemestersResponse)) $this->terminateWithError(getError($studySemestersResponse), self::ERROR_TYPE_DB);
if (!hasData($studySemestersResponse)) return $this->terminateWithSuccess([]);
$studySemesters = $this->getDataOrTerminateWithError($studySemestersResponse);
return $this->terminateWithSuccess($studySemesters);
}
}
@@ -0,0 +1,62 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*/
class ClassSchedule extends Auth_Controller
{
private $_uid; // uid of the logged user
/**
* Constructor
*/
public function __construct()
{
parent::__construct(
array(
'index' => array('lehre/unterrichtszeiten_gk:r'),
)
);
$this->load->library('PermissionLib');
$this->load->library('AuthLib');
$this->loadPhrases(
array(
'ui',
'global',
'person',
'abschlusspruefung',
'password',
'lehre'
)
);
$this->_setAuthUID();
}
// -----------------------------------------------------------------------------------------------------------------
// Public methods
public function index()
{
return $this->load->view('lehre/class_schedule/index.php', [
'permissions' => [
'lehre/unterrichtszeiten_typ_w' => $this->permissionlib->isBerechtigt('lehre/unterrichtszeiten_typ', 'suid'),
],
]);
}
// -----------------------------------------------------------------------------------------------------------------
// Private methods
/**
* Retrieve the UID of the logged user and checks if it is valid
*/
private function _setAuthUID()
{
$this->_uid = getAuthUID();
if (!$this->_uid) show_error('User authentification failed');
}
}
@@ -0,0 +1,15 @@
<?php
class ClassTimeSlotType_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'lehre.tbl_unterrichtszeiten_typ';
$this->pk = 'unterrichtszeitentyp_kurzbz';
}
}
@@ -0,0 +1,15 @@
<?php
class ClassTimeSlotValidityPeriod_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'lehre.tbl_unterrichtszeiten_gueltigkeit';
$this->pk = 'unterrichtszeitengueltigkeit_id';
}
}
@@ -0,0 +1,15 @@
<?php
class ClassTimeSlot_model extends DB_Model
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->dbTable = 'lehre.tbl_unterrichtszeiten';
$this->pk = 'unterrichtszeit_id';
}
}
@@ -168,5 +168,35 @@ class Studienplan_model extends DB_Model
return $this->loadWhere([
'person_id' => $person_id
]);
}
public function getStudyPlansForOrganizationalUnitQueryResponse($organizationalUnitShortCode)
{
$query = "SELECT sp.* FROM lehre.tbl_studienplan sp
JOIN lehre.tbl_studienordnung so ON sp.studienordnung_id = so.studienordnung_id
JOIN public.tbl_studiengang sg ON so.studiengang_kz = sg.studiengang_kz
WHERE sg.oe_kurzbz = ?";
return $this->execReadOnlyQuery($query, array($organizationalUnitShortCode));
}
public function getStudyPlansForOrganizationalUnitAndDatesQueryResponse($organizationalUnitShortCode, $startDate, $endDate)
{
$query = "SELECT DISTINCT sp.* FROM lehre.tbl_studienplan sp
JOIN lehre.tbl_studienordnung so ON sp.studienordnung_id = so.studienordnung_id
JOIN public.tbl_studiengang sg ON so.studiengang_kz = sg.studiengang_kz
AND sg.oe_kurzbz = ? AND sp.studienplan_id IN (
SELECT sps.studienplan_id
FROM lehre.tbl_studienplan_semester sps
JOIN public.tbl_studiensemester ss ON ss.studiensemester_kurzbz = sps.studiensemester_kurzbz
WHERE (
(ss.start >= ? AND ss.ende <= ?) OR
(ss.start <= ? AND ss.ende >= ?) OR
(ss.start <= ? AND ss.ende >= ? AND ss.ende <= ?) OR
(ss.start >= ? AND ss.start <= ? AND ss.ende >= ?)
)
)";
return $this->execReadOnlyQuery($query, array($organizationalUnitShortCode, $startDate, $endDate, $startDate, $endDate, $startDate, $startDate, $endDate, $startDate, $endDate, $endDate));
}
}
@@ -347,4 +347,26 @@ class Studiensemester_model extends DB_Model
$result = $this->load($studiensemester_kurzbz);
return hasData($result);
}
public function getStudySemestersByStudyPlansAndDatesQueryResponse($studyPlanIds, $fromDate, $toDate)
{
$subQuery = "(
SELECT ARRAY_AGG(DISTINCT sp.semester)
FROM lehre.tbl_studienplan_semester sp
WHERE sp.studienplan_id IN ?
AND sp.studiensemester_kurzbz = s.studiensemester_kurzbz
) AS semester_numbers";
$query = "SELECT DISTINCT s.*," . $subQuery . "
FROM public.tbl_studiensemester s
JOIN lehre.tbl_studienplan_semester sp ON sp.studiensemester_kurzbz = s.studiensemester_kurzbz
WHERE sp.studienplan_id IN ? AND (
(s.start >= ? AND s.ende <= ?) OR
(s.start <= ? AND s.ende >= ?) OR
(s.start <= ? AND s.ende >= ? AND s.ende <= ?) OR
(s.start >= ? AND s.start <= ? AND s.ende >= ?)
)";
return $this->execQuery($query, array($studyPlanIds, $studyPlanIds, $fromDate, $toDate, $fromDate, $toDate, $fromDate, $fromDate, $toDate, $fromDate, $toDate, $toDate));
}
}
@@ -0,0 +1,44 @@
<?php
$includesArray = array(
'title' => ucfirst($this->p->t('ui', 'classSchedulePageTitle')),
'vue3' => true,
'axios027' => true,
'bootstrap5' => true,
'tabulator5' => true,
'fontawesome6' => true,
'primevue3' => true,
'navigationcomponent' => true,
'filtercomponent' => true,
'vuedatepicker11' => true,
'momentjs2' => true,
'customJSs' => array(
'vendor/moment/luxonjs/luxon.min.js'
),
'customJSModules' => array(
'public/js/apps/lehre/ClassScheduleApp.js'
),
'customCSSs' => array(
'public/css/components/primevue.css',
'public/css/components/verticalsplit.css',
'public/extensions/FHC-Core-Developer/css/FhcMain.css',
'public/css/components/calendar.css',
'public/css/components/classSchedule.css',
'public/css/components/vue-datepicker.css',
'public/css/classScheduleOverview.css',
)
);
$this->load->view('templates/FHC-Header', $includesArray);
?>
<div id="main">
<core-navigation-cmpt></core-navigation-cmpt>
<router-view
cis-root="<?= CIS_ROOT; ?>"
:permissions="<?= htmlspecialchars(json_encode($permissions)); ?>"
>
</router-view>
</div>
<?php $this->load->view('templates/FHC-Footer', $includesArray); ?>
+41
View File
@@ -0,0 +1,41 @@
html {
font-size: .75em;
}
nav.navbar.navbar-header,
nav.navbar.navbar-left-side {
font-size: 18px
}
nav.navbar.navbar-left-side {
padding-top: 8px;
padding-bottom: 8px;
}
/* Relative sizing inside navbar */
nav.navbar .nav-item {
font-size: 18px
}
nav.navbar .navbar-brand-icon {
font-size: 16px
}
nav.navbar .left-side-menu-link-entry {
font-size: 14px !important;
}
nav.navbar .nav-link {
font-size: 18px;
padding-top: 15px;
padding-bottom: 8px;
}
nav.navbar .dropdown-menu {
padding: 8px 0px;
}
nav.navbar .dropdown-item {
font-size: 16px;
padding: 4px 16px;
}
+80
View File
@@ -0,0 +1,80 @@
.fhc-pointer-events-none {
pointer-events: none;
}
.fhc-pointer-events-all {
pointer-events: all;
}
.fhc-cursor-pointer {
cursor: pointer;
}
.fhc-resize-vertical {
cursor: ns-resize;
}
.fhc-drag-handle:hover {
cursor: grab;
color: #000;
}
.fhc-w-fit {
width: fit-content;
}
.tabulator-group-level-0 {
background-color: #c6c6c6 !important;
display: flex;
align-items: center;
}
.tabulator-group-level-1 {
background-color: #f5f5f5 !important;
display: flex;
align-items: center;
}
.tabulator-cell {
background-color: transparent !important;
}
div[role="row"] {
background-color: white;
}
.v-enter-active {
transition: opacity 0.5s ease;
}
.v-leave-active {
transition: none;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.scrollable {
overflow: auto;
/* Firefox */
scrollbar-width: thin;
scrollbar-color: #888 transparent;
}
/* Chrome / Edge / Safari */
.scrollable::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.scrollable::-webkit-scrollbar-thumb {
background: #888;
border-radius: 999px;
}
.scrollable::-webkit-scrollbar-track {
background: transparent;
}
+127
View File
@@ -0,0 +1,127 @@
/**
* Copyright (C) 2025 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
getAllClassTimeValidityPeriods(params) {
return {
method: "get",
url: "/api/frontend/v1/ClassScheduleApi/getAllClassTimeValidityPeriods",
params,
};
},
getAllClassTimeValidityPeriodsPerOrganizationalUnit(organizationUnitId) {
return {
method: "get",
url: `/api/frontend/v1/ClassScheduleApi/getAllClassTimeValidityPeriodsPerOrganizationalUnit/${organizationUnitId}`,
};
},
getClassTimeValidityPeriod(classTimeSlotValidityPeriodId) {
return {
method: "get",
url: `/api/frontend/v1/ClassScheduleApi/getClassTimeValidityPeriod/${classTimeSlotValidityPeriodId}`,
};
},
createClassTimeSlotValidityPeriod(userId, params) {
return {
method: "post",
url: "api/frontend/v1/ClassScheduleApi/createClassTimeSlotValidityPeriod/",
params,
};
},
deleteClassTimeSlotValidityPeriod(userId, classTimeSlotValidityPeriodId) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotValidityPeriod/${classTimeSlotValidityPeriodId}`,
};
},
getClassTimeSlotsForValidityPeriod(classTimeSlotValidityPeriodId) {
return {
method: "get",
url: `api/frontend/v1/ClassScheduleApi/getClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
};
},
createClassTimeSlotsForValidityPeriod(
userId,
classTimeSlotValidityPeriodId,
params,
) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/createClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
params,
};
},
updateClassTimeSlotsForValidityPeriod(
userId,
classTimeSlotValidityPeriodId,
params,
) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/updateClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
params,
};
},
updateClassTimeSlotValidityPeriod(
userId,
classTimeSlotValidityPeriodId,
params,
) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/updateClassTimeSlotValidityPeriod/${classTimeSlotValidityPeriodId}`,
params,
};
},
deleteClassTimeSlotsForValidityPeriod(
userId,
classTimeSlotValidityPeriodId,
) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotsForValidityPeriod/${classTimeSlotValidityPeriodId}`,
};
},
getAllClassScheduleTypes(queryParams) {
return {
method: "get",
url:
"/api/frontend/v1/ClassScheduleApi/getAllClassScheduleTypes?" +
new URLSearchParams(queryParams).toString(),
};
},
createClassTimeSlotType(params) {
return {
method: "post",
url: "api/frontend/v1/ClassScheduleApi/createClassTimeSlotType/",
params,
};
},
updateClassTimeSlotType(classTimeSlotTypeId, params) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/updateClassTimeSlotType/${encodeURIComponent(classTimeSlotTypeId)}`,
params,
};
},
deleteClassTimeSlotType(userId, classTimeSlotTypeId) {
return {
method: "post",
url: `api/frontend/v1/ClassScheduleApi/deleteClassTimeSlotType/${encodeURIComponent(classTimeSlotTypeId)}`,
};
},
};
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (C) 2025 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
getAllClassroomHours() {
return {
method: "get",
url: "/api/frontend/v1/ClassroomHourApi/getAllClassroomHours",
};
}
};
@@ -0,0 +1,8 @@
export default {
getAllOrganizationalUnits() {
return {
method: "get",
url: "api/frontend/v1/organisation/organizationalUnitApi/getAllOrganizationalUnits",
};
},
}
+6
View File
@@ -16,6 +16,12 @@
*/
export default {
getAllDegreePrograms() {
return {
method: "get",
url: "/api/frontend/v1/Studgang/getAllDegreePrograms",
};
},
studiengangInformation() {
return {
method: 'get',
+36 -10
View File
@@ -1,11 +1,37 @@
export default {
getStudienplaeneBySemester(studiengang_kz, studiensemester_kurzbz, ausbildungssemester, orgform_kurzbz)
{
return {
method: 'get',
url: 'api/frontend/v1/organisation/studienplan/getBySemester',
params: { studiengang_kz, studiensemester_kurzbz, ausbildungssemester, orgform_kurzbz },
};
}
}
getAllStudyPlans() {
return {
method: "get",
url: "api/frontend/v1/organisation/studienplan/getAllStudyPlans",
};
},
getStudyPlansByOrganizationalUnitAndSemesterDates(organizationalUnitShortCode, startDate, endDate) {
return {
method: "get",
url: `api/frontend/v1/organisation/studienplan/getStudyPlansByOrganizationalUnitAndSemesterDates/${organizationalUnitShortCode}?filter[startDate]=${startDate}&filter[endDate]=${endDate}`,
};
},
getStudienplaeneBySemester(
studiengang_kz,
studiensemester_kurzbz,
ausbildungssemester,
orgform_kurzbz,
) {
return {
method: "get",
url: "api/frontend/v1/organisation/studienplan/getBySemester",
params: {
studiengang_kz,
studiensemester_kurzbz,
ausbildungssemester,
orgform_kurzbz,
},
};
},
getStudyPlan(studienplan_id) {
return {
method: "get",
url: `api/frontend/v1/organisation/studienplan/getStudyPlan/${studienplan_id}`,
};
},
};
+13 -1
View File
@@ -29,5 +29,17 @@ export default {
url: 'api/frontend/v1/organisation/studiensemester/getAll',
params: { order, start }
};
}
},
getStudySemestersByOrganizationalUnitAndDates(organizationalUnitShortCode, startDate, endDate) {
return {
method: "get",
url: `api/frontend/v1/organisation/studiensemester/getStudySemestersByOrganizationalUnitAndDates/${organizationalUnitShortCode}?filter[startDate]=${startDate}&filter[endDate]=${endDate}`,
};
},
getStudySemestersByStudyPlanAndDates(studyPlanId, startDate, endDate) {
return {
method: "get",
url: `api/frontend/v1/organisation/studiensemester/getStudySemestersByStudyPlanAndDates?filter[studyPlanId]=${studyPlanId}&filter[startDate]=${startDate}&filter[endDate]=${endDate}`,
};
},
};
+79
View File
@@ -0,0 +1,79 @@
/**
* Copyright (C) 2023 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 ClassScheduleOverview from "../../components/ClassSchedule/ClassScheduleOverview.js";
import ClassScheduleValidityPeriodOverview from "../../components/ClassSchedule/ClassScheduleValidityPeriodOverview.js";
import ClassScheduleOrgUnitGroupedValidityPeriodsOverview from "../../components/ClassSchedule/ClassScheduleOrgUnitGroupedValidityPeriodsOverview.js";
import FhcAlert from "../../plugins/FhcAlert.js";
import Phrasen from "../../plugins/Phrasen.js";
import FhcApi from "../../plugins/Api.js";
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
import {capitalize} from "../../helpers/StringHelpers.js";
const ciPath =
FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, "") +
FHC_JS_DATA_STORAGE_OBJECT.ci_router;
const router = VueRouter.createRouter({
history: VueRouter.createWebHistory(),
routes: [
{
name: "overview",
path: `/${ciPath}/lehre/ClassSchedule`,
component: ClassScheduleOverview,
},
{
name: "validityPeriodsOverviewByOrganizationUnit",
path: `/${ciPath}/lehre/ClassSchedule/validityPeriods/groupedOverviewByOrganizationUnit/:organizationalUnitShortCode`,
component: ClassScheduleOrgUnitGroupedValidityPeriodsOverview,
params: true,
},
{
name: "validityPeriodsOverviewByOrganizationUnitAndStudyPlan",
path: `/${ciPath}/lehre/ClassSchedule/validityPeriods/groupedOverviewByOrganizationUnit/:organizationalUnitShortCode/studyPlan/:studyPlanId`,
component: ClassScheduleOrgUnitGroupedValidityPeriodsOverview,
params: true,
},
{
name: "validityPeriodOverview",
path: `/${ciPath}/lehre/ClassSchedule/validityPeriods/:classTimeSlotValidityPeriodId`,
component: ClassScheduleValidityPeriodOverview,
params: true,
},
],
});
const app = Vue.createApp({
components: {
CoreNavigationCmpt,
ClassScheduleOverview,
ClassScheduleValidityPeriodOverview,
ClassScheduleOrgUnitGroupedValidityPeriodsOverview,
},
});
app.config.globalProperties.$capitalize = capitalize;
app
.use(router)
.use(primevue.config.default, { zIndex: { overlay: 9999 } })
.use(FhcAlert)
.use(Phrasen)
.use(FhcApi)
.mount("#main");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,121 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ClassScheduleValidityPeriodPreview from "./ClassScheduleValidityPeriodPreview.js";
export default {
name: "ClassScheduleOrgUnitGroupedValidityPeriodsOverview",
components: {
ClassScheduleValidityPeriodPreview,
},
data: () => {
return {
organizationalUnitShortCode: null,
studyPlanId: null,
classScheduleValidityPeriods: [],
classScheduleTimeSlots: [],
classTimeSlotTypes: [],
};
},
computed: {},
methods: {},
async created() {
this.organizationalUnitShortCode =
this.$route.params.organizationalUnitShortCode;
this.studyPlanId = parseInt(this.$route.params.studyPlanId);
let getAllClassTimeValidityPeriodsPerOrganizationalUnitResponse =
await this.$api.call(
ApiClassSchedule.getAllClassTimeValidityPeriodsPerOrganizationalUnit(
this.organizationalUnitShortCode,
),
);
if (
getAllClassTimeValidityPeriodsPerOrganizationalUnitResponse.meta
.status === "success"
) {
this.classScheduleValidityPeriods =
getAllClassTimeValidityPeriodsPerOrganizationalUnitResponse.data.filter(
(period) => {
if (!this.studyPlanId) return true;
return period.studienplan_id === this.studyPlanId;
},
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleValidityPeriods"),
);
}
for (let validityPeriod of this.classScheduleValidityPeriods) {
if (
this.studyPlanId &&
validityPeriod.studienplan_id !== this.studyPlanId
) {
continue;
}
let getClassTimeSlotsForValidityPeriodResponse = await this.$api.call(
ApiClassSchedule.getClassTimeSlotsForValidityPeriod(
validityPeriod.unterrichtszeitengueltigkeit_id,
),
);
if (
getClassTimeSlotsForValidityPeriodResponse.meta.status === "success"
) {
this.classScheduleTimeSlots = [
...this.classScheduleTimeSlots,
...getClassTimeSlotsForValidityPeriodResponse.data,
];
} else {
this.$fhcAlert.alertError(
this.$p.t(
"ui",
"errorFetchingClassScheduleTimeSlotForValidityPeriod",
),
);
}
}
let getAllClassTimeSlotTypesResponse = await this.$api.call(
ApiClassSchedule.getAllClassScheduleTypes("filter[aktiv]=true"),
);
if (getAllClassTimeSlotTypesResponse.meta.status === "success") {
this.classTimeSlotTypes = getAllClassTimeSlotTypesResponse.data.map(
(type) => {
let descriptions = [{
lang: "de",
value: type.bezeichnung_mehrsprachig[0] || "",
}, {
lang: "en",
value: type.bezeichnung_mehrsprachig[1] || "",
}];
return {
...type,
bezeichnung_mehrsprachig: descriptions,
};
},
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleTimeSlotTypes"),
);
}
},
template: /* html */ `
<div class="container mt-4">
<div class="my-2">
<h2 v-if="!this.studyPlanId">{{ $p.t("ui", "classScheduleOrgUnitGroupedValidityPeriodsOverviewTitle") }}</h2>
<h2 v-else>{{ $p.t("ui", "classScheduleStudyPlanGroupedValidityPeriodsOverviewTitle") }}</h2>
</div>
<class-schedule-validity-period-preview
v-for="validityPeriod in classScheduleValidityPeriods"
:key="validityPeriod.unterrichtszeitengueltigkeit"
:class-schedule-validity-period="validityPeriod"
:class-time-slots="classScheduleTimeSlots.filter(slot => slot.unterrichtszeitengueltigkeit_id === validityPeriod.unterrichtszeitengueltigkeit_id)"
:class-time-slot-types="classTimeSlotTypes"
class="shadow"
/>
</div>
`,
};
@@ -0,0 +1,556 @@
import { CoreFilterCmpt } from "../filter/Filter.js";
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ApiStudienSemester from "../../../js/api/factory/studiensemester.js";
import ClassScheduleTypeModal from "./ClassScheduleTypeModal.js";
import ClassScheduleValidityPeriodModal from "./ClassScheduleValidityPeriodModal.js";
import CoreForm from "../Form/Form.js";
import FormInput from "../Form/Input.js";
import ApiOrganizationalUnit from "../../../js/api/factory/organizationalUnit.js";
export default {
name: "ClassScheduleOverview",
components: {
CoreFilterCmpt,
ClassScheduleTypeModal,
ClassScheduleValidityPeriodModal,
CoreForm,
FormInput,
},
props: {
permissions: Object,
},
provide() {
return {
cisRoot: this.cisRoot,
hasLehreUnterrichtszeitenTypWPermission:
this.permissions["lehre/unterrichtszeiten_typ_w"] || false,
};
},
watch: {
filterData: {
handler(newValue) {
this.$refs.classTimeSlotValidityPeriodsTable.tabulator.replaceData();
},
deep: true,
},
selectedSemester: {
handler(newValue) {
this.filterData.validityPeriodFrom = this.selectedSemester.start;
this.filterData.validityPeriodTo = this.selectedSemester.ende;
},
},
},
data: () => {
return {
phrasesLoaded: false,
editorParams: null,
classTimeSlotValidityPeriods: [],
classTimeSlotValidityPeriodId: null,
editedClassTimeSlotValidityPeriodId: null,
mondayClassTimeSlots: [],
isClassTimeSlotTypeModalVisible: false,
isClassTimeSlotValidityPeriodModalVisible: false,
organizationalUnits: [],
filteredOrganizationalUnits: [],
allSemesters: [],
filteredSemesters: [],
filterData: {
selectedOrganizationalUnit: null,
validityPeriodFrom: null,
validityPeriodTo: null,
},
selectedSemester: null,
};
},
computed: {
tabulatorOptions() {
const options = {
ajaxURL: "dummy",
ajaxRequestFunc: async () => {
return await this.getParsedClassTimeSlotValidityPeriodData();
},
ajaxResponse: (url, params, response) => response,
persistenceID: "class_schedule_validity_periods_table",
selectableRows: true,
columns: [
{
title: this.$p.t("ui", "zeitraum"),
formatter: (cell, formatterParams, onRendered) => {
const data = cell.getData();
const validFrom = new Date(data.gueltig_von).toLocaleDateString(
"de-AT",
{
day: "2-digit",
month: "2-digit",
year: "numeric",
},
);
data.gueltig_von;
const validTo = new Date(data.gueltig_bis).toLocaleDateString(
"de-AT",
{
day: "2-digit",
month: "2-digit",
year: "numeric",
},
);
return `${validFrom ? validFrom : "?"} - ${validTo ? validTo : "?"}`;
},
},
{
title: this.$p.t("global", "typ"),
field: "unterrichtszeiten_typ_bezeichnung_mehrsprachig",
width: 150,
},
{
title: this.$p.t("lehre", "sem"),
field: "ausbildungssemester",
width: 150,
},
{
title: this.$p.t("global", "actions"),
field: "actions",
minWidth: 150,
maxWidth: 150,
formatter: (cell, formatterParams, onRendered) => {
let container = document.createElement("div");
container.className = "d-flex gap-2";
let button = document.createElement("button");
button = document.createElement("button");
button.className = "btn btn-outline-secondary btn-action";
button.innerHTML = '<i class="fa fa-eye"></i>';
button.title = this.$p.t(
"classSchedule",
"btn_showClassTimeSlotValidityPeriod",
);
button.addEventListener("click", (event) =>
this.$router.push({
name: "validityPeriodOverview",
params: {
classTimeSlotValidityPeriodId:
cell.getData().unterrichtszeitengueltigkeit_id,
},
}),
);
container.append(button);
button = document.createElement("button");
button.className = "btn btn-outline-secondary btn-action";
button.innerHTML = '<i class="fa fa-edit"></i>';
button.title = this.$p.t(
"classSchedule",
"btn_editClassTimeSlotValidityPeriod",
);
button.addEventListener("click", (event) =>
this.editClassTimeSlotValidityPeriod(
cell.getData().unterrichtszeitengueltigkeit_id,
),
);
container.append(button);
button = document.createElement("button");
button.className =
"btn btn-outline-secondary btn-action bg-danger";
button.innerHTML = '<i class="fa fa-xmark text-white"></i>';
button.title = this.$p.t(
"classSchedule",
"btn_deleteClassTimeSlotValidityPeriod",
);
button.addEventListener("click", () => {
let isDeletionConfirmed = confirm(
this.$p.t(
"ui",
"deleteClassTimeSlotValidityPeriodConfirmation",
),
);
if (!isDeletionConfirmed) return;
this.deleteClassTimeSlotValidityPeriod(
cell.getData().unterrichtszeitengueltigkeit_id,
);
});
container.append(button);
return container;
},
frozen: true,
},
],
groupBy: [
"organisationseinheit_bezeichnung_extended",
"studienplan_bezeichnung",
],
groupHeader: [
function (value, count, data) {
let container = document.createElement("span");
container.className =
"d-flex align-items-center justify-content-between";
container.style.display = "inline-block";
container.style.width = "100%";
let label = document.createElement("span");
label.textContent = value;
container.append(label);
let button = document.createElement("button");
button.className =
"btn btn-sm btn-outline-secondary fhc-btn-for-org-unit-grouping";
button.style.marginLeft = "10px";
button.innerHTML = '<i class="fa fa-eye"></i>';
button.dataset.organizationalUnitShortCode = data[0].oe_kurzbz;
container.append(button);
return container;
},
function (value, count, data) {
let container = document.createElement("span");
container.className =
"d-flex align-items-center justify-content-between";
container.style.display = "inline-block";
container.style.width = "100%";
let label = document.createElement("span");
label.textContent = value;
container.append(label);
let button = document.createElement("button");
button.className =
"btn btn-sm btn-outline-secondary fhc-btn-for-org-unit-and-study-plan-grouping";
button.style.marginLeft = "10px";
button.innerHTML = '<i class="fa fa-eye"></i>';
button.title = 22;
button.dataset.organizationalUnitShortCode = data[0].oe_kurzbz;
button.dataset.studyPlanId = data[0].studienplan_id;
container.append(button);
return container;
},
],
};
return options;
},
tabulatorEvents() {
const events = [
{
event: "renderComplete",
handler: async () => {
document
.querySelectorAll(".fhc-btn-for-org-unit-grouping")
.forEach((button) => {
button.addEventListener("click", (e) => {
let organizationalUnitShortCode =
button.dataset.organizationalUnitShortCode;
this.$router.push({
name: "validityPeriodsOverviewByOrganizationUnit",
params: {
organizationalUnitShortCode,
},
});
});
});
document
.querySelectorAll(".fhc-btn-for-org-unit-and-study-plan-grouping")
.forEach((button) => {
button.addEventListener("click", (e) => {
let organizationalUnitShortCode =
button.dataset.organizationalUnitShortCode;
let studyPlanId = button.dataset.studyPlanId;
this.$router.push({
name: "validityPeriodsOverviewByOrganizationUnitAndStudyPlan",
params: {
organizationalUnitShortCode,
studyPlanId,
},
});
});
});
},
},
];
return events;
},
dropdownParsedOrganizationalUnits() {
return this.organizationalUnits
.filter((unit) => unit.aktiv)
.map((unit) => {
return {
label: `[${unit.organisationseinheittyp_kurzbz}] ${unit.bezeichnung}`,
value: unit.oe_kurzbz,
};
})
.sort((a, b) => a.label.localeCompare(b.label));
},
dropdownParsedSemesters() {
return this.allSemesters.map((semester) => {
return {
label: semester.studiensemester_kurzbz,
value: semester.studiensemester_kurzbz,
start: semester.start,
ende: semester.ende,
};
});
},
},
methods: {
async getParsedClassTimeSlotValidityPeriodData() {
let getAllClassTimeValidityPeriodsResponse = await this.$api.call(
ApiClassSchedule.getAllClassTimeValidityPeriods({
organizationalUnitShortCode:
this.filterData.selectedOrganizationalUnit?.value,
validityPeriodFrom: this.filterData.validityPeriodFrom
? moment(this.filterData.validityPeriodFrom).format("YYYY-MM-DD")
: null,
validityPeriodTo: this.filterData.validityPeriodTo
? moment(this.filterData.validityPeriodTo).format("YYYY-MM-DD")
: null,
}),
);
if (getAllClassTimeValidityPeriodsResponse.meta.status === "success") {
let generalWord = this.$p.t("ui", "general");
return getAllClassTimeValidityPeriodsResponse.data.map(
function (period) {
period.organisationseinheit_bezeichnung_extended =
"[" + period.organisationseinheit_organisationseinheittyp_kurzbz + "] " + period.organisationseinheit_bezeichnung;
if (!period.studienplan_bezeichnung) {
period.studienplan_bezeichnung = generalWord;
}
period.unterrichtszeiten_typ_bezeichnung_mehrsprachig =
period.unterrichtszeiten_typ_bezeichnung_mehrsprachig[0]?.split(
":",
)[1];
return {
...period,
};
},
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleValidityPeriods"),
);
}
},
showClassTimeSlotValidityPeriodModal() {
this.isClassTimeSlotValidityPeriodModalVisible = true;
},
editClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId) {
this.editedClassTimeSlotValidityPeriodId = classTimeSlotValidityPeriodId;
},
deleteClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId) {
return this.$api
.call(
ApiClassSchedule.deleteClassTimeSlotValidityPeriod(
this.id,
classTimeSlotValidityPeriodId,
),
)
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successDelete"));
window.scrollTo(0, 0);
this.$refs.classTimeSlotValidityPeriodsTable.tabulator.replaceData();
})
.catch((error) => {
this.$fhcAlert.handleSystemError(error);
});
},
showClassTimeSlotTypeModal() {
this.isClassTimeSlotTypeModalVisible = true;
},
resetClassTimeSlotTypeModal() {
this.isClassTimeSlotTypeModalVisible = false;
},
resetClassTimeSlotValidityPeriodModal() {
this.isClassTimeSlotValidityPeriodModalVisible = false;
},
filterOrganizationalUnits(event) {
let defaultItem = {
label: this.$p.t("ui", "dropdownEmptyOption"),
value: null,
};
const query = event.query.toLowerCase();
if (!query) {
return (this.filteredOrganizationalUnits = [
defaultItem,
...this.dropdownParsedOrganizationalUnits,
]);
}
return (this.filteredOrganizationalUnits = [defaultItem]
.concat(this.dropdownParsedOrganizationalUnits)
.filter((unit) => {
return unit.label.toLowerCase().includes(query);
}));
},
filterSemesters(event) {
let defaultItem = {
label: this.$p.t("ui", "dropdownEmptyOption"),
value: null,
};
const query = event.query.toLowerCase();
if (!query) {
return (this.filteredSemesters = [
defaultItem,
...this.dropdownParsedSemesters,
]);
}
return (this.filteredSemesters = [defaultItem]
.concat(this.dropdownParsedSemesters)
.filter((semester) => {
return semester.label.toLowerCase().includes(query);
}));
},
},
async created() {
let getAllClassTimeValidityPeriodsResponse = await this.$api.call(
ApiClassSchedule.getAllClassTimeValidityPeriods(),
);
if (getAllClassTimeValidityPeriodsResponse.meta.status === "success") {
this.classTimeSlotValidityPeriods =
getAllClassTimeValidityPeriodsResponse.data;
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleValidityPeriods"),
);
}
let getAllOrganizationalUnitsResponse = await this.$api.call(
ApiOrganizationalUnit.getAllOrganizationalUnits(),
);
if (getAllOrganizationalUnitsResponse.meta.status === "success") {
this.organizationalUnits = getAllOrganizationalUnitsResponse.data.sort(
(a, b) => a.bezeichnung.localeCompare(b.bezeichnung),
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingOrganizationalUnits"),
);
}
let getAllSemestersResponse = await this.$api.call(
ApiStudienSemester.getAll("DESC"),
);
if (getAllSemestersResponse.meta.status === "success") {
this.allSemesters = getAllSemestersResponse.data;
} else {
this.$fhcAlert.alertError(this.$p.t("ui", "errorFetchingSemesters"));
}
},
mounted() {
this.$p
.loadCategory(["global", "lehre", "ui", "gruppenmanagement", "core"])
.then(() => {
this.phrasesLoaded = true;
});
},
template: /* html */ `
<div class="container mt-4">
<h1 class='mb-5'>{{ $p.t("ui", "classScheduleOverviewHeading") }}</h1>
<div class="row mb-3">
<div class="col d-flex justify-content-between">
<a class="btn btn-primary mb-3" @click="showClassTimeSlotValidityPeriodModal">{{$p.t('ui', 'addClassTimeSlotValidityPeriodButton')}}</a>
<a class="btn btn-secondary mb-3" @click="showClassTimeSlotTypeModal">{{$p.t('ui', 'addClassTimeSlotTypeButton')}}</a>
</div>
</div>
<class-schedule-type-modal
:isVisible="isClassTimeSlotTypeModalVisible"
@hideBsModal="resetClassTimeSlotTypeModal"
/>
<class-schedule-validity-period-modal
:isVisible="isClassTimeSlotValidityPeriodModalVisible"
:editedClassTimeSlotValidityPeriodId="editedClassTimeSlotValidityPeriodId"
@hideBsModal="() => { resetClassTimeSlotValidityPeriodModal(); editedClassTimeSlotValidityPeriodId = null; }"
@classTimeSlotValidityPeriodCreated="() => {
$refs.classTimeSlotValidityPeriodsTable.tabulator.replaceData();
resetClassTimeSlotValidityPeriodModal();
this.editedClassTimeSlotValidityPeriodId = null;
}"
@classTimeSlotValidityPeriodUpdated="() => {
$refs.classTimeSlotValidityPeriodsTable.tabulator.replaceData();
resetClassTimeSlotValidityPeriodModal();
this.editedClassTimeSlotValidityPeriodId = null;
}"
/>
<core-filter-cmpt
v-if="phrasesLoaded"
ref="classTimeSlotValidityPeriodsTable"
table-only
:side-menu="false"
:tabulator-options="tabulatorOptions"
:tabulator-events="tabulatorEvents"
>
<template #search>
<slot name="filterzuruecksetzen">
<core-form class="d-flex flex-column flex-md-row align-items-md-end gap-3">
<div>
<form-input
:label="$capitalize($p.t('lehre/organisationseinheit'))"
:suggestions="filteredOrganizationalUnits"
:optionValue="(option) => option.value"
:optionLabel="(option) => option.label"
@complete="filterOrganizationalUnits($event)"
@itemSelect="(option) => { filterData.selectedOrganizationalUnit = option.value; }"
type="autocomplete"
name="organizationalUnitShortCode"
dropdown
forceSelection
>
</form-input>
</div>
<div>
<form-input
v-model="selectedSemester"
:label="$capitalize($p.t('lehre/studiensemester'))"
:suggestions="filteredSemesters"
:optionValue="(option) => option.value"
:optionLabel="(option) => option.label"
@complete="filterSemesters($event)"
type="autocomplete"
name="selectedSemester"
dropdown
forceSelection
>
</form-input>
</div>
<div>
<div class="d-flex align-items-center gap-2">
<form-input
v-model="filterData.validityPeriodFrom"
:label="$p.t('ui', 'validityPeriod') + ' ' + $p.t('ui', 'von')"
:teleport="true"
:enable-time-picker="false"
type="datePicker"
name="validityPeriodFrom"
format="dd.MM.yyyy"
auto-apply
/>
<form-input
v-model="filterData.validityPeriodTo"
:label="$p.t('ui', 'validityPeriod') + ' ' + $p.t('global', 'bis')"
:teleport="true"
:enable-time-picker="false"
type="datePicker"
name="validityPeriodTo"
format="dd.MM.yyyy"
auto-apply
/>
</div>
</div>
</core-form>
</slot>
</template>
</core-filter-cmpt>
</div>
`,
};
@@ -0,0 +1,273 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import BsModal from "../Bootstrap/Modal.js";
import CoreForm from "../Form/Form.js";
import FormInput from "../Form/Input.js";
export default {
name: "ClassScheduleTypeModal",
components: {
BsModal,
CoreForm,
FormInput,
},
inject: {
hasLehreUnterrichtszeitenTypWPermission:
"hasLehreUnterrichtszeitenTypWPermission",
},
props: {
isVisible: {
type: Boolean,
required: true,
},
},
emits: [
"hideBsModal",
"classTimeSlotTypeCreated",
"classTimeSlotTypeUpdated",
],
watch: {
isVisible(newValue) {
if (newValue) {
this.$refs.classScheduleTypeModal.show();
} else {
this.$refs.classScheduleTypeModal.hide();
}
},
},
data: () => {
return {
isFormVisible: false,
isEditInProgress: false,
editedClassScheduleType: null,
classTimeSlotTypeFormData: {
isActive: true,
shortCode: "",
descriptions: [
{ lang: "de", value: "" },
{ lang: "en", value: "" },
],
backgroundColor: "#ffffff",
},
classScheduleTypes: [],
};
},
methods: {
async getAllClassScheduleTypes() {
let getAllClassScheduleTypeResponse = await this.$api.call(
ApiClassSchedule.getAllClassScheduleTypes(),
);
if (getAllClassScheduleTypeResponse.meta.status === "success") {
this.classScheduleTypes = getAllClassScheduleTypeResponse.data.map(
(type) => {
let descriptions = [{
lang: "de",
value: type.bezeichnung_mehrsprachig[0] || "",
}, {
lang: "en",
value: type.bezeichnung_mehrsprachig[1] || "",
}];
return {
...type,
bezeichnung_mehrsprachig: descriptions,
};
},
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleTimeSlotTypes"),
);
}
},
createClassTimeSlotType() {
return this.$refs.classTimeSlotTypeForm
.call(
ApiClassSchedule.createClassTimeSlotType({
isActive: this.classTimeSlotTypeFormData.isActive,
shortCode: this.classTimeSlotTypeFormData.shortCode,
descriptions: this.classTimeSlotTypeFormData.descriptions,
backgroundColor: this.classTimeSlotTypeFormData.backgroundColor,
}),
)
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
this.$emit("classTimeSlotTypeCreated");
this.getAllClassScheduleTypes();
this.resetClassTimeSlotTypeForm();
});
},
editClassTimeSlotType(classScheduleType) {
this.isEditInProgress = true;
this.isFormVisible = true;
this.editedClassScheduleType = classScheduleType;
this.classTimeSlotTypeFormData = {
isActive: classScheduleType.aktiv,
shortCode: classScheduleType.unterrichtszeitentyp_kurzbz,
descriptions: classScheduleType.bezeichnung_mehrsprachig.map(
(desc) => ({
lang: desc.lang,
value: desc.value,
}),
),
backgroundColor: classScheduleType.hintergrundfarbe || "#ffffff",
};
},
updateClassTimeSlotType() {
return this.$refs.classTimeSlotTypeForm
.call(
ApiClassSchedule.updateClassTimeSlotType(
this.classTimeSlotTypeFormData.shortCode,
{
isActive: this.classTimeSlotTypeFormData.isActive,
descriptions: this.classTimeSlotTypeFormData.descriptions,
backgroundColor: this.classTimeSlotTypeFormData.backgroundColor,
},
),
)
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
this.$emit("classTimeSlotTypeUpdated");
this.getAllClassScheduleTypes();
this.resetClassTimeSlotTypeForm();
});
},
deleteClassTimeSlotType(classScheduleTypeShortCode) {
let isConfirmed = confirm(
this.$p.t("ui", "confirmDeleteClassTimeSlotType"),
);
if (!isConfirmed) {
return Promise.resolve();
}
return this.$api
.call(
ApiClassSchedule.deleteClassTimeSlotType(
this.id,
classScheduleTypeShortCode,
),
)
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successDelete"));
window.scrollTo(0, 0);
this.getAllClassScheduleTypes();
})
.catch((error) => {
this.$fhcAlert.handleSystemError(error);
});
},
showClassTimeSlotTypeForm() {
this.isFormVisible = true;
},
hideClassTimeSlotTypeForm() {
this.resetClassTimeSlotTypeForm();
},
resetClassTimeSlotTypeForm() {
this.isEditInProgress = false;
this.isFormVisible = false;
this.classTimeSlotTypeFormData = {
isActive: true,
shortCode: "",
descriptions: [
{ lang: "de", value: "" },
{ lang: "en", value: "" },
],
backgroundColor: "#ffffff",
};
},
},
async created() {
await this.getAllClassScheduleTypes();
},
template: /* html */ `
<bs-modal ref="classScheduleTypeModal" size="md" @hideBsModal="() => { $emit('hideBsModal'); resetClassTimeSlotTypeForm(); }">
<template #title>
<p v-if="hasLehreUnterrichtszeitenTypWPermission" class="fw-bold mt-3">{{$p.t('ui', 'editClassTimeSlotTypeModalTitle')}}</p>
<p v-else class="fw-bold mt-3">{{$p.t('ui', 'existingClassScheduleTypesLabel')}}</p>
</template>
<div v-if="!isFormVisible && hasLehreUnterrichtszeitenTypWPermission" class="row mb-3">
<div class="col d-flex justify-content-end">
<button @click="showClassTimeSlotTypeForm" type="button" class="btn btn-primary">{{$p.t('global', 'create')}}</button>
</div>
</div>
<core-form v-else-if="isFormVisible && hasLehreUnterrichtszeitenTypWPermission" ref="classTimeSlotTypeForm" class="row g-3 pb-3">
<div v-if="!isEditInProgress" class="row mb-3">
<form-input
v-model="classTimeSlotTypeFormData.shortCode"
:label="$p.t('ui/shortName')"
type="text"
name="shortCode"
>
</form-input>
</div>
<div v-else class="row mb-3">
<p class="mb-0"><span class="fw-bold">{{$p.t('ui/shortName')}}:</span> {{editedClassScheduleType.unterrichtszeitentyp_kurzbz}}</p>
</div>
<div class="row">
<form-input
v-for="description in classTimeSlotTypeFormData.descriptions"
:key="description.lang"
v-model="description.value"
:name="description.lang"
:label="$p.t('ui/description') + ' (' + description.lang + ')'"
type="textarea"
class="mb-3"
>
</form-input>
</div>
<div class="row mb-3">
<form-input
v-model="classTimeSlotTypeFormData.backgroundColor"
:label="$p.t('ui/backgroundColor')"
type="color"
name="backgroundColor"
>
</form-input>
</div>
<div class="row mb-3">
<div class="col d-flex align-items-center justify-content-end">
<form-input
v-model="classTimeSlotTypeFormData.isActive"
:label="$p.t('ui/isActive')"
type="checkbox"
name="isActive"
>
</form-input>
</div>
</div>
<div class="col d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary" @click="hideClassTimeSlotTypeForm">{{$p.t('ui', 'abbrechen')}}</button>
<button type="button" class="btn btn-primary" @click="isEditInProgress ? updateClassTimeSlotType() : createClassTimeSlotType()">{{$p.t('ui', 'speichern')}}</button>
</div>
</core-form>
<div class="row mb-3">
<p v-if="hasLehreUnterrichtszeitenTypWPermission" class="fw-bold mb-2">{{$p.t('ui', 'existingClassScheduleTypesLabel')}}</p>
<div
>
<div
v-for="classScheduleType in classScheduleTypes"
:key="classScheduleType.unterrichtszeitentyp_kurzbz"
:value="classScheduleType.unterrichtszeitentyp_kurzbz"
:class='{"opacity-50": !classScheduleType.aktiv}'
class=" shadow-sm p-2 mb-2 bg-body rounded"
>
<div class="d-flex justify-content-between align-items-center mb-2">
<span
:style="{ backgroundColor: classScheduleType.hintergrundfarbe }"
class="badge me-2 text-black">
{{classScheduleType.unterrichtszeitentyp_kurzbz}}
</span>
<div v-if="hasLehreUnterrichtszeitenTypWPermission" class="d-flex justify-content-between align-items-center gap-2">
<a href="#" @click.prevent="editClassTimeSlotType(classScheduleType)"><i class="fa fa-edit"></i></a>
<a href="#" @click.prevent="deleteClassTimeSlotType(classScheduleType.unterrichtszeitentyp_kurzbz)"><i class="fa fa-trash text-danger"></i></a>
</div>
</div>
<p>
{{classScheduleType.bezeichnung_mehrsprachig[0].value}} / {{classScheduleType.bezeichnung_mehrsprachig[1].value}}
</p>
</div>
</div>
</div>
</bs-modal>
`,
};
@@ -0,0 +1,170 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ApiClassroomHour from "../../../js/api/factory/classroomHour.js";
import ClassScheduleCalendarSelector from "./ClassScheduleCalendarSelector.js";
export default {
name: "ClassScheduleValidityPeriodForm",
components: {
ClassScheduleCalendarSelector,
},
props: {
classTimeSlotTypes: {
type: [Array, null],
required: true,
},
classTimeSlotValidityPeriod: {
type: Object,
required: true,
},
editedClassTimeSlots: {
type: Array,
required: false,
default: () => [],
},
classroomHours: {
type: Array,
required: false,
default: () => [],
},
},
emits: ["hideForm", "classTimeSlotsCreated", "classTimeSlotsEdited"],
watch: {
editedClassTimeSlots: {
handler(newVal) {
this.editedOverlays = newVal.map((slot) => {
return {
databaseId: slot.id,
id: slot.identifier,
weekday: slot.wochentag + 1,
type: slot.unterrichtszeitentyp_kurzbz,
startTime: slot.startTime,
endTime: slot.endTime,
};
});
},
immediate: true,
},
},
data: () => {
return {
editedOverlays: [],
classTimeSlots: [],
};
},
computed: {
areClassTimeSlotsEdited() {
return this.editedClassTimeSlots && this.editedClassTimeSlots.length > 0;
},
},
methods: {
hideForm() {
this.$emit("hideForm");
this.classTimeSlots = [];
},
addClassTimeSlotPerDay(day) {
this.classTimeSlots.push({
identifier: Math.random().toString(36).substr(2, 9),
wochentag: this.getWeekdayNumberFromDay(day),
startTime: "08:00",
endTime: "10:00",
classTimeSlotTypeShortcode: null,
});
},
removeClassTimeSlotPerDay(day, identifier) {
this.classTimeSlots = this.classTimeSlots.filter(
(slot) =>
!(
slot.wochentag === this.getWeekdayNumberFromDay(day) &&
slot.identifier === identifier
),
);
},
async createClassTimeSlots() {
let response = await this.$api.call(
ApiClassSchedule.createClassTimeSlotsForValidityPeriod(
this.id,
this.$props.classTimeSlotValidityPeriod
.unterrichtszeitengueltigkeit_id,
{
unterrichtszeiten: this.classTimeSlots.map((slot) => {
slot.wochentag = parseInt(slot.wochentag)- 1;
return slot;
}),
},
),
);
if (response.meta.status === "success") {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
this.classTimeSlots = [];
this.$emit("classTimeSlotsCreated");
} else {
this.$fhcAlert.handleSystemError(response.meta.message);
}
},
async updateClassTimeSlots() {
let response = await this.$api.call(
ApiClassSchedule.updateClassTimeSlotsForValidityPeriod(
this.id,
this.$props.classTimeSlotValidityPeriod
.unterrichtszeitengueltigkeit_id,
{
unterrichtszeiten: this.classTimeSlots.map((slot) => {
slot.wochentag = parseInt(slot.wochentag)- 1;
return slot;
}),
},
),
);
if (response.meta.status === "success") {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
this.classTimeSlots = [];
this.$emit("classTimeSlotsEdited");
} else {
this.$fhcAlert.handleSystemError(response.meta.message);
}
},
getWeekdayNumberFromDay(day) {
const days = {
monday: 1,
tuesday: 2,
wednesday: 3,
thursday: 4,
friday: 5,
saturday: 6,
sunday: 7,
};
return days[day.toLowerCase()] || null;
},
handleOverlaysChanged(newOverlays) {
this.classTimeSlots = newOverlays.map((overlay) => {
return {
id: overlay.databaseId || null,
identifier: overlay.id,
wochentag: overlay.weekday,
startTime: overlay.startingTimeSlot.split("-")[0],
endTime: overlay.endingTimeSlot.split("-")[1],
classTimeSlotTypeShortcode: overlay.type,
};
});
},
},
template: `
<div class='row'>
<div class='col-12'>
<class-schedule-calendar-selector
:classroom-hours="this.$props.classroomHours"
:default-class-time-slot-type="this.$props.classTimeSlotTypes.find(type => type.is_default)"
:class-time-slot-types="this.$props.classTimeSlotTypes"
:edited-overlays="this.editedOverlays"
@overlaysChanged="handleOverlaysChanged"
/>
</div>
<div class="col-12 d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary" @click="hideForm">{{$p.t('ui', 'abbrechen')}}</button>
<button v-if="!areClassTimeSlotsEdited" type="button" class="btn btn-primary" @click="createClassTimeSlots">{{$p.t('ui', 'speichern')}}</button>
<button v-else type="button" class="btn btn-primary" @click="updateClassTimeSlots">{{$p.t('ui', 'btnAktualisieren')}}</button>
</div>
</div>
`,
};
@@ -0,0 +1,577 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ApiStudienPlan from "../../../js/api/factory/studienplan.js";
import ApiStudienSemester from "../../../js/api/factory/studiensemester.js";
import ApiOrganizationalUnit from "../../../js/api/factory/organizationalUnit.js";
import BsModal from "../Bootstrap/Modal.js";
import CoreForm from "../Form/Form.js";
import FormInput from "../Form/Input.js";
import FormValidation from "../Form/Validation.js";
export default {
name: "ClassScheduleValidityPeriodModal",
components: {
BsModal,
CoreForm,
FormInput,
FormValidation,
},
props: {
isVisible: {
type: Boolean,
required: true,
},
editedClassTimeSlotValidityPeriodId: {
type: Number,
default: null,
},
},
emits: [
"hideBsModal",
"classTimeSlotValidityPeriodCreated",
"classTimeSlotValidityPeriodUpdated",
],
watch: {
isVisible(newValue) {
if (newValue) {
this.$refs.classTimeSlotValidityPeriodModal.show();
} else {
this.$refs.classTimeSlotValidityPeriodModal.hide();
}
},
editedClassTimeSlotValidityPeriodId(newValue) {
if (!newValue) return;
return this.$api
.call(ApiClassSchedule.getClassTimeValidityPeriod(newValue))
.then((response) => {
let validityPeriodData = response.data[0];
let parsedOrganizationalUnit = this.dropdownParsedOrganizationalUnits.find(
(unit) => unit.value === validityPeriodData.oe_kurzbz,
);
if (!parsedOrganizationalUnit) {
parsedOrganizationalUnit = {
value: validityPeriodData.oe_kurzbz,
label: validityPeriodData.oe_bezeichnung ,
};
}
this.editedClassTimeSlotValidityPeriod = validityPeriodData;
this.classTimeSlotValidityPeriodFormData = {
id: validityPeriodData.unterrichtszeitengueltigkeit_id,
organizationalUnit: parsedOrganizationalUnit,
studyPlanId: validityPeriodData.studienplan_id,
classTimeSlotTypeShortcode:
validityPeriodData.unterrichtszeitentyp_kurzbz,
validityPeriodFrom: validityPeriodData.gueltig_von,
validityPeriodTo: validityPeriodData.gueltig_bis,
semester: validityPeriodData.ausbildungssemester,
description: validityPeriodData.anmerkung,
};
this.$refs.classTimeSlotValidityPeriodModal.show();
})
.catch((error) => {
this.$fhcAlert.handleSystemError(error);
});
},
"classTimeSlotValidityPeriodFormData.organizationalUnit"() {
this.updateClassTimeSlotValidityPeriodFormDataWatcher();
},
"classTimeSlotValidityPeriodFormData.validityPeriodFrom"() {
this.updateClassTimeSlotValidityPeriodFormDataWatcher();
},
"classTimeSlotValidityPeriodFormData.validityPeriodTo"() {
this.updateClassTimeSlotValidityPeriodFormDataWatcher();
},
},
data: () => {
return {
editedValidityPeriod: null,
isFormVisible: false,
organizationalUnits: [],
filteredOrganizationalUnits: [],
studyPlans: [],
studySemesters: [],
studySemestersByNumber: [],
classTimeSlotTypes: [],
classTimeSlotValidityPeriodFormData: {
id: null,
organizationalUnit: null,
studyPlanId: null,
classTimeSlotTypeShortcode: null,
validityPeriodFrom: null,
validityPeriodTo: null,
semester: null,
description: null,
},
};
},
computed: {
isEditMode() {
return !!this.$props.editedClassTimeSlotValidityPeriodId;
},
isStudyPlanSelectDisabled() {
return (
!this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.value ||
!this.classTimeSlotValidityPeriodFormData.validityPeriodFrom ||
!this.classTimeSlotValidityPeriodFormData.validityPeriodTo
);
},
formattedValidityPeriodFrom() {
if (!this.classTimeSlotValidityPeriodFormData.validityPeriodFrom)
return null;
return moment(this.classTimeSlotValidityPeriodFormData.validityPeriodFrom).format("YYYY-MM-DD");
},
formattedValidityPeriodTo() {
if (!this.classTimeSlotValidityPeriodFormData.validityPeriodTo)
return null;
return moment(this.classTimeSlotValidityPeriodFormData.validityPeriodTo).format("YYYY-MM-DD");
},
userLanguage() {
return Vue.ref(FHC_JS_DATA_STORAGE_OBJECT.user_language);
},
dropdownParsedOrganizationalUnits() {
return this.organizationalUnits
.filter((unit) => unit.aktiv)
.map((unit) => {
return {
label: `[${unit.organisationseinheittyp_kurzbz}] ${unit.bezeichnung}`,
value: unit.oe_kurzbz,
};
})
.sort((a, b) => a.label.localeCompare(b.label));
},
},
methods: {
updateClassTimeSlotValidityPeriodFormDataWatcher() {
if (
!this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.value ||
!this.classTimeSlotValidityPeriodFormData.validityPeriodFrom ||
!this.classTimeSlotValidityPeriodFormData.validityPeriodTo
) {
this.classTimeSlotValidityPeriodFormData.studyPlanId = null;
return;
}
this.refetchFilterableOptions();
},
async refetchFilterableOptions() {
this.studyPlans = await this.getTargetedStudyPlans(
this.classTimeSlotValidityPeriodFormData.organizationalUnit?.value,
this.formattedValidityPeriodFrom,
this.formattedValidityPeriodTo,
);
if (this.isEditMode) {
let isStudyPlanStillValid = this.studyPlans.some(
(plan) =>
plan.studienplan_id ===
this.editedClassTimeSlotValidityPeriod.studienplan_id,
);
if (!isStudyPlanStillValid) {
let editedStudyPlan = await this.getStudyPlan(
this.editedClassTimeSlotValidityPeriod.studienplan_id,
);
if (editedStudyPlan) {
this.studyPlans.push(editedStudyPlan);
}
}
}
let studySemestersByDates =
await this.getStudySemestersByOrganizationalUnitAndDates(
this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.value,
this.formattedValidityPeriodFrom,
this.formattedValidityPeriodTo,
);
let studySemesters = new Array(
...new Set(
studySemestersByDates
.map((s) => s.semester_numbers)
.flat()
.sort((a, b) => a - b),
),
);
this.studySemestersByNumber = studySemesters;
if (this.classTimeSlotValidityPeriodFormData.studyPlanId) {
let studySemestersByStudyProgramId =
await this.getStudySemestersByStudyPlanAndDates(
this.classTimeSlotValidityPeriodFormData.studyPlanId,
this.formattedValidityPeriodFrom,
this.formattedValidityPeriodTo,
);
studySemestersByStudyProgramId = new Array(
...new Set(
studySemestersByStudyProgramId
.map((s) => s.semester_numbers)
.flat()
.sort((a, b) => a - b),
),
);
studySemesters = studySemestersByStudyProgramId;
}
this.studySemestersByNumber = studySemesters;
},
async getTargetedStudyPlans(
organizationalUnitShortCode,
validityPeriodFrom,
validityPeriodTo,
) {
if (
!organizationalUnitShortCode ||
!validityPeriodFrom ||
!validityPeriodTo
) {
return [];
}
let getAllStudyPlansResponse = await this.$api.call(
ApiStudienPlan.getStudyPlansByOrganizationalUnitAndSemesterDates(
organizationalUnitShortCode,
validityPeriodFrom,
validityPeriodTo,
),
);
if (getAllStudyPlansResponse.meta.status !== "success") {
this.$fhcAlert.alertError(this.$p.t("ui", "errorFetchingStudyPlans"));
}
return getAllStudyPlansResponse.data?.length
? getAllStudyPlansResponse.data
: [];
},
async getStudyPlan(studienplan_id) {
if (!studienplan_id) {
return null;
}
let getStudyPlanResponse = await this.$api.call(
ApiStudienPlan.getStudyPlan(studienplan_id),
);
if (getStudyPlanResponse.meta.status !== "success") {
this.$fhcAlert.alertError(this.$p.t("ui", "errorFetchingStudyPlan"));
return null;
}
return getStudyPlanResponse.data ? getStudyPlanResponse.data : null;
},
async getStudySemestersByOrganizationalUnitAndDates(
organizationalUnitShortCode,
validityPeriodFrom,
validityPeriodTo,
) {
if (
!organizationalUnitShortCode ||
!validityPeriodFrom ||
!validityPeriodTo
) {
return [];
}
let getStudySemestersResponse = await this.$api.call(
ApiStudienSemester.getStudySemestersByOrganizationalUnitAndDates(
organizationalUnitShortCode,
validityPeriodFrom,
validityPeriodTo,
),
);
if (getStudySemestersResponse.meta.status !== "success") {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingSemesters"),
);
}
return getStudySemestersResponse.data?.length
? getStudySemestersResponse.data
: [];
},
async getStudySemestersByStudyPlanAndDates(
studyProgramId,
validityPeriodFrom,
validityPeriodTo,
) {
if (!studyProgramId || !validityPeriodFrom || !validityPeriodTo) {
return [];
}
let getStudySemestersResponse = await this.$api.call(
ApiStudienSemester.getStudySemestersByStudyPlanAndDates(
studyProgramId,
validityPeriodFrom,
validityPeriodTo,
),
);
if (getStudySemestersResponse.meta.status !== "success") {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingSemesters"),
);
}
return getStudySemestersResponse.data?.length
? getStudySemestersResponse.data
: [];
},
createClassTimeSlotValidityPeriod() {
return this.$refs.classTimeSlotValidityPeriodData
.call(
ApiClassSchedule.createClassTimeSlotValidityPeriod(this.id, {
...this.classTimeSlotValidityPeriodFormData,
organizationalUnitShortCode:
this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.value,
}),
)
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
this.$refs.classTimeSlotValidityPeriodModal.hide();
this.resetClassTimeSlotValidityPeriodModal();
window.scrollTo(0, 0);
this.$emit("classTimeSlotValidityPeriodCreated");
})
.catch((error) => {
this.$fhcAlert.handleSystemError(error);
});
},
updateClassTimeSlotValidityPeriod() {
return this.$refs.classTimeSlotValidityPeriodData
.call(
ApiClassSchedule.updateClassTimeSlotValidityPeriod(
this.id,
this.classTimeSlotValidityPeriodFormData.id,
{
...this.classTimeSlotValidityPeriodFormData,
organizationalUnitShortCode:
this.classTimeSlotValidityPeriodFormData.organizationalUnit
?.value,
},
),
)
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave"));
this.$refs.classTimeSlotValidityPeriodModal.hide();
this.resetClassTimeSlotValidityPeriodModal();
window.scrollTo(0, 0);
this.editedClassTimeSlotValidityPeriod = null;
this.$emit("classTimeSlotValidityPeriodUpdated");
})
.catch((error) => {
this.$fhcAlert.handleSystemError(error);
});
},
resetClassTimeSlotValidityPeriodModal() {
this.$refs.classTimeSlotValidityPeriodData?.clearValidation();
this.classTimeSlotValidityPeriodFormData = {
id: null,
organizationalUnit: null,
studyPlanId: null,
classTimeSlotTypeShortcode: null,
validityPeriodFrom: null,
validityPeriodTo: null,
semester: null,
description: null,
};
},
filterOrganizationalUnits(event) {
const query = event.query.toLowerCase();
if (!query) {
return (this.filteredOrganizationalUnits = [
...this.dropdownParsedOrganizationalUnits,
]);
}
return (this.filteredOrganizationalUnits =
this.dropdownParsedOrganizationalUnits.filter((unit) => {
return unit.label.toLowerCase().includes(query);
}));
},
getClassTimeSlotTypeLabel(classTimeSlotType) {
if (!classTimeSlotType) return "";
return this.userLanguage?.value === "English"
? classTimeSlotType.bezeichnung_mehrsprachig[1].value
: classTimeSlotType.bezeichnung_mehrsprachig[0].value;
},
},
async created() {
let getAllOrganizationalUnitsResponse = await this.$api.call(
ApiOrganizationalUnit.getAllOrganizationalUnits(),
);
if (getAllOrganizationalUnitsResponse.meta.status === "success") {
this.organizationalUnits = getAllOrganizationalUnitsResponse.data.sort(
(a, b) => a.bezeichnung.localeCompare(b.bezeichnung),
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingOrganizationalUnits"),
);
}
let getAllStudyPlansResponse = await this.$api.call(
ApiStudienPlan.getAllStudyPlans(),
);
if (getAllStudyPlansResponse.meta.status === "success") {
this.studyPlans = getAllStudyPlansResponse.data;
} else {
this.$fhcAlert.alertError(this.$p.t("ui", "errorFetchingStudyPlans"));
}
let getAllClassTimeSlotTypesResponse = await this.$api.call(
ApiClassSchedule.getAllClassScheduleTypes("filter[aktiv]=true"),
);
if (getAllClassTimeSlotTypesResponse.meta.status === "success") {
this.classTimeSlotTypes = getAllClassTimeSlotTypesResponse.data.map(
(type) => {
let descriptions = [{
lang: "de",
value: type.bezeichnung_mehrsprachig[0] || "",
}, {
lang: "en",
value: type.bezeichnung_mehrsprachig[1] || "",
}];
return {
...type,
bezeichnung_mehrsprachig: descriptions,
};
},
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleTimeSlotTypes"),
);
}
},
template: /*html*/ `
<bs-modal ref="classTimeSlotValidityPeriodModal" @hideBsModal="() => { $emit('hideBsModal'); resetClassTimeSlotValidityPeriodModal(); }" size="md">
<template #title>
<p v-if="!classTimeSlotValidityPeriodFormData.id" class="fw-bold mt-3">{{$p.t('ui', 'addClassTimeSlotValidityPeriodModalTitle')}}</p>
<p v-else class="fw-bold mt-3">{{$p.t('ui', 'editClassTimeSlotValidityPeriodModalTitle')}}</p>
</template>
<core-form class="row g-3" ref="classTimeSlotValidityPeriodData">
<form-validation />
<div class="row mb-3">
<form-input
v-model="classTimeSlotValidityPeriodFormData.organizationalUnit"
:label="$capitalize($p.t('lehre/organisationseinheit')) + ' *'"
:suggestions="filteredOrganizationalUnits"
:optionValue="(option) => option.value"
:optionLabel="(option) => option.label"
@complete="filterOrganizationalUnits"
dropdown
forceSelection
type="autocomplete"
name="organizationalUnitShortCode"
>
</form-input>
</div>
<div class="row mb-3">
<div class="col-12 mb-3">
<label>{{$p.t('ui', 'validityPeriod')}}</label>
</div>
<div class="col">
<form-input
v-model="classTimeSlotValidityPeriodFormData.validityPeriodFrom"
:label="$p.t('ui/von') + ' *'"
:teleport="true"
:enable-time-picker="false"
type="datePicker"
name="validityPeriodFrom"
format="dd.MM.yyyy"
auto-apply
/>
</div>
<div class="col">
<form-input
v-model="classTimeSlotValidityPeriodFormData.validityPeriodTo"
:label="$p.t('global/bis') + ' *'"
:teleport="true"
:enable-time-picker="false"
type="datePicker"
name="validityPeriodTo"
format="dd.MM.yyyy"
auto-apply
/>
</div>
</div>
<div class="row mb-3">
<form-input
v-model="classTimeSlotValidityPeriodFormData.studyPlanId"
:label="$p.t('lehre/studienplan')"
:disabled="isStudyPlanSelectDisabled"
type="select"
name="studyPlan"
>
<option :value="null"> - </option>
<option
v-for="studyPlan in studyPlans"
:key="studyPlan.studienplan_id"
:value="studyPlan.studienplan_id"
>
{{studyPlan.bezeichnung}}
</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
type="select"
id="ausbildungssemester"
name="semester"
:disabled="isStudyPlanSelectDisabled"
:label="$p.t('lehre', 'ausbildungssemester')"
v-model="classTimeSlotValidityPeriodFormData.semester"
>
<option
v-for="studySemester in studySemestersByNumber"
:key="studySemester"
:value="studySemester"
>
{{studySemester}}
</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
type="select"
name="classTimeSlotType"
:label="$p.t('ui/classTimeSlotType')"
v-model="classTimeSlotValidityPeriodFormData.classTimeSlotTypeShortcode"
>
<option :value="null"> - </option>
<option
v-for="classTimeSlotType in classTimeSlotTypes"
:key="classTimeSlotType.unterrichtszeitentyp_kurzbz"
:value="classTimeSlotType.unterrichtszeitentyp_kurzbz"
>
{{ getClassTimeSlotTypeLabel(classTimeSlotType) }}
</option>
</form-input>
</div>
<div class="row mb-3">
<form-input
type="textarea"
name="beschreibung"
:label="$p.t('global/beschreibung')"
v-model="classTimeSlotValidityPeriodFormData.description"
>
</form-input>
</div>
</core-form>
<template #footer>
<button v-if="!classTimeSlotValidityPeriodFormData.id" type="button" class="btn btn-primary" @click="createClassTimeSlotValidityPeriod">{{$p.t('ui', 'speichern')}}</button>
<button v-else type="button" class="btn btn-primary" @click="updateClassTimeSlotValidityPeriod">{{$p.t('ui', 'btnAktualisieren')}}</button>
</template>
</bs-modal>
`,
};
@@ -0,0 +1,317 @@
import ApiClassSchedule from "../../../js/api/factory/classSchedule.js";
import ApiClassroomHour from "../../../js/api/factory/classroomHour.js";
import BsModal from "../Bootstrap/Modal.js";
import ClassScheduleValidityPeriodForm from "./ClassScheduleValidityPeriodForm.js";
import ClassScheduleValidityPeriodModal from "./ClassScheduleValidityPeriodModal.js";
import ClassScheduleCalendarSelector from "./ClassScheduleCalendarSelector.js";
export default {
name: "ClassScheduleValidityPeriodOverview",
components: {
BsModal,
ClassScheduleValidityPeriodForm,
ClassScheduleValidityPeriodModal,
ClassScheduleCalendarSelector,
},
data: () => {
return {
classroomHours: [],
isClassTimeSlotFormVisible: false,
classTimeSlotValidityPeriodId: null,
classTimeSlotValidityPeriod: null,
areClassTimeSlotsLoaded: false,
classTimeSlots: [],
classTimeSlotTypes: [],
editedClassTimeSlots: [],
isClassTimeSlotValidityPeriodModalVisible: false,
editedClassTimeSlotValidityPeriodId: null,
};
},
computed: {
classScheduleValidityPeriodStartDate() {
if (!this.classTimeSlotValidityPeriod) return null;
let dateParts = this.classTimeSlotValidityPeriod.gueltig_von
.split("-")
.reverse();
return dateParts.join(".");
},
classScheduleValidityPeriodEndDate() {
if (!this.classTimeSlotValidityPeriod) return null;
let dateParts = this.classTimeSlotValidityPeriod.gueltig_bis
.split("-")
.reverse();
return dateParts.join(".");
},
},
methods: {
async fetchClassTimeValidityPeriod() {
let getClassTimeValidityPeriodResponse = await this.$api.call(
ApiClassSchedule.getClassTimeValidityPeriod(
this.classTimeSlotValidityPeriodId,
),
);
if (getClassTimeValidityPeriodResponse.meta.status === "success") {
this.classTimeSlotValidityPeriod =
getClassTimeValidityPeriodResponse.data[0];
if (!this.classTimeSlotValidityPeriod) {
this.$fhcAlert.alertError(
this.$p.t("ui", "classTimeSlotValidityPeriodNotFound"),
);
this.$router.push({ name: "overview" });
}
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleValidityPeriod"),
);
}
},
async fetchClassTimeSlots() {
let getClassTimeSlotsForValidityPeriodResponse = await this.$api.call(
ApiClassSchedule.getClassTimeSlotsForValidityPeriod(
this.classTimeSlotValidityPeriodId,
),
);
if (
getClassTimeSlotsForValidityPeriodResponse.meta.status === "success"
) {
this.classTimeSlots = getClassTimeSlotsForValidityPeriodResponse.data;
} else {
this.$fhcAlert.alertError(
this.$p.t(
"ui",
"errorFetchingClassScheduleTimeSlotForValidityPeriod",
),
);
}
this.areClassTimeSlotsLoaded = true;
},
showClassTimeSlotForm() {
this.isClassTimeSlotFormVisible = true;
},
async editClassTimeSlotsForValidityPeriod() {
await this.fetchClassTimeSlots();
this.editedClassTimeSlots =
this.classTimeSlots.map((slot) => {
return {
...slot,
id: slot.unterrichtszeit_id,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
classTimeSlotTypeShortcode: slot.unterrichtszeitentyp_kurzbz,
};
}) || [];
this.showClassTimeSlotForm();
},
deleteClassTimeSlotsForValidityPeriod() {
let isDeletionConfirmed = confirm(
this.$p.t("ui", "confirmDeleteClassTimeSlotsForGroup"),
);
if (!isDeletionConfirmed) {
return;
}
return this.$api
.call(
ApiClassSchedule.deleteClassTimeSlotsForValidityPeriod(
this.id,
this.classTimeSlotValidityPeriodId,
),
)
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successDelete"));
this.classTimeSlots = [];
this.fetchClassTimeSlots();
})
.catch((error) => {
this.$fhcAlert.handleSystemError(error);
});
},
getClassTimeSlotType(classTimeSlot) {
let classTimeSlotType = this.classTimeSlotTypes.find(
(type) =>
type.unterrichtszeitentyp_kurzbz ===
classTimeSlot.unterrichtszeitentyp_kurzbz,
);
return classTimeSlotType;
},
getClassTimeSlotBackgroundColor(classTimeSlot) {
let classTimeSlotType = this.classTimeSlotTypes.find(
(type) =>
type.unterrichtszeitentyp_kurzbz ===
classTimeSlot.unterrichtszeitentyp_kurzbz,
);
return classTimeSlotType ? classTimeSlotType.hintergrundfarbe : "#fff";
},
editClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId) {
this.editedClassTimeSlotValidityPeriodId = classTimeSlotValidityPeriodId;
},
deleteClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId) {
let isDeletionConfirmed = confirm(
this.$p.t("ui", "deleteClassTimeSlotValidityPeriodConfirmation"),
);
if (!isDeletionConfirmed) return;
return this.$api
.call(
ApiClassSchedule.deleteClassTimeSlotValidityPeriod(
this.id,
classTimeSlotValidityPeriodId,
),
)
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t("ui", "successDelete"));
this.$router.push({ name: "overview" });
})
.catch((error) => {
this.$fhcAlert.handleSystemError(error);
});
},
resetClassTimeSlotValidityPeriodModal() {
this.isClassTimeSlotValidityPeriodModalVisible = false;
},
},
async created() {
this.classTimeSlotValidityPeriodId =
this.$route.params.classTimeSlotValidityPeriodId;
await this.fetchClassTimeValidityPeriod();
let getAllClassTimeSlotTypesResponse = await this.$api.call(
ApiClassSchedule.getAllClassScheduleTypes("filter[aktiv]=true"),
);
if (getAllClassTimeSlotTypesResponse.meta.status === "success") {
this.classTimeSlotTypes = getAllClassTimeSlotTypesResponse.data.map(
(type) => {
let descriptions = [{
lang: "de",
value: type.bezeichnung_mehrsprachig[0] || "",
}, {
lang: "en",
value: type.bezeichnung_mehrsprachig[1] || "",
}];
return {
...type,
bezeichnung_mehrsprachig: descriptions,
};
},
);
} else {
this.$fhcAlert.alertError(
this.$p.t("ui", "errorFetchingClassScheduleTimeSlotTypes"),
);
}
let getAllClassroomHoursResponse = await this.$api.call(
ApiClassroomHour.getAllClassroomHours(),
);
if (getAllClassroomHoursResponse.meta.status === "success") {
this.classroomHours = getAllClassroomHoursResponse.data.map((hour) => {
return {
...hour,
beginn: hour.beginn.substring(0, 5),
ende: hour.ende.substring(0, 5),
};
});
} else {
this.$fhcAlert.alertError(this.$p.t("ui", "errorFetchingClassroomHours"));
}
this.fetchClassTimeSlots();
},
template: /* html */ `
<div class="container mt-4">
<div class='mb-5'>
<div class="d-flex align-items-center justify-content-between mb-2">
<div class="d-flex align-items-center gap-3">
<h1 class='m-0'>
{{ $p.t("ui", "classScheduleValidityPeriodOverviewHeading") }}
</h1>
<span class="m-0 badge bg-secondary">{{ classTimeSlotValidityPeriod?.orgform_kurzbz }}</span>
</div>
<div class="d-flex align-items-center gap-2">
<a
@click="editClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId)"
class="btn btn-link fs-3 p-0">
<i class="fa fa-edit"></i></a>
<a @click="deleteClassTimeSlotValidityPeriod(classTimeSlotValidityPeriodId)" class="btn btn-link text-danger fs-3 p-0"><i class="fa fa-trash"></i></a>
</div>
</div>
<h2>{{ classScheduleValidityPeriodStartDate }} - {{ classScheduleValidityPeriodEndDate }}</h2>
<h4 class="text-capitalize">{{ $p.t("lehre", "organisationseinheit") }}: {{ classTimeSlotValidityPeriod?.oe_bezeichnung }} ({{ classTimeSlotValidityPeriod?.oe_organisationseinheittyp_kurzbz }})</h4>
<h4 class="text-capitalize">{{ $p.t("lehre", "studienplan") }}: {{ classTimeSlotValidityPeriod?.studienplan_bezeichnung }}</h4>
<h5 class="text-capitalize">{{ $p.t("lehre", "ausbildungssemester") }}:
<span class="fw-normal">{{ classTimeSlotValidityPeriod?.ausbildungssemester }}</span>
</h5>
<h5 class="text-capitalize">{{ $p.t("global", "anmerkung") }}:
<span class="fw-normal">{{ classTimeSlotValidityPeriod?.anmerkung }}</span>
</h5>
</div>
<div>
<div v-if='!isClassTimeSlotFormVisible && !classTimeSlots.length && areClassTimeSlotsLoaded' class="col-12 d-flex justify-content-end">
<button type="button" class="btn btn-primary" @click="showClassTimeSlotForm">{{$p.t('ui', 'addClassTimeSlotButton')}}</button>
</div>
<class-schedule-validity-period-form
v-if="isClassTimeSlotFormVisible"
:class-time-slot-types="this.classTimeSlotTypes"
:classroom-hours="this.classroomHours.map(hour => hour.beginn + '-' + hour.ende)"
:class-time-slot-validity-period="classTimeSlotValidityPeriod"
:edited-class-time-slots="editedClassTimeSlots"
@classTimeSlotsCreated="() => { isClassTimeSlotFormVisible = false; this.areClassTimeSlotsLoaded = false; this.classTimeSlots = []; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
@classTimeSlotsEdited="() => { isClassTimeSlotFormVisible = false; this.areClassTimeSlotsLoaded = false; this.classTimeSlots = []; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
@hideForm="() => { isClassTimeSlotFormVisible = false; this.areClassTimeSlotsLoaded = false; this.classTimeSlots = []; fetchClassTimeSlots(); this.editedClassTimeSlots = []; }"
class="mb-4"
/>
<div v-if="!isClassTimeSlotFormVisible">
<h4>{{ $p.t("ui", "classScheduleValidityPeriodTimeSlots") }}</h4>
</div>
<transition>
<div v-if="classTimeSlots && Object.keys(classTimeSlots).length > 0 && !isClassTimeSlotFormVisible">
<div class="col-12 d-flex align-items-center justify-content-end gap-2">
<a class="ml-auto" @click="editClassTimeSlotsForValidityPeriod"><i class="fa fa-edit fs-5"></i></a>
<a class="ml-auto" @click="deleteClassTimeSlotsForValidityPeriod"><i class="fa fa-trash text-danger fs-5"></i></a>
</div>
<div class="row border-top rounded p-2 mt-4 mb-2 pt-1 pb-5">
<class-schedule-calendar-selector
:classroom-hours="this.classroomHours.map(hour => hour.beginn + '-' + hour.ende)"
:class-time-slot-types="this.classTimeSlotTypes"
:edited-overlays="classTimeSlots.map((slot) => {
return {
databaseId: slot.id,
id: slot.identifier,
weekday: parseInt(slot.wochentag) + 1,
type: slot.unterrichtszeitentyp_kurzbz,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
};
})"
:isPreviewMode="true"
/>
</div>
</div>
</transition>
<div v-if="!classTimeSlots || Object.keys(classTimeSlots).length === 0" class="d-flex align-items-center justify-content-center border rounded p-4 mt-4">
<p class="m-0">{{ $p.t("ui", "noClassScheduleValidityPeriodTimeSlotsFound") }}</p>
</div>
</div>
<class-schedule-validity-period-modal
:isVisible="isClassTimeSlotValidityPeriodModalVisible"
:editedClassTimeSlotValidityPeriodId="editedClassTimeSlotValidityPeriodId"
@hideBsModal="() => { resetClassTimeSlotValidityPeriodModal(); editedClassTimeSlotValidityPeriodId = null; }"
@classTimeSlotValidityPeriodUpdated="() => {
resetClassTimeSlotValidityPeriodModal();
this.editedClassTimeSlotValidityPeriodId = null;
fetchClassTimeValidityPeriod();
}"
/>
</div>
`,
};
@@ -0,0 +1,112 @@
import ClassScheduleCalendarSelector from "./ClassScheduleCalendarSelector.js";
import ApiClassroomHour from "../../../js/api/factory/classroomHour.js";
export default {
name: "ClassScheduleValidityPeriodPreview",
components: {
ClassScheduleCalendarSelector,
},
props: {
classScheduleValidityPeriod: {
type: Object,
required: true,
},
classTimeSlots: {
type: Array,
required: true,
},
classTimeSlotTypes: {
type: Array,
required: true,
},
},
data: () => {
return {
classRoomHours: [],
};
},
computed: {
classScheduleValidityPeriodStartDate() {
if (!this.$props.classScheduleValidityPeriod) return null;
let dateParts = this.$props.classScheduleValidityPeriod.gueltig_von
.split("-")
.reverse();
return dateParts.join(".");
},
classScheduleValidityPeriodEndDate() {
if (!this.$props.classScheduleValidityPeriod) return null;
let dateParts = this.$props.classScheduleValidityPeriod.gueltig_bis
.split("-")
.reverse();
return dateParts.join(".");
},
classScheduleValidityPeriodStudyPlan() {
return this.$props.classScheduleValidityPeriod.studienplan_bezeichnung;
},
},
methods: {
showClassScheduleValidityPeriod(classScheduleValidityPeriodId) {
this.$router.push({
name: "validityPeriodOverview",
params: {
classTimeSlotValidityPeriodId: classScheduleValidityPeriodId,
},
});
},
},
async created() {
let getAllClassroomHoursResponse = await this.$api.call(
ApiClassroomHour.getAllClassroomHours(),
);
if (getAllClassroomHoursResponse.meta.status === "success") {
this.classroomHours = getAllClassroomHoursResponse.data.map((hour) => {
return {
...hour,
beginn: hour.beginn.substring(0, 5),
ende: hour.ende.substring(0, 5),
};
});
} else {
this.$fhcAlert.alertError(this.$p.t("ui", "errorFetchingClassroomHours"));
}
},
template: /* html */ `
<div class="container mt-4">
<div class='py-3 d-flex align-items-center justify-content-between'>
<div>
<h2>{{ classScheduleValidityPeriodStartDate }} - {{ classScheduleValidityPeriodEndDate }}</h2>
<h4>{{ classScheduleValidityPeriodStudyPlan }}</h4>
</div>
<a
@click="showClassScheduleValidityPeriod($props.classScheduleValidityPeriod.unterrichtszeitengueltigkeit_id)"
class="ml-auto btn btn-link"
><i class="fa fa-eye fs-5"></i></a>
</div>
<div class='py-3 mb-4'>
<div v-if="$props.classTimeSlots && $props.classTimeSlots.length > 0">
<div class="row border-top rounded pt-1 pb-5">
<class-schedule-calendar-selector
:classroom-hours="this.classroomHours.map(hour => hour.beginn + '-' + hour.ende)"
:class-time-slot-types="this.classTimeSlotTypes"
:edited-overlays="$props.classTimeSlots.map((slot) => {
return {
databaseId: slot.id,
id: slot.identifier,
weekday: parseInt(slot.wochentag) + 1,
type: slot.unterrichtszeitentyp_kurzbz,
startTime: slot.uhrzeit_von,
endTime: slot.uhrzeit_bis,
};
})"
:isPreviewMode="true"
/>
</div>
</div>
<div v-else class="d-flex align-items-center justify-content-center border rounded p-2">
<p class="m-0">{{ $p.t("ui", "noClassScheduleValidityPeriodTimeSlotsFound") }}</p>
</div>
</div>
</div>
`,
};
+2 -1
View File
@@ -30,7 +30,8 @@ export default {
function onStart(evt) {
const value = el.dataset.fhcDraggableValue;
if (value) {
setTransferData(evt, JSON.parse(value), true);
let disableImage = binding.modifiers?.noImage === true;
setTransferData(evt, JSON.parse(value), !disableImage);
if (el.dataset.fhcEffectAllowed)
evt.dataTransfer.effectAllowed = el.dataset.fhcEffectAllowed;
+1 -1
View File
@@ -37,7 +37,7 @@ export function formatDate(d) {
// parameter is of type Date
if(d instanceof Date)
{
if (isNaN(date.valueOf())) {
if (isNaN(d.valueOf())) {
return 'N/A';
}
// if the date is an invalid string then creating a date from the string will fail and N/A is returned
+3
View File
@@ -30,6 +30,9 @@ const TYPE_DEFINITION = {
prestudent: {
id: "prestudent_id",
dragIcon: "fa-solid fa-user-graduate text-muted"
},
calendar_selector_overlay: {
id: "calendar_selector_overlay_id",
}
// TODO: IMPLEMENT OTHER TYPES
};
+1
View File
@@ -93,6 +93,7 @@ require_once('dbupdate_3.4/62889_reihungstest_ueberwachung_mit_constructor.php')
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/76031_unterrichtszeiten_der_studiengänge.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -0,0 +1,293 @@
<?php
if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz='lehre/unterrichtszeiten_gk' LIMIT 1"))
{
if($db->db_num_rows($result)==0)
{
$qry = "
INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung) VALUES
('lehre/unterrichtszeiten_gk','Unterrichtszeiten Gültigkeit')
";
if(!$db->db_query($qry))
echo '<strong>system.tbl_berechtigung '.$db->db_last_error().'</strong><br>';
else
echo ' system.tbl_berechtigung: lehre/unterrichtszeiten_gk permissions inserted<br>';
}
}
if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz='lehre/unterrichtszeiten_typ' LIMIT 1"))
{
if($db->db_num_rows($result)==0)
{
$qry = "
INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung) VALUES
('lehre/unterrichtszeiten_typ','Unterrichtszeiten Typ')
";
if(!$db->db_query($qry))
echo '<strong>system.tbl_berechtigung '.$db->db_last_error().'</strong><br>';
else
echo ' system.tbl_berechtigung: lehre/unterrichtszeiten_typ permissions inserted<br>';
}
}
if(!$result = @$db->db_query("SELECT 1 FROM lehre.tbl_unterrichtszeiten LIMIT 1"))
{
$qry = '
CREATE TABLE lehre.tbl_unterrichtszeiten (
"unterrichtszeit_id" INTEGER NOT NULL,
"wochentag" INTEGER NOT NULL,
"uhrzeit_von" TIME NOT NULL,
"uhrzeit_bis" TIME NOT NULL,
"unterrichtszeitentyp_kurzbz" VARCHAR(32) NOT NULL,
"unterrichtszeitengueltigkeit_id" INTEGER NOT NULL,
"insertamum" TIMESTAMP WITH TIME ZONE DEFAULT now(),
"insertvon" VARCHAR(32),
"updateamum" TIMESTAMP WITH TIME ZONE DEFAULT now(),
"updatevon" VARCHAR(32),
CONSTRAINT pk_unterrichtszeit_id PRIMARY KEY("unterrichtszeit_id")
);';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten: '.$db->db_last_error().'</strong><br>';
else
echo '<br>lehre.tbl_unterrichtszeiten table created';
$db->db_query('CREATE SEQUENCE IF NOT EXISTS lehre.seq_tbl_unterrichtszeiten_unterrichtszeit_id
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;');
$db->db_query("ALTER TABLE lehre.tbl_unterrichtszeiten ALTER COLUMN unterrichtszeit_id SET DEFAULT nextval('lehre.seq_tbl_unterrichtszeiten_unterrichtszeit_id');");
$qry = 'GRANT SELECT ON TABLE lehre.tbl_unterrichtszeiten TO web;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>web</strong> on lehre.tbl_unterrichtszeiten';
$qry = 'GRANT USAGE ON lehre.seq_tbl_unterrichtszeiten_unterrichtszeit_id TO web;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>web</strong> on lehre.tbl_unterrichtszeiten';
$qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE lehre.tbl_unterrichtszeiten TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten';
$qry = 'GRANT USAGE, INSERT ON lehre.seq_tbl_unterrichtszeiten_unterrichtszeit_id TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten';
$qry = 'GRANT USAGE, UPDATE ON lehre.seq_tbl_unterrichtszeiten_unterrichtszeit_id TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten';
}
if(!$result = @$db->db_query("SELECT 1 FROM lehre.tbl_unterrichtszeiten_typ LIMIT 1"))
{
$qry = '
CREATE TABLE lehre.tbl_unterrichtszeiten_typ (
"unterrichtszeitentyp_kurzbz" VARCHAR(32) NOT NULL,
"bezeichnung_mehrsprachig" TEXT[] NOT NULL,
"aktiv" BOOLEAN DEFAULT true,
"hintergrundfarbe" VARCHAR(7),
"is_default" BOOLEAN DEFAULT false,
"insertamum" TIMESTAMP WITH TIME ZONE DEFAULT now(),
"insertvon" VARCHAR(32),
"updateamum" TIMESTAMP WITH TIME ZONE DEFAULT now(),
"updatevon" VARCHAR(32),
CONSTRAINT pk_unterrichtszeitentyp_kurzbz PRIMARY KEY("unterrichtszeitentyp_kurzbz")
);';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_typ: '.$db->db_last_error().'</strong><br>';
else
echo '<br>lehre.tbl_unterrichtszeiten_typ table created';
$db->db_query('CREATE SEQUENCE IF NOT EXISTS lehre.seq_tbl_unterrichtszeiten_typ_unterrichtszeitentyp_kurzbz
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;');
$db->db_query("ALTER TABLE lehre.tbl_unterrichtszeiten_typ ALTER COLUMN unterrichtszeitentyp_kurzbz SET DEFAULT nextval('lehre.seq_tbl_unterrichtszeiten_typ_unterrichtszeitentyp_kurzbz');");
$qry = 'GRANT SELECT ON TABLE lehre.tbl_unterrichtszeiten_typ TO web;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_typ: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>web</strong> on lehre.tbl_unterrichtszeiten_typ';
$qry = 'GRANT USAGE ON lehre.seq_tbl_unterrichtszeiten_typ_unterrichtszeitentyp_kurzbz TO web;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_typ: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>web</strong> on lehre.tbl_unterrichtszeiten_typ';
$qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE lehre.tbl_unterrichtszeiten_typ TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_typ: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten_typ';
$qry = 'GRANT USAGE, INSERT ON lehre.seq_tbl_unterrichtszeiten_typ_unterrichtszeitentyp_kurzbz TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_typ: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten_typ';
$qry = 'GRANT USAGE, UPDATE ON lehre.seq_tbl_unterrichtszeiten_typ_unterrichtszeitentyp_kurzbz TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_typ: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten_typ';
$qry = "
INSERT INTO lehre.tbl_unterrichtszeiten_typ (unterrichtszeitentyp_kurzbz, bezeichnung_mehrsprachig, aktiv, hintergrundfarbe, is_default) VALUES
('unterrichtszeiten', ARRAY['Unterrichtszeiten', 'Teaching Times'], 't', '#FFFFFF', 'f'),
('vorlesungen', ARRAY['Vorlesung', 'Lecture'], 't', '#FF8A8A', 't'),
('backuptage', ARRAY['Übung', 'Exercise'], 't', '#8AFF8A', 'f'),
('ausgleichswochen', ARRAY['Ausgleichswochen', 'Compensation Weeks'], 't', '#8A8AFF', 'f'),
('prüfungswochen', ARRAY['Prüfungswochen', 'Exam Weeks'], 't', '#FFFF8A', 'f');
";
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_typ seeders: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Seeded lehre.tbl_unterrichtszeiten_typ with main class time slot types';
}
if(!$result = @$db->db_query("SELECT 1 FROM lehre.tbl_unterrichtszeiten_gueltigkeit LIMIT 1"))
{
$qry = '
CREATE TABLE lehre.tbl_unterrichtszeiten_gueltigkeit (
"unterrichtszeitengueltigkeit_id" BIGINT NOT NULL,
"gueltig_von" DATE NOT NULL,
"gueltig_bis" DATE NOT NULL,
"oe_kurzbz" VARCHAR(32) NOT NULL,
"ausbildungssemester" SMALLINT,
"anmerkung" TEXT,
"unterrichtszeitentyp_kurzbz" VARCHAR(32),
"studienplan_id" INTEGER,
"insertamum" TIMESTAMP WITH TIME ZONE DEFAULT now(),
"insertvon" VARCHAR(32),
"updateamum" TIMESTAMP WITH TIME ZONE DEFAULT now(),
"updatevon" VARCHAR(32),
CONSTRAINT pk_unterrichtszeitengueltigkeit_id PRIMARY KEY("unterrichtszeitengueltigkeit_id")
);';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>lehre.tbl_unterrichtszeiten_gueltigkeit table created';
$db->db_query('CREATE SEQUENCE IF NOT EXISTS lehre.seq_tbl_unterrichtszeiten_gueltigkeit_unterrichtszeitengueltigkeit_id
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;');
$db->db_query("ALTER TABLE lehre.tbl_unterrichtszeiten_gueltigkeit ALTER COLUMN unterrichtszeitengueltigkeit_id SET DEFAULT nextval('lehre.seq_tbl_unterrichtszeiten_gueltigkeit_unterrichtszeitengueltigkeit_id');");
$qry = 'GRANT SELECT ON TABLE lehre.tbl_unterrichtszeiten_gueltigkeit TO web;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>web</strong> on lehre.tbl_unterrichtszeiten_gueltigkeit';
$qry = 'GRANT USAGE ON lehre.seq_tbl_unterrichtszeiten_gueltigkeit_unterrichtszeitengueltigkeit_id TO web;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>web</strong> on lehre.tbl_unterrichtszeiten_gueltigkeit';
$qry = 'GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE lehre.tbl_unterrichtszeiten_gueltigkeit TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten_gueltigkeit';
$qry = 'GRANT USAGE, INSERT ON lehre.seq_tbl_unterrichtszeiten_gueltigkeit_unterrichtszeitengueltigkeit_id TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten_gueltigkeit';
$qry = 'GRANT USAGE, UPDATE ON lehre.seq_tbl_unterrichtszeiten_gueltigkeit_unterrichtszeitengueltigkeit_id TO vilesci;';
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Granted privileges to <strong>vilesci</strong> on lehre.tbl_unterrichtszeiten_gueltigkeit';
}
$result = $db->db_query("SELECT constraint_name FROM information_schema.table_constraints
WHERE table_name='tbl_unterrichtszeiten' AND constraint_type='FOREIGN KEY' AND constraint_name='fk_unterrichtszeitentyp_kurzbz'");
if($db->db_num_rows($result)==0)
{
$qry = "ALTER TABLE lehre.tbl_unterrichtszeiten ADD CONSTRAINT fk_unterrichtszeitentyp_kurzbz FOREIGN KEY(unterrichtszeitentyp_kurzbz) REFERENCES lehre.tbl_unterrichtszeiten_typ(unterrichtszeitentyp_kurzbz);";
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Added foreign key constraint fk_unterrichtszeitentyp_kurzbz to lehre.tbl_unterrichtszeiten';
}
$result = $db->db_query("SELECT constraint_name FROM information_schema.table_constraints
WHERE table_name='tbl_unterrichtszeiten' AND constraint_type='FOREIGN KEY' AND constraint_name='fk_unterrichtszeitengueltigkeit_id'");
if($db->db_num_rows($result)==0)
{
$qry = "ALTER TABLE lehre.tbl_unterrichtszeiten ADD CONSTRAINT fk_unterrichtszeitengueltigkeit_id FOREIGN KEY(unterrichtszeitengueltigkeit_id) REFERENCES lehre.tbl_unterrichtszeiten_gueltigkeit(unterrichtszeitengueltigkeit_id);";
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Added foreign key constraint fk_unterrichtszeitengueltigkeit_id to lehre.tbl_unterrichtszeiten';
}
$result = $db->db_query("SELECT constraint_name FROM information_schema.table_constraints
WHERE table_name='tbl_unterrichtszeiten_gueltigkeit' AND constraint_type='FOREIGN KEY' AND constraint_name='fk_unterrichtszeitentyp_kurzbz'");
if($db->db_num_rows($result)==0)
{
$qry = "ALTER TABLE lehre.tbl_unterrichtszeiten_gueltigkeit ADD CONSTRAINT fk_unterrichtszeitentyp_kurzbz FOREIGN KEY(unterrichtszeitentyp_kurzbz) REFERENCES lehre.tbl_unterrichtszeiten_typ(unterrichtszeitentyp_kurzbz);";
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Added foreign key constraint fk_unterrichtszeitentyp_kurzbz to lehre.tbl_unterrichtszeiten_gueltigkeit';
}
$result = $db->db_query("SELECT constraint_name FROM information_schema.table_constraints
WHERE table_name='tbl_unterrichtszeiten_gueltigkeit' AND constraint_type='FOREIGN KEY' AND constraint_name='fk_studienplan_id'");
if($db->db_num_rows($result)==0)
{
$qry = "ALTER TABLE lehre.tbl_unterrichtszeiten_gueltigkeit ADD CONSTRAINT fk_studienplan_id FOREIGN KEY(studienplan_id) REFERENCES lehre.tbl_studienplan(studienplan_id);";
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Added foreign key constraint fk_studienplan_id to lehre.tbl_unterrichtszeiten_gueltigkeit';
}
$result = $db->db_query("SELECT constraint_name FROM information_schema.table_constraints
WHERE table_name='tbl_unterrichtszeiten_gueltigkeit' AND constraint_type='FOREIGN KEY' AND constraint_name='fk_oe_kurzbz'");
if($db->db_num_rows($result)==0)
{
$qry = "ALTER TABLE lehre.tbl_unterrichtszeiten_gueltigkeit ADD CONSTRAINT fk_oe_kurzbz FOREIGN KEY(oe_kurzbz) REFERENCES public.tbl_organisationseinheit(oe_kurzbz);";
if(!$db->db_query($qry))
echo '<strong>lehre.tbl_unterrichtszeiten_gueltigkeit: '.$db->db_last_error().'</strong><br>';
else
echo '<br>Added foreign key constraint fk_oe_kurzbz to lehre.tbl_unterrichtszeiten_gueltigkeit';
}
?>
+1401 -1
View File
File diff suppressed because it is too large Load Diff