Compare commits

...
Author SHA1 Message Date
Johann Hoffmann c2c317ce33 stv/lvvw noten tab punkte editor, punkte work in noten col and dropdown aswell; 2026-10-21 12:06:16 +02:00
Harald Bamberger e68634cf6f Merge branch 'feature-78270/StudVW_Abschlusspruefung_Eingabemodal_Uhrzeit_konfigurierbar' 2026-10-20 08:20:31 +02:00
Harald Bamberger e3ed6066c2 configure model-type on field uhrzeit timepicker and remove obsolete checkfunction, add translations for timepicker buttons, add uhrzeit to localstorage 2026-10-20 07:54:08 +02:00
Harald Bamberger 39ea868f08 Merge branch 'master' into feature-78270/StudVW_Abschlusspruefung_Eingabemodal_Uhrzeit_konfigurierbar 2026-10-16 15:59:05 +02:00
Harald Bamberger b2f3b5a3bc Merge branch 'bugfix-84655-bootstrap-modal-dispose-error' 2026-10-13 18:40:47 +02:00
adisposkofh 864b56e62c resolved error with modal dispose; removed dispose, issue with bubbling hide events in nested modals 2026-10-13 14:19:40 +02:00
Harald Bamberger 424ed0c5bb do not load Model from Extension in Core 2026-10-13 12:32:49 +02:00
Harald Bamberger b0f1572576 Merge branch 'cis40_2026-09_ma_rc' 2026-10-13 11:10:32 +02:00
Harald Bamberger 03efc55c93 remove checked in symlink that is generated by composer 2026-10-13 10:37:25 +02:00
Harald Bamberger 95c4dc2734 use Compat component for gesamtnote 2026-10-13 10:26:19 +02:00
Harald Bamberger 94c019888a use legacy link for gesamtnote for the moment 2026-10-13 09:50:04 +02:00
Harald Bamberger ed83b5e617 zeitsperren: add Mailphrases, mailstatus handling backend and frontend 2026-10-13 07:56:59 +02:00
Harald Bamberger 510ce22cc9 Merge branch 'cis40_2026-09_ma_rc_bugfix-83851-cms-content-table-formatting' into cis40_2026-09_ma_rc 2026-10-12 17:54:07 +02:00
Harald Bamberger 53049504a7 modify Zeitsperren API Endpoints to send sancho Mails, modify update check so that a zeitsperre does not block an update on itself when overlapping, add phrases for sancho mail subject to ci phrase system 2026-10-12 17:13:52 +02:00
Harald Bamberger 3e06740758 add permissions cis/eigene_zeitsperren and cis/andere_zeitsperren and use them for zeitsperren and zeitsperrenma applications and api endpoints 2026-10-12 14:25:31 +02:00
adisposkofh 73d22e14e5 cms content tabulator formatting fix 2026-10-12 12:39:43 +02:00
Harald Bamberger bca2884aa2 CisMenuApp fetch Authinfo, Cis search use isMitarbeiter flag to conditinally render Zeitsperren Button in search result 2026-10-12 11:37:45 +02:00
Harald Bamberger 63d0bfc69b Merge branch 'cis40_2026-09_ma_rc_bugfix-84008-lvplan-with-lvid-shows-reservations' into cis40_2026-09_ma_rc 2026-10-12 10:30:29 +02:00
Harald Bamberger ab05f32fde Merge branch 'cis40_2026-09_ma_rc' into cis40_2026-09_ma_rc_bugfix-84008-lvplan-with-lvid-shows-reservations 2026-10-12 10:21:03 +02:00
adisposkofh cdd36f61b1 in lv plan not fetching reservations if lv id query param is present 2026-10-09 12:10:32 +02:00
ma0068 756b9b995d add new Formfield uhrzeit in Tab Abschlusspruefung 2026-08-12 17:34:05 +02:00
28 changed files with 563 additions and 58 deletions
+3
View File
@@ -74,6 +74,9 @@ $config['tabs'] =
],
],
],
'hiddenFields' => [
'uhrzeit'
]
],
'exemptions' => [
//if true, Anrechnungen can be added and edited in tab Anrechnungen
+1 -1
View File
@@ -7,7 +7,7 @@ class Zeitsperren extends Auth_Controller
public function __construct()
{
parent::__construct([
'index' => ['basis/cis:r'],
'index' => ['cis/eigene_zeitsperren:r'],
]);
// Load Libraries
@@ -13,7 +13,7 @@ class Zeitsperrenma extends Auth_Controller
public function __construct()
{
parent::__construct([
'index' => ['basis/cis:r']
'index' => ['cis/andere_zeitsperren:r']
]);
}
@@ -379,7 +379,8 @@ class LvMenu extends FHCAPI_Controller
'name'=>$this->p->t('lehre', 'gesamtnote'),
'phrase' => 'lehre/gesamtnote',
'c4_icon'=> base_url('skin/images/button_endnote.png'),
'c4_link'=> site_url('Cis/Benotungstool/'.urlencode($lvid).'/'.urlencode($angezeigtes_stsem))
'c4_link'=> base_url('cis.php/Cis/Compat/legacy/cis/private/lehre/benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem))
//'c4_link'=> site_url('Cis/Benotungstool/'.urlencode($lvid).'/'.urlencode($angezeigtes_stsem))
//'c4_link'=> base_url('cis/private/lehre/benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem))
//'c4_link'=> base_url('benotungstool/lvgesamtnoteverwalten.php?lvid='.urlencode($lvid).'&stsem='.urlencode($angezeigtes_stsem))
);
@@ -7,14 +7,14 @@ class MaZeitsperren extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
'getAllActiveZeitsperren' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'getAllZeitsperrenFixeMa' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'getAllZeitsperrenLector' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'getAllZeitsperrenOes' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'getZeitsperrenAss' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'getAllActiveZeitsperren' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
'getAllZeitsperrenFixeMa' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
'getAllZeitsperrenLector' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
'getAllZeitsperrenOes' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
'getZeitsperrenAss' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
'getStgLectors' => self::PERM_LOGGED,
'loadZeitsperrenLectorStg' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'loadZeitsperrenMa' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'loadZeitsperrenLectorStg' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
'loadZeitsperrenMa' => ['cis/andere_zeitsperren:r', 'admin:r', 'assistenz:r'],
'getDetailsMa' => self::PERM_LOGGED
]);
@@ -7,14 +7,14 @@ class Zeitsperren extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
'getZeitsperrenUser' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'getZeitsperrenUser' => ['cis/eigene_zeitsperren:r', 'admin:r', 'assistenz:r'],
'getTypenZeitsperren' => self::PERM_LOGGED,
'getTypenErreichbarkeit' => self::PERM_LOGGED,
'getStunden' => self::PERM_LOGGED,
'loadZeitsperre' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'add' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'update' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'delete' => ['basis/mitarbeiter:r', 'admin:r', 'assistenz:r'],
'loadZeitsperre' => ['cis/eigene_zeitsperren:r', 'admin:r', 'assistenz:r'],
'add' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'],
'update' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'],
'delete' => ['cis/eigene_zeitsperren:rw', 'admin:rw', 'assistenz:rw'],
]);
// Load Libraries
@@ -179,6 +179,21 @@ class Zeitsperren extends FHCAPI_Controller
);
$data = $this->getDataOrTerminateWithError($result);
if($zeitsperretyp_kurzbz == 'ZA')
{
$this->sendSanchoMailToVorgesetzten(
'Sancho_Content_Zeitausgleich',
array(
'beschreibung' =>$zeitsperretyp_kurzbz,
'vonDatum' => (new DateTime($vondatum))->format('d.m.Y'),
'bisDatum' => (new DateTime($bisdatum))->format('d.m.Y'),
'Saldo'=> ''
),
$this->p->t('zeitsperren', 'zeitausgleichNeu'),
$mitarbeiter_uid
);
}
$this->terminateWithSuccess($data);
}
@@ -285,9 +300,13 @@ class Zeitsperren extends FHCAPI_Controller
$zeitsperretyp_kurzbz = array_key_exists('zeitsperretyp_kurzbz', $post) ? $update['zeitsperretyp_kurzbz'] : $data->zeitsperretyp_kurzbz;
$result = $this->ZeitsperreModel->getSperreByDate($mitarbeiter_uid, $vondatum, $vonstunde, true);
$data = $this->getDataOrTerminateWithError($result);
$data = (array) $this->getDataOrTerminateWithError($result);
if(hasData($result))
$data = array_filter($data, function($zeitsperre) use ($zeitsperre_id) {
return $zeitsperre->zeitsperre_id != $zeitsperre_id;
});
if(count($data) > 0)
{
$this->terminateWithError($this->p->t('zeitsperren', 'error_existingZeitsperre', ['typ'=> current($data)->zeitsperretyp_kurzbz]), self::ERROR_TYPE_GENERAL);
}
@@ -309,6 +328,21 @@ class Zeitsperren extends FHCAPI_Controller
$data = $this->getDataOrTerminateWithError($result);
if($zeitsperretyp_kurzbz == 'ZA')
{
$this->sendSanchoMailToVorgesetzten(
'Sancho_Content_ZA_Aendern',
array(
'beschreibung' =>$zeitsperretyp_kurzbz,
'vonDatum' => (new DateTime($vondatum))->format('d.m.Y'),
'bisDatum' => (new DateTime($bisdatum))->format('d.m.Y'),
'Saldo'=> ''
),
$this->p->t('zeitsperren', 'zeitausgleichGeaendert'),
$mitarbeiter_uid
);
}
$this->terminateWithSuccess($data);
}
else
@@ -327,7 +361,8 @@ class Zeitsperren extends FHCAPI_Controller
$loggedInUser = getAuthUID();
$result = $this->ZeitsperreModel->load($zeitsperre_id);
$data = $this->getDataOrTerminateWithError($result);
$uid = current($data)->mitarbeiter_uid;
$zeitsperre = (is_array($data) && count($data) > 0) ? current($data) : null;
$uid = (!is_null($zeitsperre)) ? $zeitsperre->mitarbeiter_uid : '';
if($uid != $loggedInUser)
$this->terminateWithError($this->p->t('ui', 'noPermission'), self::ERROR_TYPE_GENERAL);
@@ -339,6 +374,21 @@ class Zeitsperren extends FHCAPI_Controller
if (isError($result)) {
$this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
}
if($zeitsperre->zeitsperretyp_kurzbz == 'ZA')
{
$res = $this->sendSanchoMailToVorgesetzten(
'Sancho_Mail_ZA_loeschen',
array(
'beschreibung' =>$zeitsperre->zeitsperretyp_kurzbz,
'vonDatum' => (new DateTime($zeitsperre->vondatum))->format('d.m.Y'),
'bisDatum' => (new DateTime($zeitsperre->bisdatum))->format('d.m.Y')
),
$this->p->t('zeitsperren', 'zeitausgleichGeloescht'),
$zeitsperre->mitarbeiter_uid
);
}
$this->terminateWithSuccess((getData($result) ?: []));
}
@@ -363,5 +413,57 @@ class Zeitsperren extends FHCAPI_Controller
return $tage <= 14;
}
protected function sendSanchoMailToVorgesetzten($sancho_vorlage_kurzbz,
$sancho_vorlage_data, $subject, $mauid)
{
$this->load->helper('hlp_sancho_helper');
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
$this->load->model('person/Person_model', 'PersonModel');
$fullname = getData($this->PersonModel->getFullName($mauid));
$vorgesetzte = (array) getData($this->MitarbeiterModel->getVorgesetzte($mauid));
$to = array();
$vorgesetzte_namen = array();
$mailsent = false;
$msg = $this->p->t('zeitsperren', 'konnteKeinFreigabemailVersendetWerden');
if(count($vorgesetzte) > 0)
{
foreach($vorgesetzte as $vg)
{
$to[] = $vg->vorgesetzter .'@' . DOMAIN;
$vorgesetzter_fullname = getData($this->PersonModel->getFullName($vg->vorgesetzter));
$vorgesetzte_namen[] = $vorgesetzter_fullname;
}
$sancho_vorlage_data['vorgesetzter'] = implode(', ', $vorgesetzte_namen);
$sancho_vorlage_data['nameMitarbeiter'] = $fullname;
$mailsent = sendSanchoMail(
$sancho_vorlage_kurzbz,
$sancho_vorlage_data,
implode(',', $to),
$subject
);
if($mailsent)
{
$msg = $this->p->t('zeitsperren', 'VorgesetzteInformiert',
array('vorgesetzte' => $sancho_vorlage_data['vorgesetzter']));
}
else
{
$msg = $this->p->t('zeitsperren', 'fehlerBeimSendenAufgetreten',
array('vorgesetzte' => $sancho_vorlage_data['vorgesetzter']));
}
}
$res = (object) array(
'mailsent' => $mailsent,
'msg' => $msg
);
$this->addMeta('mailstatus', $res);
}
}
@@ -32,7 +32,6 @@ class Funktionen extends FHCAPI_Controller
]);
// Load models
$this->load->model('extensions/FHC-Core-Personalverwaltung/Api_model', 'ApiModel');
$this->load->model('ressource/Funktion_model', 'FunktionModel');
$this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
@@ -315,6 +315,7 @@ class Abschlusspruefung extends FHCAPI_Controller
'abschlussbeurteilung_kurzbz' => $formData['abschlussbeurteilung_kurzbz'],
'datum' => $formData['datum'], //TODO(Manu) check if minute format like FAS
'sponsion' => $formData['sponsion'],
'uhrzeit' => $formData['uhrzeit'],
'pruefer1' => $formData['pruefer1'],
'pruefer2' => $formData['pruefer2'],
'pruefer3' => $formData['pruefer3'],
@@ -387,6 +388,7 @@ class Abschlusspruefung extends FHCAPI_Controller
'abschlussbeurteilung_kurzbz' => $formData['abschlussbeurteilung_kurzbz'],
'datum' => $datum,
'sponsion' => $sponsion,
'uhrzeit' => $formData['uhrzeit'],
'pruefer1' => $pruefer1,
'pruefer2' => $pruefer2,
'pruefer3' => $pruefer3,
@@ -184,10 +184,17 @@ class Grades extends FHCAPI_Controller
{
$this->load->library('form_validation');
// a Note without Punkte clears old Punkte, else Note and Punkte contradict
$punkte = $this->input->post('punkte');
if (!(defined('CIS_GESAMTNOTE_PUNKTE') && CIS_GESAMTNOTE_PUNKTE) || trim((string) $punkte) === '')
$punkte = null;
$this->form_validation->set_rules("lehrveranstaltung_id", $this->p->t('lehre', 'lehrveranstaltung'), "required|integer");
$this->form_validation->set_rules("student_uid", $this->p->t('person', 'student'), "required");
$this->form_validation->set_rules("studiensemester_kurzbz", $this->p->t('lehre', 'studiensemester'), "required");
$this->form_validation->set_rules('note', $this->p->t('lehre', 'note'), 'required|numeric');
// with Punkte the Notenschluessel gives the Note
$this->form_validation->set_rules('note', $this->p->t('lehre', 'note'), $punkte === null ? 'required|numeric' : 'numeric');
$this->form_validation->set_rules('punkte', $this->p->t('stv', 'grades_points'), 'numeric|greater_than_equal_to[0]|less_than[10000]');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
@@ -206,6 +213,16 @@ class Grades extends FHCAPI_Controller
$this->load->model('education/Zeugnisnote_model', 'ZeugnisnoteModel');
// the Notenschluessel of the LV decides the Note for the Punkte
if ($punkte !== null) {
$this->load->model('education/Notenschluesselaufteilung_model', 'NotenschluesselaufteilungModel');
$note = $this->getDataOrTerminateWithError(
$this->NotenschluesselaufteilungModel->getNote($punkte, $lehrveranstaltung_id, $studiensemester_kurzbz)
);
if ($note === null)
$this->terminateWithValidationErrors(['punkte' => $this->p->t('stv', 'grades_error_noGradeForPoints')]);
}
$result = $this->ZeugnisnoteModel->load([
'studiensemester_kurzbz' => $studiensemester_kurzbz,
'student_uid' => $student_uid,
@@ -220,6 +237,7 @@ class Grades extends FHCAPI_Controller
'lehrveranstaltung_id' => $lehrveranstaltung_id
], [
'note' => $note,
'punkte' => $punkte,
'benotungsdatum' => $now,
'updateamum' => $now,
'updatevon' => $authUID
@@ -230,6 +248,7 @@ class Grades extends FHCAPI_Controller
'student_uid' => $student_uid,
'lehrveranstaltung_id' => $lehrveranstaltung_id,
'note' => $note,
'punkte' => $punkte,
'benotungsdatum' => $now,
'insertamum' => $now,
'insertvon' => $authUID
@@ -237,7 +256,8 @@ class Grades extends FHCAPI_Controller
}
$this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess(true);
// the client shows the Note that the server saved
$this->terminateWithSuccess(['note' => $note, 'punkte' => $punkte]);
}
/**
@@ -26,8 +26,13 @@ class Notenschluesselzuordnung_model extends DB_Model
$this->db->where("lehrveranstaltung_id", $lehrveranstaltung_id);
if ($studiensemester_kurzbz) {
// without the group the OR matches each row without a semester, of any LV
$this->db->group_start();
$this->db->where("studiensemester_kurzbz", $studiensemester_kurzbz);
$this->db->or_where("studiensemester_kurzbz", null);
$this->db->group_end();
// the row of the semester before the row without a semester (NULLS LAST)
$this->addOrder("studiensemester_kurzbz", "ASC");
} else {
$this->db->where("studiensemester_kurzbz", null);
}
@@ -65,7 +70,12 @@ class Notenschluesselzuordnung_model extends DB_Model
$this->addLimit(1);
if ($studiensemester_kurzbz) {
$this->db->where_in("studiensemester_kurzbz", [$studiensemester_kurzbz, null]);
// IN ('X', NULL) never matches NULL
$this->db->group_start();
$this->db->where("studiensemester_kurzbz", $studiensemester_kurzbz);
$this->db->or_where("studiensemester_kurzbz", null);
$this->db->group_end();
$this->addOrder("studiensemester_kurzbz", "ASC");
$result = $this->load();
} else {
$result = $this->loadWhere([
+3 -2
View File
@@ -52,7 +52,7 @@ export default {
url: url
};
},
updateCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note, lehrveranstaltung_bezeichnung}) {
updateCertificate({lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note, punkte = null, lehrveranstaltung_bezeichnung}) {
return {
method: 'post',
url: 'api/frontend/v1/stv/grades/updateCertificate',
@@ -60,7 +60,8 @@ export default {
lehrveranstaltung_id,
student_uid,
studiensemester_kurzbz,
note
note,
punkte
}
};
},
+11 -1
View File
@@ -2,6 +2,8 @@ import CisMenu from "../../components/Cis/Menu.js";
import PluginsPhrasen from '../../plugins/Phrasen.js';
import Theme from "../../plugins/Theme.js";
import ApiAuthinfo from "../../api/factory/authinfo.js";
const app = Vue.createApp({
name: 'CisMenuApp',
components: {
@@ -9,11 +11,15 @@ const app = Vue.createApp({
},
data: () => ({
windowWidth: 0,
isStudent: null,
isMitarbeiter: null,
}),
provide() {
return {
isNarrow: Vue.computed(() => this.windowWidth < 992),
isMobile: Vue.computed(() => this.windowWidth < 767),
isStudent: Vue.computed(() => this.isStudent),
isMitarbeiter: Vue.computed(() => this.isMitarbeiter)
}
},
methods: {
@@ -21,8 +27,12 @@ const app = Vue.createApp({
this.windowWidth = window.innerWidth;
},
},
created() {
async created() {
this.windowWidth = window.innerWidth;
await this.$api.call(ApiAuthinfo.getAuthInfo()).then((res) => {
this.isMitarbeiter = res.data.isMitarbeiter;
this.isStudent = res.data.isStudent;
});
},
mounted() {
window.addEventListener("resize", this.handleWindowResize);
+11 -8
View File
@@ -103,13 +103,6 @@ export default {
keyboard: this.keyboard
});
},
//to avoid pagelocks if more than one instance
beforeUnmount() {
if (this.modal) {
this.modal.dispose();
this.modal = null;
}
},
popup(body, options, title, footer) {
const BsModal = this,
slots = {};
@@ -164,7 +157,17 @@ export default {
});
});
},
template: `<div ref="modal" class="bootstrap-modal modal" tabindex="-1" @[\`hide.bs.modal\`]="$emit('hideBsModal')" @[\`hidden.bs.modal\`]="$emit('hiddenBsModal')" @[\`hidePrevented.bs.modal\`]="$emit('hidePreventedBsModal')" @[\`show.bs.modal\`]="$emit('showBsModal')" @[\`shown.bs.modal\`]="$emit('shownBsModal')">
template: `
<div
ref="modal"
class="bootstrap-modal modal"
tabindex="-1"
@[\`hide.bs.modal\`]="$emit('hideBsModal', $event)"
@[\`hidden.bs.modal\`]="$emit('hiddenBsModal', $event)"
@[\`hidePrevented.bs.modal\`]="$emit('hidePreventedBsModal', $event)"
@[\`show.bs.modal\`]="$emit('showBsModal', $event)"
@[\`shown.bs.modal\`]="$emit('shownBsModal', $event)"
>
<div class="modal-dialog" :class="fullscreen ? 'modal-fullscreen' : dialogClass">
<div class="modal-content">
<div v-if="$slots.title" class="modal-header" :class="headerClass">
@@ -67,7 +67,7 @@ export default {
new Tabulator(table, {
index: index,
layout: "fitDataFill",
layout: "fitColumns",
columnDefaults: {
formatter: "html",
@@ -68,7 +68,7 @@ export default {
new Tabulator(table, {
index: index,
layout: "fitDataFill",
layout: "fitColumns",
columnDefaults: {
formatter: "html",
@@ -74,7 +74,7 @@ export default {
for (let table of tables) {
this.sanitizeLegacyTables(table)
new Tabulator(table, {
layout: "fitDataStretch",
layout: "fitColumns",
columnDefaults: {
formatter: "html",
@@ -83,10 +83,26 @@ export default {
});
},
getPromiseFunc(start, end) {
return [
this.$api.call(ApiLvPlan.eventsLv(this.propsViewData.lv_id, start.toISODate(), end.toISODate())),
this.$api.call(ApiLvPlan.getLvPlanReservierungen(start.toISODate(), end.toISODate()))
let functions = [
this.$api.call(
ApiLvPlan.eventsLv(
this.propsViewData.lv_id,
start.toISODate(),
end.toISODate(),
),
),
];
if (!this.$props.propsViewData.lv_id?.length) {
functions.push(
this.$api.call(
ApiLvPlan.getLvPlanReservierungen(
start.toISODate(),
end.toISODate(),
),
),
);
}
return functions;
}
},
created() {
+5 -1
View File
@@ -65,6 +65,10 @@ export default {
label: Vue.computed(() => this.$p.t("profil/zeitsperren")),
icon: "fas fa-calendar-days",
type: "link",
isMitarbeiter: Vue.computed(() => this.isMitarbeiter),
renderif: function() {
return this.isMitarbeiter;
},
action: function (data) {
const uid = JSON.parse(data.data).uid;
const link =
@@ -163,7 +167,7 @@ export default {
openMenuHierarchy: [],
};
},
inject: ["isNarrow", "isMobile"],
inject: ["isNarrow", "isMobile", "isMitarbeiter", "isStudent"],
provide(){
return{
setActiveEntry: this.setActiveEntry,
@@ -91,6 +91,15 @@ export default {
.then(() => this.deleteZeitsperre(zeitsperre_id))
.catch(this.$fhcAlert.handleSystemError);
},
alertMailstatus(response) {
if(response?.meta?.mailstatus) {
if(response.meta.mailstatus.mailsent) {
this.$fhcAlert.alertInfo(response.meta.mailstatus.msg);
} else {
this.$fhcAlert.alertWarning(response.meta.mailstatus.msg);
}
}
},
saveZeitsperre(zeitsperreId = null) {
const isNew = !zeitsperreId;
@@ -109,8 +118,9 @@ export default {
return this.$refs.dataZeitsperre
.call(request)
.then(() => {
.then((response) => {
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
this.alertMailstatus(response);
this.reset();
this.reload();
@@ -122,6 +132,7 @@ export default {
.call(ApiTimelocks.deleteZeitsperre(zeitsperre_id))
.then(response => {
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete'));
this.alertMailstatus(response);
})
.catch(this.$fhcAlert.handleSystemError)
.finally(()=> {
@@ -56,7 +56,8 @@ export default {
anmerkung: null,
protokoll: null,
note: null,
link: null
link: null,
uhrzeit: null
},
statusNew: true,
arrTypen: [],
@@ -310,6 +311,7 @@ export default {
this.formData.pruefungstyp_kurzbz = data.pruefungstyp_kurzbz;
this.formData.datum = data.datum;
this.formData.uhrzeit = data.uhrzeit;
this.formData.sponsion = data.sponsion;
this.formData.akadgrad_id = data.akadgrad_id;
this.formData.pruefungsantritt_kurzbz = data.pruefungsantritt_kurzbz;
@@ -411,6 +413,7 @@ export default {
pruefer3_label: this.selectedPruefer3?.label || null,
akadgrad_id: this.formData.akadgrad_id,
datum: this.formData.datum,
uhrzeit: this.formData.uhrzeit || null,
sponsion: this.formData.sponsion
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
@@ -527,6 +530,7 @@ export default {
this.formData.abschlussbeurteilung_kurzbz = null;
this.formData.datum = null;
this.formData.sponsion = null;
this.formData.uhrzeit = null;
this.formData.pruefer1 = null;
this.formData.pruefer2 = null;
this.formData.pruefer3 = null;
@@ -620,7 +624,7 @@ export default {
);
}
});
},
}
},
created() {
this.$api
@@ -715,7 +719,6 @@ export default {
</template>
<form-form ref="formFinalExam" @submit.prevent>
<legend>{{this.$p.t('global','details')}}</legend>
<p v-if="statusNew">[{{$p.t('ui', 'neu')}}]</p>
@@ -916,6 +919,22 @@ export default {
</form-input>
</div>
<div v-if="!config.hiddenFields?.includes('uhrzeit')" class="row mb-3">
<form-input
container-class="col-6 stv-details-uhrzeit"
:label="$p.t('global', 'uhrzeit')"
type="DatePicker"
v-model="formData.uhrzeit"
model-type="HH:mm:ss"
time-picker
text-input
name="uhrzeit"
:teleport="true"
:cancel-text="$p.t('ui', 'abbrechen')"
:select-text="$p.t('ui', 'ok')"
/>
</div>
<div class="row mb-3">
<form-input
container-class="col-6 stv-details-abschlusspruefung-sponsion"
@@ -979,7 +998,7 @@ export default {
:cisRoot="cisRoot"
@linkGenerated="printDocument"
></abschlusspruefung-dropdown>
</Teleport>
</Teleport>
</div>
`
}
@@ -317,7 +317,7 @@ export default {
<div class="stv-details-kontakt-contact h-100 pt-3">
<!--Modal: contactModal-->
<bs-modal ref="contactModal">
<bs-modal ref="contactModal" @hideBsModal.stop="">
<template #title>
<p v-if="statusNew" class="fw-bold mt-3">{{$p.t('person', 'kontakt_new')}}</p>
<p v-else class="fw-bold mt-3">{{$p.t('person', 'kontakt_edit')}}</p>
@@ -1,6 +1,7 @@
import {CoreFilterCmpt} from "../../../../filter/Filter.js";
import ZeugnisActions from './Zeugnis/Actions.js';
import ZeugnisDocuments from './Zeugnis/Documents.js';
import { debounce } from '../../../../../helpers/debounce.js';
export default {
name: 'Zeugnis',
@@ -99,7 +100,7 @@ export default {
return;
// get row data
const {lehrveranstaltung_id, uid: student_uid, studiensemester_kurzbz} = cell.getData();
const {lehrveranstaltung_id, uid: student_uid, studiensemester_kurzbz, _punkte: punkte = null} = cell.getData();
listPromise
// get bezeichnung
@@ -111,6 +112,7 @@ export default {
student_uid,
studiensemester_kurzbz,
note,
punkte,
note_bezeichnung
}))
// send to backend
@@ -120,7 +122,7 @@ export default {
.then(list => list.find(el => el.value == note))
.then(found => found?.label)
// update other fields in row
.then(note_bezeichnung => cell.getRow().update({note_bezeichnung}))
.then(note_bezeichnung => cell.getRow().update({note_bezeichnung, punkte, _punkte: null}))
.then(() => cell.getRow().reformat())
// cleanup
.then(cell.clearEdited)
@@ -134,13 +136,16 @@ export default {
if (this.config.usePoints) {
gradeField.editorParams = {
valuesLookup: (cell, filterTerm) => {
if (filterTerm) {
// the last typed Punkte go with the selected Note; a decimal comma counts as a point
const punkte = filterTerm ? filterTerm.replace(',', '.') : null;
cell.getData()._punkte = punkte;
if (punkte) {
return this.$api
.call(
this.endpoint.getGradeFromPoints(
filterTerm,
punkte,
cell.getData().lehrveranstaltung_id,
this.currentSemester
cell.getData().studiensemester_kurzbz
),
{ errorHandling: false }
)
@@ -174,6 +179,124 @@ export default {
gradeField.headerFiler = this.optionalTabulatorOptions?.headerFilter?.note || this.optionalTabulatorOptions?.note || false
}
let punkteField = {
field: 'punkte',
title: this.$p.t('stv/grades_points'),
visible: !!this.config.usePoints,
// numeric(8,4) arrives as "45.5000"
formatter: cell => (cell.getValue() ?? '') === '' ? '' : String(Number(cell.getValue()))
};
if (this.config.usePoints && ['both', 'inline'].includes(this.config.edit)) {
// saves the Punkte of one row; the server derives the Note, the row shows what it saved
const savePunkte = (row, punkte, before) => {
const data = row.getData();
const saveId = data._punkteSaveId = (data._punkteSaveId || 0) + 1;
const { lehrveranstaltung_id, uid: student_uid, studiensemester_kurzbz, lehrveranstaltung_bezeichnung } = data;
this.$api
.call(
// without Punkte the Note before the edit stays; a preview of a shorter input must not stick
this.endpoint.updateCertificate({ lehrveranstaltung_id, student_uid, studiensemester_kurzbz, note: punkte === null ? before.note : data.note, punkte }),
{ errorHeader: lehrveranstaltung_bezeichnung }
)
.then(result => listPromise.then(list => ({
...result.data,
note_bezeichnung: list.find(el => el.value == result.data.note)?.label
})))
.then(saved => {
// a later save of the same row wins
if (row.getData()._punkteSaveId !== saveId)
return;
// update, not reformat: a reformat destroys an editor that is open in this row again
row.update(saved);
this.$fhcAlert.alertSuccess(this.$p.t('stv/grades_updated'));
})
.catch(err => {
if (row.getData()._punkteSaveId === saveId)
row.update(before);
this.$fhcAlert.handleFormValidation(err);
});
};
// the Note of the Notenschluessel shows while typing; Enter, Tab and a click elsewhere save
punkteField.editor = (cell, onRendered, success, cancel) => {
const row = cell.getRow();
const { note, note_bezeichnung, punkte } = row.getData();
const input = document.createElement('input');
input.type = 'text';
input.inputMode = 'decimal';
input.style.cssText = 'width: 100%; height: 100%; box-sizing: border-box; padding: 4px;';
// cellEditCancelled reads this state when Tabulator closes the editor itself
const editor = row.getData()._punkteEditor = {
open: true,
initial: punkteField.formatter(cell),
before: { note, note_bezeichnung, punkte },
value: () => input.value.trim().replace(',', '.'),
save: () => savePunkte(row, editor.value() === '' ? null : editor.value(), editor.before)
};
input.value = editor.initial;
const showNote = debounce(() => {
if (!editor.open)
return;
if (editor.value() === '')
return row.update({ note, note_bezeichnung });
const { lehrveranstaltung_id, studiensemester_kurzbz } = row.getData();
this.$api
.call(this.endpoint.getGradeFromPoints(editor.value(), lehrveranstaltung_id, studiensemester_kurzbz), { errorHandling: false })
.then(result => listPromise.then(list => list.find(el => el.value == result.data)))
.then(found => editor.open && found && row.update({ note: found.value, note_bezeichnung: found.label }))
.catch(() => {});
}, 500);
const finish = save => {
if (!editor.open)
return;
editor.open = false;
if (save && editor.value() !== editor.initial) {
success(editor.value());
return editor.save();
}
cancel();
row.update({ note, note_bezeichnung });
};
input.addEventListener('input', showNote);
input.addEventListener('blur', () => finish(true));
input.addEventListener('keydown', evt => {
if (evt.key === 'Enter')
finish(true);
if (evt.key === 'Escape')
finish(false);
// like the Tabulator editors: else the table keybindings scroll and close the editor
if (evt.key === 'Home' || evt.key === 'End')
evt.stopPropagation();
});
onRendered(() => {
input.focus();
input.select();
});
return input;
};
// Tabulator closes an open editor itself when another edit starts; the typed Punkte still count
punkteField.cellEditCancelled = cell => {
const editor = cell.getData()._punkteEditor;
if (!editor?.open)
return;
editor.open = false;
if (editor.value() === editor.initial)
return cell.getRow().update({ note: editor.before.note, note_bezeichnung: editor.before.note_bezeichnung });
cell.getRow().update({ punkte: editor.value() });
editor.save();
};
// one click opens the editor; without it the first click only selects the row
punkteField.cellClick = (evt, cell) => {
if (!cell.getElement().classList.contains('tabulator-editing'))
cell.edit();
};
}
const columns = [
{ field: 'zeugnis', title: this.$p.t('stv/grades_zeugnis'), formatter: 'tickCross' },
{ field: 'lehrveranstaltung_bezeichnung', title: this.$p.t('lehre/lehrveranstaltung') },
@@ -221,7 +344,7 @@ export default {
{ field: 'ects_lv', title: this.$p.t('lehre/ects'), visible: false },
{ field: 'lehrform', title: this.$p.t('lehre/lehrform'), visible: false },
{ field: 'kurzbz', title: this.$p.t('lehre/kurzbz'), visible: false },
{ field: 'punkte', title: this.$p.t('stv/grades_points'), visible: false },
punkteField,
{ field: 'lehrveranstaltung_bezeichnung_english', title: this.$p.t('stv/grades_lehrveranstaltung_bezeichnung_english'), visible: false },
{ field: 'uid', title: this.$p.t('person/uid'), visible: false },
{ field: 'vorname', title: this.$p.t('person/vorname'), visible: false },
@@ -32,7 +32,8 @@ export default {
return {
grades: [],
suggestions: null,
currentPoints: ''
currentPoints: '',
lastPoints: null
};
},
computed: {
@@ -68,14 +69,15 @@ export default {
},
methods: {
convertPoints({evt, query}) {
this.lastPoints = query ? query.replace(',', '.') : null;
if (!query) {
return this.suggestions = this.grades;
}
this.$refs.points
.call(ApiStvGrades.getGradeFromPoints(
query,
this.lastPoints,
this.selected.find(Boolean)?.lehrveranstaltung_id,
this.currentSemester
this.selected.find(Boolean)?.studiensemester_kurzbz
))
.then(result => {
if (result.data === null) {
@@ -90,7 +92,7 @@ export default {
if (this.selected)
this.selected.forEach(grade => grade.note = note);
this.currentPoints = '';
this.current = note;
this.selectedData.forEach(data => this.$emit('setGrade', {...data, note, punkte: this.lastPoints}));
},
deleteGrades() {
this.selectedData.forEach(data => this.$emit('deleteGrade', data));
@@ -604,9 +604,11 @@ export default {
<!--Modal: KontaktdatenModal -->
<bs-modal
v-if="autocompleteSelectedBetreuer && autocompleteSelectedBetreuer.person_id"
@hideBsModal.stop=""
ref="kontaktdatenModal"
dialog-class="modal-xl modal-dialog-scrollable"
v-if="autocompleteSelectedBetreuer && autocompleteSelectedBetreuer.person_id">
>
<template #title>
<p class="fw-bold mt-3">{{$p.t('projektarbeit', 'kontaktdatenBearbeiten')}}</p>
-1
View File
@@ -1 +0,0 @@
../vendor
+1
View File
@@ -105,6 +105,7 @@ require_once('dbupdate_3.4/78292_gehaltsanpassungtyp.php');
require_once('dbupdate_3.4/54225_reihungstest_ablauf_fuer_quereinsteiger.php');
require_once('dbupdate_3.4/77757_widgets_berechtigungen.php');
require_once('dbupdate_3.4/77048_cis4_mitarbeiter_studstatus_widget.php');
require_once('dbupdate_3.4/83538_cis4_employees.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -0,0 +1,35 @@
<?php
// Add permission: cis/eigene_zeitsperren
if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'cis/eigene_zeitsperren';"))
{
if($db->db_num_rows($result) == 0)
{
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung) VALUES('cis/eigene_zeitsperren', 'CI/VueJs Applikation Eigene Zeitsperren');";
if(!$db->db_query($qry))
{
echo '<strong>system.tbl_berechtigung '.$db->db_last_error().'</strong><br>';
}
else
{
echo 'system.tbl_berechtigung: Added permission "cis/eigene_zeitsperren"<br>';
}
}
}
// Add permission: cis/andere_zeitsperren
if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'cis/andere_zeitsperren';"))
{
if($db->db_num_rows($result) == 0)
{
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung) VALUES('cis/andere_zeitsperren', 'CI/VueJs Applikation Zeitsperren anderer MA anzeigen');";
if(!$db->db_query($qry))
{
echo '<strong>system.tbl_berechtigung '.$db->db_last_error().'</strong><br>';
}
else
{
echo 'system.tbl_berechtigung: Added permission "cis/andere_zeitsperren"<br>';
}
}
}
+142
View File
@@ -42493,6 +42493,26 @@ In the list:
)
)
),
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'grades_error_noGradeForPoints',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Der Notenschlüssel der Lehrveranstaltung ergibt für diese Punkte keine Note',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'The grading key of the course gives no grade for these points',
'description' => '',
'insertvon' => 'system'
)
)
),
// groups
array(
'app' => 'core',
@@ -68077,6 +68097,128 @@ I have been informed that I am under no obligation to consent to the transmissio
)
),
// MYLV PHRASES END
// Zeitsperren Sancho Subjects begin
array(
'app' => 'core',
'category' => 'zeitsperren',
'phrase' => 'zeitausgleichNeu',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Zeitausgleich wurde neu eingetragen',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Compensatory time was entered',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'zeitsperren',
'phrase' => 'zeitausgleichGeaendert',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Zeitausgleich wurde geändert',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Compensatory time was changed',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'zeitsperren',
'phrase' => 'zeitausgleichGeloescht',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Zeitausgleich wurde gelöscht',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Compensatory time was deleted',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'zeitsperren',
'phrase' => 'VorgesetzteInformiert',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Email wurde an {vorgesetzte} versandt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Email was sent to {vorgesetzte}',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'zeitsperren',
'phrase' => 'fehlerBeimSendenAufgetreten',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Fehler beim Senden des Freigabemails an {vorgesetzte}',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Error occured sending mail to {vorgesetzte}',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'zeitsperren',
'phrase' => 'konnteKeinFreigabemailVersendetWerden',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Es konnte kein Freigabemail versendet werden, da kein Vorgesetzter eingetragen ist!',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'An approval mail could not be sent because the “Superior” field was left blank!',
'description' => '',
'insertvon' => 'system'
)
)
),
// Zeitsperren Sancho Subjects end
);