gui fuer kalender sync hinzugefuegt und syncjob angepasst

This commit is contained in:
ma0048
2026-07-21 13:50:17 +02:00
parent 9abaf30ed3
commit 9983a48102
16 changed files with 1463 additions and 167 deletions
+83
View File
@@ -0,0 +1,83 @@
/**
* Copyright (C) 2026 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 {
getSyncs(studiensemester_kurzbz) {
return {
method: 'get',
url: 'api/frontend/v1/tempus/KalenderSync/getSyncs',
params: { studiensemester_kurzbz }
};
},
loadSync(kalender_syncstatus_id) {
return {
method: 'get',
url: 'api/frontend/v1/tempus/KalenderSync/loadSync',
params: { kalender_syncstatus_id }
};
},
getSyncStatus() {
return {
method: 'get',
url: 'api/frontend/v1/tempus/KalenderSync/getSyncStatus',
};
},
add(formData) {
return {
method: 'post',
url: 'api/frontend/v1/tempus/KalenderSync/add',
params: { formData }
};
},
delete(kalender_syncstatus_id) {
return {
method: 'post',
url: 'api/frontend/v1/tempus/KalenderSync/delete',
params: { kalender_syncstatus_id }
};
},
start(formData) {
return {
method: 'post',
url: 'api/frontend/v1/tempus/KalenderSync/start',
params: { formData }
};
},
updateSync(formData) {
return {
method: 'post',
url: 'api/frontend/v1/tempus/KalenderSync/updateSync',
params: { formData }
};
},
getStudienplan(oe_kurzbz, studiensemester_kurzbz, ausbildungssemester) {
return {
method: 'get',
url: 'api/frontend/v1/tempus/KalenderSync/getStudienplan',
params: { oe_kurzbz, studiensemester_kurzbz, ausbildungssemester }
};
},
getMaxSemester(studiengang_kzs) {
return {
method: 'post',
url: 'api/frontend/v1/stv/status/getMaxSemester/',
params: { studiengang_kzs }
};
},
};
+2
View File
@@ -16,6 +16,7 @@
*/
import FhcTempus from "../components/Tempus/Tempus.js";
import FhcTempusSync from "../components/Tempus/TempusSync.js";
import Phrasen from "../plugins/Phrasen.js";
import {capitalize} from "../helpers/StringHelpers.js";
@@ -27,6 +28,7 @@ const router = VueRouter.createRouter({
history: VueRouter.createWebHistory(),
routes: [
{ path: `/${ciPath}/Tempus`, component: FhcTempus },
{ path: `/${ciPath}/Tempus/sync`, component: FhcTempusSync },
]
});
+340
View File
@@ -0,0 +1,340 @@
import BsModal from '../Bootstrap/Modal.js';
import CoreForm from '../Form/Form.js';
import FormValidation from '../Form/Validation.js';
import FormInput from '../Form/Input.js';
import ApiTempusSync from '../../api/factory/tempus/sync.js';
export default {
components: {
BsModal,
CoreForm,
FormValidation,
FormInput
},
props: {
config: Object,
stsem_kurzbz: String
},
emits: [
'saved'
],
data() {
return {
mode: 'new',
formData: {},
studienplaene: [],
max_semester: null,
syncStati: []
};
},
computed: {
semester() {
if (!this.max_semester)
return [];
return Array.from({length: this.max_semester}, (_, index) => index);
}
},
watch: {
'formData.oe_kurzbz'(newOrganisation) {
if (!newOrganisation) {
this.max_semester = null;
this.studienplaene = [];
return;
}
let filtered_org = this.config.organisationen.filter(org => org.oe_kurzbz === newOrganisation)[0];
if (filtered_org && filtered_org.studiengang_kz)
this.getSemester(filtered_org.studiengang_kz);
else
this.max_semester = null;
this.getStudienplan();
},
'formData.studiensemester_kurzbz'() {
this.getStudienplan();
},
'formData.ausbildungssemester'() {
this.getStudienplan();
}
},
methods: {
openNew() {
this.$refs.modal.hide();
this.mode = 'new';
this.max_semester = null;
this.studienplaene = [];
this.formData = {
oe_kurzbz: null,
studiensemester_kurzbz: this.stsem_kurzbz ?? null,
datum_bis: null,
studienplan_id: null,
ausbildungssemester: null,
sync_status_kurzbz: null,
mail: false
};
this.$refs.form.clearValidation()
this.$refs.modal.show()
},
openEdit(entry) {
if (!entry || entry.kalender_syncstatus_id === undefined || entry.kalender_syncstatus_id === null)
{
return;
}
this.$refs.modal.hide();
this.mode = 'edit';
this.$api
.call(ApiTempusSync.loadSync(entry.kalender_syncstatus_id))
.then(result => {
this.formData = result.data;
return this.$refs.form.clearValidation();
})
.then(() => this.$refs.modal.show())
.catch(this.$fhcAlert.handleSystemError);
},
openStart() {
this.$refs.modal.hide();
this.mode = 'start';
this.max_semester = null;
this.studienplaene = [];
this.formData = {
oe_kurzbz: null,
studiensemester_kurzbz: this.stsem_kurzbz ?? null,
datum_bis: null,
sync_status_kurzbz: null,
studienplan_id: null,
ausbildungssemester: null,
mail: false
};
this.$refs.form.clearValidation()
this.$refs.modal.show()
},
getSemester(stg_kz) {
this.$api.call(ApiTempusSync.getMaxSemester([stg_kz]))
.then(response => response.data)
.then(response => {
this.max_semester = response;
let found = this.semester.filter(semester => semester === this.formData.ausbildungssemester);
if (found.length === 0)
this.formData.ausbildungssemester = null;
})
.catch(error => {
this.$fhcAlert.handleSystemError(error);
});
},
getStudienplan() {
if (!this.formData.oe_kurzbz || !this.formData.studiensemester_kurzbz) {
this.studienplaene = [];
return;
}
this.$api.call(ApiTempusSync.getStudienplan(
this.formData.oe_kurzbz,
this.formData.studiensemester_kurzbz,
this.formData.ausbildungssemester ?? null
))
.then(response => response.data)
.then(response => {
this.studienplaene = response;
let found = this.studienplaene.filter(plan => plan.studienplan_id === this.formData.studienplan_id);
if (found.length === 0)
this.formData.studienplan_id = null;
})
.catch(error => {
this.$fhcAlert.handleSystemError(error);
});
},
save() {
if (this.mode === 'start')
this.start();
else if (this.mode === 'new')
this.insert();
else
this.update();
},
insert() {
this.$refs.form
.call(ApiTempusSync.add(this.formData))
.then(() => {
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
this.$emit('saved');
this.$refs.modal.hide();
})
.catch(this.$fhcAlert.handleSystemError);
},
update() {
this.$refs.form
.call(ApiTempusSync.updateSync(this.formData))
.then(() => {
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
this.$emit('saved');
this.$refs.modal.hide();
})
.catch(this.$fhcAlert.handleSystemError);
},
start() {
this.$refs.form
.call(ApiTempusSync.start(this.formData))
.then(() => {
this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave'));
this.$emit('saved');
this.$refs.modal.hide();
})
.catch(this.$fhcAlert.handleSystemError);
}
},
created() {
console.log(this.config)
this.$api
.call(ApiTempusSync.getSyncStatus())
.then(result => this.syncStati = result.data)
.catch(error => this.$fhcAlert.handleSystemError(error));
},
template: `
<bs-modal ref="modal" >
<template #title>
<template v-if="mode === 'new'">{{ $p.t('lehre', 'tempus_sync_new') }}</template>
<template v-else-if="mode === 'edit'">{{ $p.t('lehre', 'tempus_sync_edit') }}</template>
<template v-else>{{ $p.t('lehre', 'tempus_sync_start') }}</template>
</template>
<core-form ref="form">
<form-validation></form-validation>
<form-input
container-class="mb-3"
type="select"
v-model="formData.oe_kurzbz"
name="oe_kurzbz"
:label="$p.t('lehre/organisationseinheit')"
required
>
<option
v-for="organisation in config.organisationen"
:key="organisation.oe_kurzbz"
:value="organisation.oe_kurzbz"
>
{{ organisation.typ.toUpperCase() }}{{ organisation.kurzbz.toUpperCase() }} ({{ organisation.kurzbzlang }})
</option>
</form-input>
<form-input
container-class="mb-3"
type="select"
v-model="formData.studiensemester_kurzbz"
name="studiensemester_kurzbz"
:label="$p.t('lehre/studiensemester')"
required
>
<option
v-for="studiensemester in config.studiensemestern"
:key="studiensemester.studiensemester_kurzbz"
:value="studiensemester.studiensemester_kurzbz"
>
{{ studiensemester.studiensemester_kurzbz }}
</option>
</form-input>
<form-input
container-class="mb-3"
type="select"
v-model="formData.ausbildungssemester"
name="ausbildungssemester"
:label="$p.t('lehre/ausbildungssemester')"
:disabled="!max_semester"
>
<option :value="null">-- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --</option>
<option
v-for="number in semester"
:key="number"
:value="number"
>
{{ number }}
</option>
</form-input>
<form-input
container-class="mb-3"
type="select"
v-model="formData.studienplan_id"
name="studienplan_id"
:label="$p.t('lehre/studienplan')"
:disabled="!studienplaene.length"
>
<option :value="null">-- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --</option>
<option
v-for="plan in studienplaene"
:key="plan.studienplan_id"
:value="plan.studienplan_id"
>
{{ plan.bezeichnung }}-{{plan.orgform_kurzbz}}
</option>
</form-input>
<form-input
container-class="mb-3"
type="DatePicker"
v-model="formData.datum_bis"
name="datum_bis"
:label="$p.t('ui/dateTo')"
auto-apply
:enable-time-picker="false"
text-input
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
>
</form-input>
<form-input
container-class="mb-3"
type="select"
v-model="formData.sync_status_kurzbz"
name="sync_status_kurzbz"
:label="$p.t('global/status')"
>
<option :value="null">-- {{ $p.t('fehlermonitoring', 'keineAuswahl') }} --</option>
<option
v-for="status in syncStati"
:key="status.status_kurzbz"
:value="status.status_kurzbz"
>
{{ status.bezeichnung }}
</option>
</form-input>
<form-input
container-class="mb-3"
type="checkbox"
v-model="formData.mail"
name="mail"
:label="$p.t('lehre/mail_benachrichtigung')"
>
</form-input>
</core-form>
<template #footer>
<button
type="button"
class="btn btn-primary"
@click="save"
>
<template v-if="mode === 'start'">{{ $p.t('global', 'jetztStarten') }}</template>
<template v-else>{{ $p.t('ui', 'ok') }}</template>
</button>
</template>
</bs-modal>`
};
+196
View File
@@ -0,0 +1,196 @@
import {CoreFilterCmpt} from '../filter/Filter.js';
import {CoreNavigationCmpt} from '../navigation/Navigation.js';
import FormInput from "../Form/Input.js";
import CoreBaseLayout from '../../components/layout/BaseLayout.js';
import ApiTempusSync from '../../api/factory/tempus/sync.js';
import TempusSyncModal from './Modal.js';
export default {
props: {
config: Object,
defaultSemester: String
},
components: {
CoreFilterCmpt,
CoreBaseLayout,
CoreNavigationCmpt,
FormInput,
TempusSyncModal
},
data() {
return {
studiensemester_kurzbz: this.defaultSemester ?? null
}
},
watch: {
studiensemester_kurzbz()
{
this.reloadTable();
}
},
computed: {
tabulatorOptions() {
return {
index: "kalender_syncstatus_id",
ajaxURL: 'dummy',
ajaxRequestFunc: () => this.$api.call(ApiTempusSync.getSyncs(this.studiensemester_kurzbz)),
ajaxResponse: (url, params, response) => {
return response.data || [];
},
layout: 'fitDataStretch',
placeholder: "Keine Daten verfügbar",
persistenceID: "2026_07_21_tempus_sync_v1",
locale: true,
columns: [
{
formatter: 'rowSelection',
titleFormatter: 'rowSelection',
headerSort: false,
width: 40
},
{titlePhrase: 'lehre/organisationseinheit', field: 'oebezeichnung'},
{titlePhrase: 'lehre/studiensemester', field: 'studiensemester_kurzbz'},
{titlePhrase: 'lehre/bis_title', field: 'datum_bis',
formatter: (cell) => {
const value = cell.getValue();
if (!value) return ''
const date = luxon.DateTime.fromSQL(value)
return date.isValid ? date.toFormat('dd.MM.yyyy') : value
},
minWidth: 100
},
{titlePhrase: 'lehre/studienplan', field: 'studienplanbezeichnung', minWidth: 200},
{titlePhrase: 'lehre/ausbildungssemester', field: 'ausbildungssemester'},
{titlePhrase: 'global/status', field: 'sync_status_kurzbz'},
{titlePhrase: 'lehre/mail_benachrichtigung', field: 'mail', formatter: 'tickCross', hozAlign: 'center', minWidth: 80},
{titlePhrase:'global/insertamum', field: 'insertamum',
formatter: (cell) => {
const value = cell.getValue();
if (!value) return ''
const date = luxon.DateTime.fromSQL(value)
return date.isValid ? date.toFormat('dd.MM.yyyy HH:mm') : value
},
visible: false
},
{titlePhrase: 'global/insertvon', field: 'insertvon', visible: false},
{titlePhrase: 'global/updateamum', field: 'updateamum',
formatter: (cell) => {
const value = cell.getValue();
if (!value) return ''
const date = luxon.DateTime.fromSQL(value)
return date.isValid ? date.toFormat('dd.MM.yyyy HH:mm') : value
},
visible: false
},
{titlePhrase: 'global/updatevon', field: 'updatevon', visible: false},
{
titlePhrase: 'global/aktionen',
field: 'actions',
headerSort: false,
hozAlign: 'center',
width: 120,
formatter: (cell) => {
let container = document.createElement('div');
container.className = "d-flex gap-2";
let button = document.createElement('button');
button.className = 'btn btn-outline-secondary';
button.innerHTML = '<i class="fa fa-edit"></i>';
button.addEventListener('click', () =>
this.$refs.syncModal.openEdit(cell.getRow().getData())
);
container.append(button);
button = document.createElement('button');
button.className = 'btn btn-outline-secondary';
button.innerHTML = '<i class="fa fa-trash"></i>';
button.addEventListener('click', evt => {
evt.stopPropagation();
this.$fhcAlert
.confirmDelete()
.then(result => result ? cell.getData().kalender_syncstatus_id : Promise.reject({handled:true}))
.then(kalender_syncstatus_id => this.$api.call(ApiTempusSync.delete(kalender_syncstatus_id)))
.then(() => {
this.reloadTable();
})
.catch(this.$fhcAlert.handleSystemError);
});
container.append(button);
return container;
}
}
],
}
}
},
methods: {
openNewModal()
{
this.$refs.syncModal.openNew();
},
reloadTable()
{
this.$refs.syncTable.reloadTable();
},
openStartModal()
{
this.$refs.syncModal.openStart();
}
},
template: `
<core-navigation-cmpt></core-navigation-cmpt>
<core-base-layout>
<template #main>
<core-filter-cmpt
ref="syncTable"
:tabulator-options="tabulatorOptions"
:table-only=true
:side-menu="false"
:reload="true"
:useSelectionSpan="false"
new-btn-label="Hinzufügen"
new-btn-show
@click:new="openNewModal">
<template #actions>
<form-input
type="select"
v-model="studiensemester_kurzbz"
name="studiensemester_kurzbz"
>
<option :value="null" disabled>-- {{ $p.t('lehre', 'studiensemester') }} --</option>
<option
v-for="studiensemester in config.studiensemestern"
:key="studiensemester.studiensemester_kurzbz"
:value="studiensemester.studiensemester_kurzbz"
>
{{ studiensemester.studiensemester_kurzbz }}
</option>
</form-input>
<button
type="button"
class="btn btn-outline-secondary"
@click="openStartModal"
>
{{ $p.t('global', 'jetztStarten') }}
</button>
</template>
</core-filter-cmpt>
<tempus-sync-modal
ref="syncModal"
:config="config"
:stsem_kurzbz="studiensemester_kurzbz"
@saved="reloadTable"
>
</tempus-sync-modal>
</template>
</core-base-layout>
`
};