Compare commits

..

15 Commits

Author SHA1 Message Date
ma0068 a26fc3e7ea update system db update for Storniert 2026-06-03 07:43:10 +02:00
ma0068 60607d54ef delete unused code 2026-06-02 16:12:08 +02:00
ma0068 fe7466b17c refactor query getAllPrestudentsWhereComitteeExamFailed for exception stornierte Antraege 2026-06-02 14:43:03 +02:00
ma0068 1fa72bd286 filter terminated out of active filter entries in leitung list 2026-06-02 14:40:21 +02:00
ma0068 80f6161d03 View Student: do not show terminated Antraege 2026-06-01 14:50:14 +02:00
ma0068 7858d90d12 new workflow Terminate of Studierendenantrag
- DB Update for new studierendenantrag_statustyp with studierendenantrag_statustyp_kurzbz = 'Storniert'
- new permission student/studierendenantrag
- possibility to set status to Terminated in case of new permission
2026-06-01 14:10:14 +02:00
Harald Bamberger cb7a0f7669 Merge branch 'feature-70376/Lohnguide' 2026-05-13 11:53:14 +02:00
Harald Bamberger 68d97a5e97 handle case where old value or new value and not both are null explicitly in markDirty Method 2026-05-13 11:42:25 +02:00
Harald Bamberger d27071528f revert change to comparision in markDirty Method 2026-05-13 11:16:18 +02:00
Harald Bamberger 17772c3738 Merge branch 'master' into feature-70376/Lohnguide 2026-05-13 11:15:07 +02:00
Harald Bamberger bbb4f8a01c Merge branch 'bug-76146/studvw_karteireiter_dokumente_akzeptiert_eintraege_ohne_vorhandenes_dokument' 2026-05-06 16:13:50 +02:00
Harald Bamberger b7e48633ab Merge branch 'master' into bug-76146/studvw_karteireiter_dokumente_akzeptiert_eintraege_ohne_vorhandenes_dokument 2026-05-05 13:33:38 +02:00
Werner Masik 7f13c128f1 allow null value for vordienstzeit; changed comparison in markDirty to !== (because of 0 vs. null issue) 2026-05-04 20:35:51 +02:00
Werner Masik 58a921b500 changed lohnguide kommentar data type to text 2026-04-30 09:47:12 +02:00
ma0048 21d80905a2 akzeptierte dokumente anzeigen, auch wenn kein dokument vorhanden ist 2026-04-13 13:04:46 +02:00
23 changed files with 487 additions and 472 deletions
@@ -45,7 +45,8 @@ class Leitung extends FHCAPI_Controller
'unpauseAntrag' => ['student/antragfreigabe:w', 'student/studierendenantrag:w'],
'objectAntrag' => ['student/antragfreigabe:w', 'student/studierendenantrag:w'],
'approveObjection' => ['student/antragfreigabe:w', 'student/studierendenantrag:w'],
'denyObjection' => ['student/antragfreigabe:w', 'student/studierendenantrag:w']
'denyObjection' => ['student/antragfreigabe:w', 'student/studierendenantrag:w'],
'terminateAntrag' => ['student/antragstornieren:w']
]);
// Libraries
@@ -426,4 +427,37 @@ class Leitung extends FHCAPI_Controller
return $this->terminateWithSuccess($studierendenantrag_id);
}
public function terminateAntrag()
{
$this->load->library('form_validation');
$this->form_validation->set_rules(
'studierendenantrag_id',
'Studierenden Antrag',
[
'required',
['isEntitledToTerminateAntrag', [$this->antraglib, 'isEntitledToTerminateAntrag']],
['antragCanBeTerminated', [$this->antraglib, 'antragCanBeTerminated']]
],
[
'isEntitledToUnpauseAntrag' => $this->p->t('studierendenantrag', 'error_no_right'),
'antragCanBeTerminated' => $this->p->t(
'studierendenantrag',
'error_not_terminated',
['id' => $this->input->post('studierendenantrag_id')]
)
]
);
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$studierendenantrag_id = $this->input->post('studierendenantrag_id');
$result = $this->antraglib->terminateAntrag($studierendenantrag_id, getAuthUID());
$this->getDataOrTerminateWithError($result);
return $this->terminateWithSuccess($studierendenantrag_id);
}
}
@@ -78,52 +78,32 @@ class Dokumente extends FHCAPI_Controller
$this->terminateWithError($this->p->t('ui', 'errorMissingValue', ['value' => 'Studiengang_kz']), self::ERROR_TYPE_GENERAL);
$resultPreDoc = $this->_getPrestudentDokumente($prestudent_id);
$arrayAccepted = [];
$person_id = $this->_getPersonId($prestudent_id);
$docNames = array_map(function ($item) {
return $item->dokument_kurzbz;
}, $resultPreDoc);
$mergedArray = [];
foreach($docNames as $doc)
foreach ($resultPreDoc as $pre)
{
$result = $this->AkteModel->getAktenFAS($person_id, $doc, $studiengang_kz, $prestudent_id, true);
$result = $this->AkteModel->getAktenFAS($person_id, $pre->dokument_kurzbz, $studiengang_kz, $prestudent_id, true);
if (isError($result))
{
return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL);
}
if (hasData($result))
{
$data = getData($result);
foreach ($data as $value)
foreach (getData($result) as $doc)
{
array_push($arrayAccepted, $value);
$merged = clone $doc;
$merged->docdatum = $pre->docdatum;
$merged->insertvonma = $pre->insertvonma;
$merged->bezeichnung = $pre->bezeichnung;
$mergedArray[] = $merged;
}
}
}
//Mapping with document_kurzbz
$preDocMap = [];
foreach ($resultPreDoc as $pre) {
$preDocMap[$pre->dokument_kurzbz] = $pre;
}
$mergedArray = [];
foreach ($arrayAccepted as $doc) {
$merged = clone $doc;
if (isset($preDocMap[$doc->dokument_kurzbz])) {
$merged->docdatum = $preDocMap[$doc->dokument_kurzbz]->docdatum;
$merged->insertvonma = $preDocMap[$doc->dokument_kurzbz]->insertvonma;
$merged->bezeichnung = $preDocMap[$doc->dokument_kurzbz]->bezeichnung;
} else {
$merged->akzeptiertdatum = null;
$merged->akzeptiertvon = null;
else
{
$mergedArray[] = $pre;
}
$mergedArray[] = $merged;
}
$this->terminateWithSuccess($mergedArray);
@@ -86,9 +86,12 @@ class Studierendenantrag extends FHC_Controller
$stgA = $this->permissionlib->getSTG_isEntitledFor('student/studierendenantrag') ?: [];
$permissionTerminateAntrag = $this->permissionlib->isBerechtigt('student/antragstornieren');
$this->load->view('lehre/Antrag/Leitung/List', [
'stgA' => $stgA,
'stgL' => $stgL
'stgL' => $stgL,
'permissionTerminateAntrag' => $permissionTerminateAntrag
]);
}
+47
View File
@@ -1948,6 +1948,22 @@ class AntragLib
return $result;
}
/**
* @param integer $antrag_id
* @param string $insertvon
*
* @return stdClass
*/
public function terminateAntrag($antrag_id, $insertvon)
{
$result = $this->_ci->StudierendenantragstatusModel->insert([
'studierendenantrag_id' => $antrag_id,
'studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_TERMINATED,
'insertvon' => $insertvon
]);
return $result;
}
/**
* @param integer $studierendenantrag_id
@@ -2119,6 +2135,16 @@ class AntragLib
return $this->hasAccessToAntrag($antrag_id, 'student/antragfreigabe');
}
/**
* @param integer $antrag_id
*
* @return boolean
*/
public function isEntitledToTerminateAntrag($antrag_id)
{
return ($this->hasAccessToAntrag($antrag_id, 'student/antragstornieren'));
}
/**
* @param integer $antrag_id
*
@@ -2149,6 +2175,27 @@ class AntragLib
return $this->_ci->StudierendenantragModel->isManuallyPaused($antrag_id);
}
/**
* @param integer $antrag_id
*
* @return boolean
*/
public function antragCanBeTerminated($antrag_id)
{
$this->_ci->StudierendenantragModel->db->where_not_in('campus.get_status_studierendenantrag(studierendenantrag_id)', [
Studierendenantragstatus_model::STATUS_DEREGISTERED,
Studierendenantragstatus_model::STATUS_APPROVED,
Studierendenantragstatus_model::STATUS_TERMINATED
]);
$result = $this->_ci->StudierendenantragModel->loadWhere([
'studierendenantrag_id' => $antrag_id,
'typ' => Studierendenantrag_model::TYP_WIEDERHOLUNG
]);
return hasData($result);
}
/**
* @param integer $antrag_id
* @param string|array $status
@@ -40,7 +40,9 @@ abstract class AbstractBestandteil implements IValidation
if( is_bool($new_value) && ($old_value !== $new_value) ) {
$this->modifiedcolumns[$columnname] = $columnname;
} else if($old_value != $new_value) {
} else if(is_null($old_value) xor is_null($new_value)) {
$this->modifiedcolumns[$columnname] = $columnname;
} else if($old_value != $new_value) {
$this->modifiedcolumns[$columnname] = $columnname;
}
}
@@ -137,19 +137,25 @@ EOTXT;
return parent::__toString() . $txt;
}
/* public function validate()
public function validate()
{
if( !(filter_var($this->tage, FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => 1,
'max_range' => 50
)
)
)) ) {
$this->validationerrors[] = 'Urlaubsanspruch muss eine Tagesanzahl im Bereich 1 bis 50 sein.';
$value = $this->vordienstzeit;
if ($value === null || $value === '') {
$result = null; // allow null value
} else {
$result = filter_var($value, FILTER_VALIDATE_INT, [
'options' => [
'min_range' => 0,
'max_range' => 100
]
]);
if ($result === false) {
$this->validationerrors[] = 'Vordienstjahre muss eine ganze Zahl (0 bis 100) enthalten oder leer sein.';
}
}
return parent::validate();
} */
}
}
+49 -13
View File
@@ -286,24 +286,60 @@ class Pruefung_model extends DB_Model
$this->db->where_in("get_rolle_prestudent(ps.prestudent_id, null)", $this->config->item('antrag_prestudentstatus_whitelist'));
if (is_array($status)) {
if (in_array(null, $status)) {
$status = array_filter($status);
if (count($status)) {
$this->db->group_start();
$this->db->where_in('campus.get_status_studierendenantrag(a.studierendenantrag_id)', $status);
$this->db->or_where('campus.get_status_studierendenantrag(a.studierendenantrag_id)', null);
$this->db->group_end();
} else {
$this->db->where('campus.get_status_studierendenantrag(a.studierendenantrag_id)', null);
if (is_array($status))
{
if (in_array(null, $status))
{
//$status = array_filter($status);
//just filter out null
$status = array_filter($status, static function ($value)
{
return $value !== null;
});
$this->db->group_start();
// kein Antrag vorhanden ODER nur stornierte Anträge vorhanden
$this->db->or_where("
NOT EXISTS (
SELECT 1
FROM campus.tbl_studierendenantrag a2
WHERE a2.prestudent_id = ps.prestudent_id
AND a2.typ = " . $this->db->escape(Studierendenantrag_model::TYP_WIEDERHOLUNG) . "
AND COALESCE(
campus.get_status_studierendenantrag(a2.studierendenantrag_id),
''
) <> 'Storniert')", null, false);
if (!empty($status))
{
$this->db->or_where_in('campus.get_status_studierendenantrag(a.studierendenantrag_id)', $status);
}
} else {
$this->db->group_end();
}
else
{
$this->db->where_in('campus.get_status_studierendenantrag(a.studierendenantrag_id)', $status);
}
} else {
}
// kein Antrag vorhanden ODER nur stornierte Anträge vorhanden
elseif ($status === null)
{
$this->db->where("
NOT EXISTS (
SELECT 1
FROM campus.tbl_studierendenantrag a2
WHERE a2.prestudent_id = ps.prestudent_id
AND a2.typ = " . $this->db->escape(Studierendenantrag_model::TYP_WIEDERHOLUNG) . "
AND COALESCE(
campus.get_status_studierendenantrag(a2.studierendenantrag_id),
''
) <> 'Storniert'
)", null, false);
}
else
{
$this->db->where('campus.get_status_studierendenantrag(a.studierendenantrag_id)', $status);
}
return $this->loadWhereCommitteeExamsFailed();
}
}
@@ -98,7 +98,8 @@ class Studierendenantrag_model extends DB_Model
Studierendenantragstatus_model::STATUS_OBJECTION_DENIED,
Studierendenantragstatus_model::STATUS_DEREGISTERED,
Studierendenantragstatus_model::STATUS_PAUSE,
Studierendenantragstatus_model::STATUS_REMINDERSENT
Studierendenantragstatus_model::STATUS_REMINDERSENT,
Studierendenantragstatus_model::STATUS_TERMINATED
]);
$this->db->or_group_start();
$this->db->where('s.studierendenantrag_statustyp_kurzbz', Studierendenantragstatus_model::STATUS_APPROVED);
@@ -16,6 +16,7 @@ class Studierendenantragstatus_model extends DB_Model
const STATUS_OBJECTION_DENIED = 'EinspruchAbgelehnt';
const STATUS_DEREGISTERED = 'Abgemeldet';
const STATUS_PAUSE = 'Pause';
const STATUS_TERMINATED = 'Storniert';
const INSERTVON_ABMELDUNGSTGL = "AbmeldungStgl";
const INSERTVON_DEREGISTERED = "Studienabbruch";
@@ -44,6 +44,7 @@ $this->load->view(
<studierendenantrag-leitung
:stg-a="<?= htmlspecialchars(json_encode(array_values($stgA))); ?>"
:stg-l="<?= htmlspecialchars(json_encode(array_values($stgL))); ?>"
:permission-terminate-antrag="<?= json_encode($permissionTerminateAntrag); ?>"
>
</studierendenantrag-leitung>
+147 -137
View File
@@ -71,147 +71,157 @@ if(defined('CIS4')){
</thead>
<tbody>
<?php foreach($array['antraege'] as $antrag){ ?>
<tr>
<td><?= $antrag->studierendenantrag_id; ?></td>
<td><?= $this->p->t('studierendenantrag', 'antrag_typ_' . $antrag->typ); ?></td>
<td>
<?=
(
$antrag->status == Studierendenantragstatus_model::STATUS_PAUSE
&& $antrag->status_insertvon == Studierendenantragstatus_model::INSERTVON_DEREGISTERED
)
? $this->p->t('studierendenantrag', 'status_stop')
: $antrag->status_bezeichnung;
?>
</td>
<td><?= $antrag->studiensemester_kurzbz; ?></td>
<td><?= (new DateTime($antrag->datum))->format('d.m.Y'); ?></td>
<td><?= $antrag->datum_wiedereinstieg ? (new DateTime($antrag->datum_wiedereinstieg))->format('d.m.Y') : ''; ?></td>
<td><!-- Button trigger modal -->
<?php if($antrag->grund){ ?>
<a href="#modalgrund<?= $antrag->studierendenantrag_id; ?>" data-bs-toggle="modal">
<?= $this->p->t('studierendenantrag', 'antrag_grund'); ?>
</a>
<!-- Modal -->
<div
class="modal fade"
id="modalgrund<?= $antrag->studierendenantrag_id; ?>"
tabindex="-1"
aria-labelledby="modalgrundLabel<?= $antrag->studierendenantrag_id; ?>"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5
class="modal-title"
id="modalgrundLabel<?= $antrag->studierendenantrag_id; ?>"
>
<?= $this->p->t('studierendenantrag', 'antrag_grund'); ?>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea
class="form-control"
style="width: 100%; height: 250px;"
readonly
>
<?= $antrag->grund; ?>
</textarea>
<?php if (
!(
$antrag->typ == Studierendenantrag_model::TYP_WIEDERHOLUNG
&& $antrag->status == Studierendenantragstatus_model::STATUS_TERMINATED
)
) { ?>
<tr>
<td><?= $antrag->studierendenantrag_id; ?></td>
<td><?= $this->p->t('studierendenantrag', 'antrag_typ_' . $antrag->typ); ?></td>
<td>
<?=
(
$antrag->status == Studierendenantragstatus_model::STATUS_PAUSE
&& $antrag->status_insertvon == Studierendenantragstatus_model::INSERTVON_DEREGISTERED
)
? $this->p->t('studierendenantrag', 'status_stop')
: $antrag->status_bezeichnung;
?>
</td>
<td><?= $antrag->studiensemester_kurzbz; ?></td>
<td><?= (new DateTime($antrag->datum))->format('d.m.Y'); ?></td>
<td><?= $antrag->datum_wiedereinstieg ? (new DateTime($antrag->datum_wiedereinstieg))->format('d.m.Y') : ''; ?></td>
<td><!-- Button trigger modal -->
<?php if($antrag->grund){ ?>
<a href="#modalgrund<?= $antrag->studierendenantrag_id; ?>" data-bs-toggle="modal">
<?= $this->p->t('studierendenantrag', 'antrag_grund'); ?>
</a>
<!-- Modal -->
<div
class="modal fade"
id="modalgrund<?= $antrag->studierendenantrag_id; ?>"
tabindex="-1"
aria-labelledby="modalgrundLabel<?= $antrag->studierendenantrag_id; ?>"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5
class="modal-title"
id="modalgrundLabel<?= $antrag->studierendenantrag_id; ?>"
>
<?= $this->p->t('studierendenantrag', 'antrag_grund'); ?>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea
class="form-control"
style="width: 100%; height: 250px;"
readonly
>
<?= $antrag->grund; ?>
</textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<?php } ?>
</td>
<td>
<?php if($antrag->dms_id) {?>
<a
class="text-decoration-none"
href="<?= site_url('lehre/Antrag/Attachment/show/' . $antrag->dms_id) ?>"
target="_blank">
<i class="fa fa-paperclip" aria-hidden="true"></i> <?= $this->p->t('studierendenantrag', 'antrag_anhang'); ?>
</a>
<?php } ?>
</td>
<td>
<a
href="<?= site_url('lehre/Studierendenantrag/' .
strtolower($antrag->typ) .
'/' .
$antrag->prestudent_id .
'/' .
$antrag->studierendenantrag_id); ?>"
>
<i class="fa-solid fa-pen" title="<?= $this->p->t('studierendenantrag', 'btn_edit'); ?>"></i>
</a>
<?php
$allowed = [];
switch ($antrag->typ) {
case Studierendenantrag_model::TYP_ABMELDUNG:
$allowed = [
Studierendenantragstatus_model::STATUS_APPROVED
];
break;
case Studierendenantrag_model::TYP_ABMELDUNG_STGL:
$allowed = [
Studierendenantragstatus_model::STATUS_OBJECTION_DENIED,
Studierendenantragstatus_model::STATUS_DEREGISTERED
];
break;
case Studierendenantrag_model::TYP_UNTERBRECHUNG:
$allowed = [
Studierendenantragstatus_model::STATUS_APPROVED,
Studierendenantragstatus_model::STATUS_REMINDERSENT
];
break;
case Studierendenantrag_model::TYP_WIEDERHOLUNG:
$allowed = [
Studierendenantragstatus_model::STATUS_DEREGISTERED
];
break;
}
if (in_array($antrag->status, $allowed)) { ?>
<a
class="ms-2"
target="_blank"
href="<?= base_url('cis/private/pdfExport.php?xml=Antrag' .
$antrag->typ .
'.xml.php&xsl=Antrag' .
$antrag->typ .
'&id=' .
$antrag->studierendenantrag_id .
'&uid=' .
getAuthUID()); ?>"
>
<i
class="fa-solid fa-download"
title="<?= $this->p->t('studierendenantrag', 'btn_download_antrag'); ?>"
<?php } ?>
</td>
<td>
<?php if($antrag->dms_id) {?>
<a
class="text-decoration-none"
href="<?= site_url('lehre/Antrag/Attachment/show/' . $antrag->dms_id) ?>"
target="_blank">
<i class="fa fa-paperclip" aria-hidden="true"></i> <?= $this->p->t('studierendenantrag', 'antrag_anhang'); ?>
</a>
<?php } ?>
</td>
<td>
<a
href="<?= site_url('lehre/Studierendenantrag/' .
strtolower($antrag->typ) .
'/' .
$antrag->prestudent_id .
'/' .
$antrag->studierendenantrag_id); ?>"
>
</i>
</a>
<?php } ?>
<?php if ($antrag->typ == Studierendenantrag_model::TYP_WIEDERHOLUNG
&& $antrag->status == Studierendenantragstatus_model::STATUS_APPROVED
) { ?>
<a
class="ms-2"
href="#modallv<?= $antrag->studierendenantrag_id; ?>"
data-bs-toggle="modal"
>
<?= $this->p->t('studierendenantrag', 'btn_show_lvs'); ?>
</a>
<lv-popup
id="modallv<?= $antrag->studierendenantrag_id; ?>"
antrag-id = "<?= $antrag->studierendenantrag_id; ?>"
>
<?= $this->p->t('studierendenantrag', 'my_lvs'); ?>
</lv-popup>
<?php } ?>
</td>
</tr>
<i class="fa-solid fa-pen" title="<?= $this->p->t('studierendenantrag', 'btn_edit'); ?>"></i>
</a>
<?php
$allowed = [];
switch ($antrag->typ) {
case Studierendenantrag_model::TYP_ABMELDUNG:
$allowed = [
Studierendenantragstatus_model::STATUS_APPROVED
];
break;
case Studierendenantrag_model::TYP_ABMELDUNG_STGL:
$allowed = [
Studierendenantragstatus_model::STATUS_OBJECTION_DENIED,
Studierendenantragstatus_model::STATUS_DEREGISTERED
];
break;
case Studierendenantrag_model::TYP_UNTERBRECHUNG:
$allowed = [
Studierendenantragstatus_model::STATUS_APPROVED,
Studierendenantragstatus_model::STATUS_REMINDERSENT
];
break;
case Studierendenantrag_model::TYP_WIEDERHOLUNG:
$allowed = [
Studierendenantragstatus_model::STATUS_DEREGISTERED
];
break;
}
if (in_array($antrag->status, $allowed)) { ?>
<a
class="ms-2"
target="_blank"
href="<?= base_url('cis/private/pdfExport.php?xml=Antrag' .
$antrag->typ .
'.xml.php&xsl=Antrag' .
$antrag->typ .
'&id=' .
$antrag->studierendenantrag_id .
'&uid=' .
getAuthUID()); ?>"
>
<i
class="fa-solid fa-download"
title="<?= $this->p->t('studierendenantrag', 'btn_download_antrag'); ?>"
>
</i>
</a>
<?php } ?>
<?php if ($antrag->typ == Studierendenantrag_model::TYP_WIEDERHOLUNG
&& $antrag->status == Studierendenantragstatus_model::STATUS_APPROVED
) { ?>
<a
class="ms-2"
href="#modallv<?= $antrag->studierendenantrag_id; ?>"
data-bs-toggle="modal"
>
<?= $this->p->t('studierendenantrag', 'btn_show_lvs'); ?>
</a>
<lv-popup
id="modallv<?= $antrag->studierendenantrag_id; ?>"
antrag-id = "<?= $antrag->studierendenantrag_id; ?>"
>
<?= $this->p->t('studierendenantrag', 'my_lvs'); ?>
</lv-popup>
<?php } ?>
</td>
</tr>
<?php } ?>
<?php } ?>
</tbody>
</table>
-1
View File
@@ -2,7 +2,6 @@
@import './SvgIcons.css';
@import './components/searchbar/searchbar.css';
@import './components/verticalsplit.css';
@import './components/horizontalsplit.css';
@import './components/FilterComponent.css';
@import './components/Tabs.css';
@import './components/Notiz.css';
-75
View File
@@ -1,75 +0,0 @@
:root {
--fhc-horizontalsplit-hsplitter-bg-color: var(--fhc-background, #eee);
--fhc-horizontalsplit-hsplitter-border-color: var(--fhc-border, #eee);
--fhc-horizontalsplit-hsplitter-splitactions-color: var(--fhc-dark, #000);
}
.horizontalsplit-container {
display: flex;
flex-direction: row;
overflow: hidden;
max-height: 100%;
padding: 0px;
}
.horizontalsplitted {
overflow: auto;
flex-shrink: 0;
}
.horizontalsplitter {
flex-shrink: 0;
width: 16px;
cursor: col-resize;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
}
.horizontalsplitter.left {
border-left: solid 3px var(--fhc-horizontalsplit-hsplitter-border-color);
margin-right: 3px;
}
.horizontalsplitter.right {
border-right: solid 3px var(--fhc-horizontalsplit-hsplitter-border-color);
margin-left: 3px;
}
.splitactions.horizontal {
background-color: var(--fhc-horizontalsplit-hsplitter-bg-color);
color: var(--fhc-horizontalsplit-hsplitter-splitactions-color);
display: flex;
flex-direction: column;
padding: 5px 0 5px 0;
}
.splitactions.horizontal.left {
border-radius: 0 40% 40% 0;
}
.splitactions.horizontal.right {
border-radius: 40% 0 0 40%;
}
.splitactions.horizontal .splitaction {
width: 14px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
.splitactions.horizontal .splitaction.resize {
cursor: col-resize;
}
#content {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
#content > div:first-child {
margin-top: 30px;
}
+8 -1
View File
@@ -96,5 +96,12 @@ export default {
url: '/api/frontend/v1/studstatus/leitung/denyObjection',
params: antrag
};
}
},
terminate(antrag) {
return {
method: 'post',
url: '/api/frontend/v1/studstatus/leitung/terminateAntrag',
params: antrag
};
},
};
@@ -18,7 +18,11 @@ export default {
},
props: {
stgL: Array,
stgA: Array
stgA: Array,
permissionTerminateAntrag: {
type: Boolean,
default: false
},
},
data() {
return {
@@ -223,6 +227,13 @@ export default {
.then(this.showResults);
}
},
actionTerminate(evt){
var antraege = evt || this.selectedData;
this.$refs.loader.show();
this
._singleOrMultiApiCall(antraege, ApiStudstatusLeitung.terminate)
.then(this.showResults);
},
showResults(results) {
let fulfilled = results.filter(res => res.status == 'fulfilled');
this.$refs.loader.hide();
@@ -270,6 +281,7 @@ export default {
:stg-a="stgkzA"
:stg-l="stgkzL"
:filter="filter"
:permission-terminate-antrag="permissionTerminateAntrag"
v-model:columnData="columns"
v-model:selectedData="selectedData"
@action:approve="actionApprove"
@@ -280,7 +292,8 @@ export default {
@action:objectionApprove="actionObjectionApprove"
@action:cancel="actionCancel"
@action:pause="actionPause"
@action:unpause="actionUnpause"
@action:unpause="actionUnpause"
@action:terminate="actionTerminate"
@reload="reload"
>
</leitung-table>
@@ -16,7 +16,11 @@ export default {
columnData: Array,
stgL: Array,
stgA: Array,
filter: String
filter: String,
permissionTerminateAntrag: {
type: Boolean,
default: false
},
},
emits: [
'update:columnData',
@@ -29,7 +33,8 @@ export default {
'action:objectionApprove',
'action:cancel',
'action:pause',
'action:unpause'
'action:unpause',
'action:terminate'
],
data() {
return {
@@ -308,6 +313,26 @@ export default {
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.$emit('action:pause', [cell.getData()]));
container.append(button);
// Terminate
if(this.permissionTerminateAntrag){
button = document.createElement('button');
icon = document.createElement('i');
span = document.createElement('span');
icon.className = "fa-solid fa-xmark";
icon.setAttribute('aria-hidden', true);
icon.setAttribute('title', this.$p.t('studierendenantrag', 'btn_terminate'));
span.className = "fa-sr-only";
span.append(this.$p.t('studierendenantrag', 'btn_terminate'));
button.append(icon);
button.append(span);
button.className = "btn btn-outline-danger";
button.addEventListener('click', () => this.$emit('action:terminate', [cell.getData()]));
container.append(button);
}
}
let canUnpause = data.status == 'Pause' && !['AbmeldungStgl', 'Studienabbruch'].includes(data.status_insertvon);
@@ -337,6 +362,26 @@ export default {
button.className = "btn btn-outline-secondary";
button.addEventListener('click', () => this.$emit('action:unpause', [cell.getData()]));
container.append(button);
// Terminate: show buttion
if(this.permissionTerminateAntrag){
button = document.createElement('button');
icon = document.createElement('i');
span = document.createElement('span');
icon.className = "fa-solid fa-xmark";
icon.setAttribute('aria-hidden', true);
icon.setAttribute('title', this.$p.t('studierendenantrag', 'btn_terminate'));
span.className = "fa-sr-only";
span.append(this.$p.t('studierendenantrag', 'btn_terminate'));
button.append(icon);
button.append(span);
button.className = "btn btn-outline-danger";
button.addEventListener('click', () => this.$emit('action:terminate', [cell.getData()]));
container.append(button);
}
}
if (data.typ == 'AbmeldungStgl' && data.status == 'Genehmigt') {
+17 -30
View File
@@ -18,7 +18,6 @@
import CoreSearchbar from "../searchbar/searchbar.js";
import NavLanguage from "../navigation/Language.js";
import VerticalSplit from "../verticalsplit/verticalsplit.js";
import HorizontalSplit from "../horizontalsplit/horizontalsplit.js";
import AppMenu from "../AppMenu.js";
import AppConfig from "../AppConfig.js";
import StvVerband from "./Studentenverwaltung/Verband.js";
@@ -38,7 +37,6 @@ export default {
CoreSearchbar,
NavLanguage,
VerticalSplit,
HorizontalSplit,
AppMenu,
AppConfig,
StvVerband,
@@ -237,10 +235,6 @@ export default {
}
}
}
},
sidebarCollapsed(newVal) {
if(newVal) this.$refs.hSplit.collapseLeft()
else this.$refs.hSplit.showBoth()
}
},
methods: {
@@ -638,30 +632,23 @@ export default {
</app-menu>
</div>
</aside>
<horizontal-split ref="hSplit" :defaultRatio="[15, 85]">
<template #left>
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 w-100">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<stv-verband :preselectedKey="studiengangKz ? '' + studiengangKz : null" :endpoint="verbandEndpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
<stv-studiensemester v-model:studiensemester-kurzbz="studiensemesterKurzbz" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
</nav>
</template>
<template #right>
<main>
<vertical-split :defaultRatio="[50, 50]">
<template #top>
<stv-list ref="stvList" v-model:selected="selected" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz" @filterActive="handleCustomFilter"></stv-list>
</template>
<template #bottom>
<stv-details ref="details" :students="selected" @reload="reloadList"></stv-details>
</template>
</vertical-split>
</main>
</template>
</horizontal-split>
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<stv-verband :preselectedKey="studiengangKz ? '' + studiengangKz : null" :endpoint="verbandEndpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
<stv-studiensemester v-model:studiensemester-kurzbz="studiensemesterKurzbz" @update:studiensemester-kurzbz="studiensemesterChanged"></stv-studiensemester>
</nav>
<main class="col-md-8 ms-sm-auto col-lg-9 col-xl-10">
<vertical-split>
<template #top>
<stv-list ref="stvList" v-model:selected="selected" :studiengang-kz="studiengangKz" :studiensemester-kurzbz="studiensemesterKurzbz" @filterActive="handleCustomFilter"></stv-list>
</template>
<template #bottom>
<stv-details ref="details" :students="selected" @reload="reloadList"></stv-details>
</template>
</vertical-split>
</main>
</div>
</div>
<app-config ref="config" v-model="appconfig" :endpoints="configEndpoints"></app-config>
@@ -1,147 +0,0 @@
export default {
name: 'HorizontalSplit',
props: {
defaultRatio: {
type: Array,
default: () => [50, 50]
}
},
data: function () {
return {
availWidth: 0,
leftwidth: 0,
rightwidth: 0,
mousePosX: 0,
resize: false,
hsplitterOffset: 0,
selfOffsetLeft: 0
};
},
template: `
<div ref="horizontalsplit" class="horizontalsplit-container">
<div ref="leftpanel" class="horizontalsplitted"
:style="{ width: this.leftwidthcss }">
<slot name="left">
<p>Left Panel</p>
</slot>
</div>
<div ref="hsplitter" class="horizontalsplitter"
:class="this.leftOrRightClass" @mousedown="this.dragStart">
<div class="splitactions horizontal" :class="this.leftOrRightClass">
<span @click="this.collapseRight" class="splitaction">
<i class="fas fa-angle-right text-muted"></i>
</span>
<span @dblclick="this.showBoth" class="splitaction resize">
<i class="fas fa-grip-lines-vertical text-muted"></i>
</span>
<span @click="this.collapseLeft" class="splitaction">
<i class="fas fa-angle-left text-muted"></i>
</span>
</div>
</div>
<div ref="rightpanel" class="horizontalsplitted"
:style="{ width: this.rightwidthcss }">
<slot name="right">
<p/>
</slot>
</div>
</div>
`,
mounted: function () {
this.calcWidths();
this.trackHorizontalSplitterOffsetLeft();
window.addEventListener('resize', this.calcWidths);
},
updated: function () {
this.trackHorizontalSplitterOffsetLeft();
},
beforeDestroy: function () {
window.removeEventListener('resize', this.calcWidths);
},
methods: {
calcWidths: function () {
var oldavailWidth = this.availWidth;
this.selfOffsetLeft = this.$refs.horizontalsplit.offsetLeft;
this.availWidth = this.$refs.horizontalsplit.offsetWidth - this.$refs.hsplitter.offsetWidth;
if ((this.leftwidth === 0 && this.rightwidth === 0) || oldavailWidth === 0) {
this.leftwidth = Math.floor(this.availWidth * (this.defaultRatio[0] / 100));
} else {
this.leftwidth = Math.floor(((this.leftwidth * 100) / oldavailWidth) / 100 * this.availWidth);
}
this.rightwidth = this.availWidth - this.leftwidth;
},
collapseLeft: function () {
this.calcWidths();
this.leftwidth = 0;
this.rightwidth = this.availWidth;
},
collapseRight: function () {
this.calcWidths();
this.leftwidth = this.availWidth;
this.rightwidth = 0;
},
showBoth: function () {
this.leftwidth = Math.floor(this.availWidth * (this.defaultRatio[0] / 100));
this.rightwidth = this.availWidth - this.leftwidth;
},
isCollapsed: function () {
if (this.leftwidth === 0) {
return 'left';
} else if (this.rightwidth === 0) {
return 'right';
} else {
return false;
}
},
dragStart: function (e) {
e.preventDefault();
e.stopPropagation();
window.addEventListener('mouseup', this.dragEnd);
window.addEventListener('mousemove', this.drag);
this.resize = true;
this.mousePosX = e.clientX;
},
drag: function (e) {
if (!this.resize) {
return;
}
e.preventDefault();
e.stopPropagation();
var offsetX = e.clientX - this.mousePosX;
this.leftwidth = this.leftwidth + offsetX;
if (this.leftwidth < 0) {
this.leftwidth = 0;
}
if (this.leftwidth > this.availWidth) {
this.leftwidth = this.availWidth;
}
this.rightwidth = this.availWidth - this.leftwidth;
this.mousePosX = e.clientX;
},
dragEnd: function (e) {
e.preventDefault();
e.stopPropagation();
window.removeEventListener('mousemove', this.drag);
window.removeEventListener('mouseup', this.dragEnd);
this.resize = false;
this.mousePosX = e.clientX;
},
trackHorizontalSplitterOffsetLeft: function () {
this.hsplitterOffset = this.$refs.hsplitter.offsetLeft;
}
},
computed: {
leftOrRightClass: function () {
return ((this.hsplitterOffset - this.selfOffsetLeft) <= Math.floor(this.availWidth / 2))
? 'left'
: 'right';
},
leftwidthcss: function () {
return this.leftwidth + 'px';
},
rightwidthcss: function () {
return this.rightwidth + 'px';
}
}
};
@@ -1,11 +1,5 @@
export default {
name: 'VerticalSplit',
props: {
defaultRatio: {
type: Array,
default: () => [50, 50]
}
},
data: function() {
return {
availHeight: 0,
@@ -56,22 +50,17 @@ export default {
updated: function() {
this.trackVerticalSplitterOffsetTop();
},
beforeDestroy: function () {
window.removeEventListener('resize', this.calcHeights);
},
methods: {
calcHeights: function() {
var windowheight = window.innerHeight;
var oldavailHeight = this.availHeight;
this.selfOffsetTop = this.$refs.verticalsplit.offsetTop;
this.availHeight = windowheight - this.selfOffsetTop - this.$refs.vsplitter.offsetHeight;
if( (this.topheight === 0 && this.bottomheight === 0) || oldavailHeight === 0 ) {
this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100));
this.topheight = Math.floor(this.availHeight/2);
} else {
this.topheight = Math.floor( ((((this.topheight * 100) / oldavailHeight) / 100) * this.availHeight) );
}
this.bottomheight = this.availHeight - this.topheight;
},
collapseTop: function() {
@@ -85,8 +74,8 @@ export default {
this.bottomheight = 0;
},
showBoth: function() {
this.topheight = Math.floor(this.availHeight * (this.defaultRatio[0] / 100));
this.bottomheight = this.availHeight - this.topheight
this.topheight = Math.floor(this.availHeight/2);
this.bottomheight = Math.floor(this.availHeight/2);
},
isCollapsed: function() {
if( this.topheight === 0 ) {
+1
View File
@@ -94,6 +94,7 @@ require_once('dbupdate_3.4/71399_dashboard_update_widget_paths.php');
require_once('dbupdate_3.4/71645_studvw_messagetab_ladezeit.php');
require_once('dbupdate_3.4/71566_studienordnungsdokument_neuer_organisationseinheitstyp_programm.php');
require_once('dbupdate_3.4/70376_lohnguide.php');
require_once('dbupdate_3.4/77000_studstatus_stornierte_wiederholende.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
+2 -2
View File
@@ -264,8 +264,8 @@ CREATE TABLE IF NOT EXISTS hr.tbl_vertragsbestandteil_lohnguide (
stellenbezeichnung varchar(255),
fachrichtung_kurzbz character varying(32) NOT NULL,
modellstelle_kurzbz character varying(32) NOT NULL,
kommentar_person varchar(255),
kommentar_modellstelle varchar(255),
kommentar_person text,
kommentar_modellstelle text,
CONSTRAINT tbl_vertragsbestandteil_lohnguide_pk PRIMARY KEY (vertragsbestandteil_id),
CONSTRAINT tbl_vertragsbestandteil_fk FOREIGN KEY (vertragsbestandteil_id) REFERENCES hr.tbl_vertragsbestandteil (vertragsbestandteil_id) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT tbl_vertragsbestandteil_lohnguide_fachrichtung_fk FOREIGN KEY (fachrichtung_kurzbz) REFERENCES hr.tbl_lohnguide_fachrichtung (fachrichtung_kurzbz) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE,
@@ -0,0 +1,15 @@
<?php
// neuen studierendenantrag_statustyp mit studierendenantrag_statustyp_kurzbz = 'Storniert' als Zeile hinzufügen
if($result = @$db->db_query("SELECT 1 FROM campus.tbl_studierendenantrag_statustyp WHERE studierendenantrag_statustyp_kurzbz= 'Storniert';"))
{
if($db->db_num_rows($result) == 0)
{
$qry = "INSERT INTO campus.tbl_studierendenantrag_statustyp(studierendenantrag_statustyp_kurzbz, bezeichnung) VALUES ('Storniert', '{Storniert, Terminated}');";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_studierendenantrag_statustyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>campus.tbl_studierendenantrag_statustyp: Zeile Storniert hinzugefuegt!<br>';
}
}
+60
View File
@@ -58177,6 +58177,66 @@ I have been informed that I am under no obligation to consent to the transmissio
)
),
// ### Phrases Dashboard Admin END
array(
'app' => 'core',
'category' => 'studierendenantrag',
'phrase' => 'status_terminated',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Storniert',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Terminated',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'studierendenantrag',
'phrase' => 'btn_terminate',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Stornieren',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Terminate',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'studierendenantrag',
'phrase' => 'error_not_terminated',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Id {id} ist bereits storniert',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Id {id} is already terminated',
'description' => '',
'insertvon' => 'system'
)
)
),
);