added archive tab to Studierendenverwaltung, possibility to archive documents

This commit is contained in:
Alexei Karpenko
2025-02-20 20:07:07 +01:00
parent b43df5aea0
commit b989eae461
10 changed files with 625 additions and 16 deletions
+2 -2
View File
@@ -2,6 +2,6 @@
if (! defined('BASEPATH')) exit('No direct script access allowed');
// use vuejs dev version
$config['use_vuejs_dev_version'] = false;
$config['use_vuejs_dev_version'] = true;
// use bundled javascript
$config['use_bundled_javascript'] = false;
$config['use_bundled_javascript'] = false;
@@ -43,7 +43,8 @@ class Documents extends FHCAPI_Controller
parent::__construct([
'permissionAlternativeFormat' => self::PERM_LOGGED,
'archive' => ['admin:rw', 'assistenz:rw'],
'archiveSigned' => ['admin:rw', 'assistenz:rw']
'archiveSigned' => ['admin:rw', 'assistenz:rw'],
'download' => ['admin:rw', 'assistenz:rw']
]);
// Load Phrases
@@ -66,7 +67,7 @@ class Documents extends FHCAPI_Controller
}
/**
* Download a not signed document.
* Archive a not signed document.
*
* @param string $xml (optional)
* @param string $xsl (optional)
@@ -79,7 +80,7 @@ class Documents extends FHCAPI_Controller
}
/**
* Download a signed document.
* Archive a signed document.
*
* @param string $xml (optional)
* @param string $xsl (optional)
@@ -91,6 +92,42 @@ class Documents extends FHCAPI_Controller
return $this->_archive($xml, $xsl, getAuthUID());
}
/**
*
* @return void
*/
public function download($xml, $xsl, $sign_user = null)
{
$akteExportData = $this->_getAkteExportData($xml, $xsl, $sign_user);
$akteData = $akteData['akteData'];
$exportData = $akteData['exportData'];
/**
* [
'vorlage' => $vorlage,
'xml_data' => $data,
'oe_kurzbz' => $xsl_oe_kurzbz,
'version' => $version,
'outputformat' => $outputformat,
'sign_user' => $sign_user
]
*/
// Output
$result = $this->documentexportlib->showContent(
$akteData['akteData']['inhalt'],
$exportData['vorlage'],
$exportData['xml_data'],
$exportData['oe_kurzbz'],
$exportData['version'],
$exportData['outputformat'],
$exportData['sign_user']
);
$this->terminateWithSuccess(true);
}
/**
* Helper function for archive() and archiveSigned()
*
@@ -100,16 +137,34 @@ class Documents extends FHCAPI_Controller
*
* @return void
*/
public function _archive($xml, $xsl, $sign_user = null)
private function _archive($xml, $xsl, $sign_user = null)
{
$akteData = $this->_getAkteExportData($xml, $xsl, $sign_user);
$this->load->model('crm/Akte_model', 'AkteModel');
$result = $this->AkteModel->insert($akteData['akteData']);
$this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess(true);
}
/**
*
* @param
* @return object success or error
*/
private function _getAkteExportData($xml, $xsl, $sign_user = null)
{
if (!$xml || !$xsl) {
$this->load->library('form_validation');
if (!$xml) {
$xml = $this->input->post_get('xml');
$this->addMeta('xml', $xml);
$this->form_validation->set_rules('xml', 'xml', 'required');
}
if (!$xsl) {
$xsl = $this->input->post_get('xsl');
$this->addMeta('xsl', $xsl);
$this->form_validation->set_rules('xsl', 'xsl', 'required');
}
@@ -151,6 +206,7 @@ class Documents extends FHCAPI_Controller
$this->load->model('system/Vorlage_model', 'VorlageModel');
$result = $this->VorlageModel->load($xsl);
$this->addMeta("ress", $result);
$vorlage = current($this->getDataOrTerminateWithError($result));
if (!$vorlage)
show_404();
@@ -171,6 +227,7 @@ class Documents extends FHCAPI_Controller
$studiengang_kz = null;
if ($akteData['uid']) {
$this->load->model('crm/Student_model', 'StudentModel');
$this->StudentModel->addSelect('tbl_student.*, UPPER(typ || kurzbz) AS kuerzel');
$this->StudentModel->addJoin('public.tbl_studiengang', 'studiengang_kz', 'LEFT');
$result = $this->StudentModel->load([$akteData['uid']]);
$student = current($this->getDataOrTerminateWithError($result));
@@ -318,6 +375,7 @@ class Documents extends FHCAPI_Controller
$result = $this->VorlagestudiengangModel->getCurrent($xsl, $xsl_oe_kurzbz, $version);
$access_rights = current($this->getDataOrTerminateWithError($result));
// TODO: was bedeutet wenn keine berechtigung?
if (!$access_rights || !$access_rights->berechtigung)
return show_404();
@@ -413,10 +471,17 @@ class Documents extends FHCAPI_Controller
$akteData['titel'] .= '.pdf';
$akteData['inhalt'] = base64_encode($content);
$this->load->model('crm/Akte_model', 'AkteModel');
$result = $this->AkteModel->insert($akteData);
$this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess(true);
return [
'akteData' => $akteData,
'exportData' =>
[
'vorlage' => $vorlage,
'xml_data' => $data,
'oe_kurzbz' => $xsl_oe_kurzbz,
'version' => $version,
'outputformat' => $outputformat,
'sign_user' => $sign_user
]
];
}
}
@@ -0,0 +1,202 @@
<?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');
use CI3_Events as Events;
/**
* This controller operates between (interface) the JS (GUI) and the back-end
* Provides data to the ajax get calls about archive documents
* Listens to ajax post calls to change the archive documents
* This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
*/
class Archiv extends FHCAPI_Controller
{
/**
* Calls the parent's constructor and prepares libraries and phrases
*/
public function __construct()
{
parent::__construct([
'get' => ['admin:r', 'assistenz:r'],
'getArchivVorlagen' => ['admin:r', 'assistenz:r'],
'archive' => ['admin:w', 'assistenz:w'],
'download' => ['admin:w', 'assistenz:w'],
//'update' => ['admin:w', 'assistenz:w'],
'delete' => ['admin:w', 'assistenz:w']
]);
// Load models
$this->load->model('crm/Akte_model', 'AkteModel');
$this->load->model('system/Vorlage_model', 'VorlageModel');
// Load language phrases
$this->loadPhrases([
'archiv'
]);
}
//------------------------------------------------------------------------------------------------------------------
// Public methods
/**
* Get archive documents for a person
* @return void
*/
public function get()
{
$person_id = $this->input->post('person_id');
$this->load->library('form_validation');
if (!$person_id || !is_array($person_id))
{
$this->form_validation->set_rules('person_id', 'Person ID', 'required');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
}
$result = $this->AkteModel->getArchiv($person_id);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
/**
* Get Vorlagen for archiving documents
* @return void
*/
public function getArchivVorlagen()
{
$result = $this->VorlageModel->getArchivVorlagen();
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
/**
*
* @param
* @return object success or error
*/
public function download()
{
$akte_id = $this->input->get('akte_id');
if (!is_numeric($akte_id)) $this->terminateWithError('akte Id missing');
$result = $this->AkteModel->load($akte_id);
if (!hasData($result)) $this->terminateWithError('Akte not found');
$data = $this->getDataOrTerminateWithError($result);
$data = getData($result)[0];
//$this->addMeta("daa", $data->inhalt);
$fileObj = new stdClass();
if (isset($data->inhalt) && $data->inhalt != '')
{
// Define handle to output stream
$tmpFilePointer = fopen("php://output", 'w');
$meta_data = stream_get_meta_data($tmpFilePointer);
$filename = $meta_data["uri"];
fwrite($tmpFilePointer, $data->inhalt);
header('Content-Description: File Transfer');
header('Content-Type: '. $data->mimetype);
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
//header('Content-Length: ' . filesize($fileObj->file));
//header("Content-type: $data->mimetype");
header('Content-Disposition: attachment; filename="'.$data->titel.'"');
readfile($filename);
//echo base64_decode($data->inhalt);
die();
//~ $fileObj->file = $data->inhalt;
//~ $fileObj->name = $data->titel;
//~ $fileObj->mimetype = $data->mimetype;
//~ $fileObj->disposition = 'attachment';
}
else
{
$this->load->library('AkteLib');
$result = $this->aktelib->get($akte_id);
}
/* * $fileObj->filename
* $fileObj->file
* $fileObj->name
* $fileObj->mimetype
* $fileObj->disposition*/
}
/**
* Delete archived Akte
*
* @return void
*/
public function delete()
{
$this->load->library('form_validation');
$this->form_validation->set_rules('akte_id', 'Akte ID', 'required');
$this->form_validation->set_rules('studiengang_kz', 'Studiengang', 'has_permissions_for_stg[admin:rw,assistenz:rw]');
if (!$this->form_validation->run())
$this->terminateWithValidationErrors($this->form_validation->error_array());
$akte_id = $this->input->post('akte_id');
$result = $this->AkteModel->load($akte_id);
if (!hasData($result))
{
$this->terminateWithError($this->p->t('archiv', 'error_missing', [
'akte_id' => $akte_id
]));
}
$result = getData($result)[0];
if ($result->dokument_kurzbz == 'Ausbvert'
&& isset($result->akzeptiertamum)
&& !isEmptyString($result->akzeptiertamum)
&& !has_permissions_for_stg($this->input->post('studiengang_kz'), 'admin:rw')
)
{
$this->terminateWithError($this->p->t('archiv', 'nur_admins_loschen_ausbildungsvertraege', [
'akte_id' => $akte_id
]));
}
$result = $this->AkteModel->delete($akte_id);
if (isError($result)) $this->terminateWithError(getError($result));
$this->terminateWithSuccess();
}
}
@@ -91,6 +91,7 @@ class Config extends FHCAPI_Controller
'title' => $this->p->t('stv', 'tab_resources'),
'component' => './Stv/Studentenverwaltung/Details/Betriebsmittel.js'
];
/* TODO(chris): Ausgeblendet für Testing
$result['grades'] = [
'title' => $this->p->t('stv', 'tab_grades'),
'component' => './Stv/Studentenverwaltung/Details/Noten.js',
@@ -103,7 +104,14 @@ class Config extends FHCAPI_Controller
'documentslist' => $this->gradesDocumentsList()
]
];
*/
$result['archive'] = [
'title' => $this->p->t('stv', 'tab_archive'),
'component' => './Stv/Studentenverwaltung/Details/Archiv.js'
//~ 'config' => [
//~ //'columns' => $this->kontoColumns()
//~ ]
];
Events::trigger('stv_conf_student', function & () use (&$result) {
return $result;
@@ -138,6 +146,13 @@ class Config extends FHCAPI_Controller
'changeStatusToAbsolvent' => $this->permissionlib->isBerechtigt('admin')
]
];
$result['archive'] = [
'title' => $this->p->t('stv', 'tab_archive'),
'component' => './Stv/Studentenverwaltung/Details/Archiv.js'
//~ 'config' => [
//~ //'columns' => $this->kontoColumns()
//~ ]
];
Events::trigger('stv_conf_students', function & () use (&$result) {
return $result;
+7 -3
View File
@@ -195,9 +195,9 @@ class Akte_model extends DB_Model
}
/**
* Liefert die Archivdokumente einer Person
* Liefert die Archivdokumente einer Person/mehrerer Personen
*
* @param integer $person_id
* @param integer/array $person_id
* @param boolean|null $signiert Wenn true werden nur Dokumente geliefert die digital signiert wurden.
* @param boolean|null $stud_selfservice Wenn true werden nur Dokumente geliefert die Studierende selbst herunterladen duerfen.
*
@@ -237,10 +237,14 @@ class Akte_model extends DB_Model
if ($stud_selfservice !== null)
$this->db->where('stud_selfservice', (boolean)$stud_selfservice);
if (is_array($person_id))
$this->db->where_in('person_id', $person_id);
else
$this->db->where('person_id', $person_id);
$this->addOrder('erstelltam', 'DESC');
return $this->loadWhere([
'person_id' => $person_id,
'archiv' => true
]);
}
+11 -1
View File
@@ -13,7 +13,7 @@ class Vorlage_model extends DB_Model
}
/**
* Returns mume types
* Returns mime types
*/
public function getMimeTypes()
{
@@ -21,4 +21,14 @@ class Vorlage_model extends DB_Model
return $this->execQuery($query);
}
/**
* Returns all Vorlagen for archive
*/
public function getArchivVorlagen()
{
$query ="SELECT * FROM public.tbl_vorlage WHERE archivierbar=true ORDER BY bezeichnung";
return $this->execQuery($query);
}
}
+2
View File
@@ -3,6 +3,7 @@ import students from './stv/students.js';
import filter from './stv/filter.js';
import konto from './stv/konto.js';
import grades from './stv/grades.js';
import archiv from './stv/archiv.js';
export default {
verband,
@@ -10,6 +11,7 @@ export default {
filter,
konto,
grades,
archiv,
configStudent() {
return this.$fhcApi.get('api/frontend/v1/stv/config/student');
},
+30
View File
@@ -0,0 +1,30 @@
export default {
tabulatorConfig(config, self) {
config.ajaxURL = 'api/frontend/v1/stv/archiv/get';
config.ajaxParams = () => {
const params = {
person_id: self.modelValue.person_id || self.modelValue.map(e => e.person_id)
};
return params;
};
config.ajaxRequestFunc = (url, config, params) => this.$fhcApi.post(url, params, config);
config.ajaxResponse = (url, params, response) => response.data;
return config;
},
getArchivVorlagen() {
return this.$fhcApi.post('api/frontend/v1/stv/archiv/getArchivVorlagen');
},
archive(data) {
return this.$fhcApi.post(
'api/frontend/v1/documents/archiveSigned',
data
);
},
//~ edit(data) {
//~ return this.$fhcApi.post('api/frontend/v1/stv/konto/update', data);
//~ },
delete({akte_id, studiengang_kz}) {
return this.$fhcApi.post('api/frontend/v1/stv/archiv/delete', {akte_id, studiengang_kz});
}
};
@@ -0,0 +1,241 @@
import {CoreFilterCmpt} from "../../../filter/Filter.js";
import FormInput from "../../../Form/Input.js";
//~ import KontoNew from "./Konto/New.js";
//~ import KontoEdit from "./Konto/Edit.js";
export default {
components: {
CoreFilterCmpt,
FormInput
//~ KontoNew,
//~ KontoEdit
},
inject: {
defaultSemester: {
from: 'defaultSemester'
}
},
props: {
modelValue: Object,
config: {
type: Object,
default: {}
}
},
data() {
return {
loading: false,
vorlage_kurzbz: '',
vorlagenArchiv: [],
vorlageXmlXslMappings: {
'zeugnis.rdf.php': [
'Zeugnis',
'ZeugnisEng'
],
'abschlusspruefung.rdf.php': [
'PrProtokollBakk',
'PrProtBakkEng',
'PrProtBA',
'PrProtBAEng',
'PrProtokollDipl',
'PrProtDiplEng',
'PrProtMA',
'PrProtMAEng',
'Bescheid',
'BescheidEng',
'Bakkurkunde',
'BakkurkundeEng',
'Diplomurkunde',
'DiplomurkundeEng',
],
'diplomasupplement.xml.php': [
'DiplSupplement',
'SZeugnis'
],
'studienblatt.xml.php': [
'Studienblatt',
'StudienblattEng'
],
'ausbildungsvertrag.xml.php': [
'Ausbildungsver',
'AusbVerEng'
],
'abschlussdokument_lehrgaenge.xml.php': [
'AbschlussdokumentLehrgaenge'
]
}
//studiengang_kz: false
};
},
computed: {
//~ personIds() {
//~ if (this.modelValue.person_id)
//~ return [this.modelValue.person_id];
//~ return this.modelValue.map(e => e.person_id);
//~ },
tabulatorColumns() {
const columns = [
{title: "Akte Id", field: "akte_id", visible: false},
{title: "Titel", field: "titel"},
{title: "Bezeichnung", field: "bezeichnung"},
{title: "Erstelldatum", field: "erstelltam"},
{title: "Signiert", field: "erstelltam"},
{title: "Selfservice", field: "signiert"},
{title: "AkzeptiertAmUm", field: "akzeptiertamum"},
{title: "Gedruckt", field: "gedruckt", visible: false},
{
title: 'Aktionen', field: 'actions',
formatter: (cell, formatterParams, onRendered) => {
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.edit.open(cell.getData())
//~ );
//~ container.append(button);
let 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 ? {akte_id: cell.getData().akte_id, studiengang_kz: this.modelValue.studiengang_kz} : Promise.reject({handled:true}))
.then(this.$fhcApi.factory.stv.archiv.delete)
.then(() => {
//cell.getRow().delete();
this.reload();
})
.catch(this.$fhcAlert.handleSystemError);
});
container.append(button);
return container;
},
minWidth: 150, // Ensures Action-buttons will be always fully displayed
maxWidth: 150,
frozen: true
}
];
return Object.values(columns);
},
tabulatorOptions() {
return this.$fhcApi.factory.stv.archiv.tabulatorConfig({
layout:"fitDataTable",
columns: this.tabulatorColumns,
//selectable: true,
//selectableRangeMode: 'click',
index: 'akte_id',
persistenceID: 'stv-details-archiv'
}, this);
},
tabulatorEvents() {
const events = [
{
event: "rowDblClick",
handler: (e, row) => {
this.actionDownload(row.getData().akte_id);
}
}
];
return events;
}
},
watch: {
modelValue() {
this.$refs.table.reloadTable();
}
},
methods: {
reload() {
this.$refs.table.reloadTable();
},
updateData(data) {
if (!data)
return this.reload();
// TODO(chris): check children (!delete?, multiple children)
//this.$refs.table.tabulator.updateOrAddData(data.map(row => row.buchungsnr_verweis ? {buchungsnr:row.buchungsnr_verweis, _children:row} : row));
this.$refs.table.tabulator.updateOrAddData(data);
},
actionArchive() {
this.loading = true;
this.$fhcApi
.factory.stv.archiv.archive({
xml: this.getXmlByXsl(this.vorlage_kurzbz),
xsl: this.vorlage_kurzbz,
ss: this.defaultSemester,
uid: this.modelValue.uid,
prestudent_id: this.modelValue.prestudent_id
})
.then(result => result.data)
.then(() => {
this.reload();
this.loading = false;
})
.then(() => this.$p.t('ui/gespeichert'))
.then(this.$fhcAlert.alertSuccess)
.catch(error => {
this.$fhcAlert.handleSystemError(error);
this.loading = false;
});
},
actionDownload(akte_id) {
window.open(
FHC_JS_DATA_STORAGE_OBJECT.app_root
+ FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/api/frontend/v1/stv/archiv/download?akte_id=' + encodeURIComponent(akte_id),
'_blank'
);
},
getXmlByXsl(xsl) {
for (let xml in this.vorlageXmlXslMappings) {
let xslArr = this.vorlageXmlXslMappings[xml];
if (xslArr.includes(xsl)) return xml;
}
return null;
}
},
created() {
this.$fhcApi
.factory.stv.archiv.getArchivVorlagen()
.then(result => {this.vorlagenArchiv = result.data; this.vorlage_kurzbz = result.data.length > 0 ? result.data[0].vorlage_kurzbz : '';})
.catch(this.$fhcAlert.handleSystemError);
},
template: `
<div class="stv-details-konto h-100 d-flex flex-column">
<core-filter-cmpt
ref="table"
table-only
:side-menu="false"
:tabulator-options="tabulatorOptions"
:tabulator-events="tabulatorEvents"
reload
>
<template #actions>
<div class="input-group w-auto">
<select class="form-select" v-model="vorlage_kurzbz">
<option v-for="vorlage in vorlagenArchiv" :key="vorlage.vorlage_kurzbz" :value="vorlage.vorlage_kurzbz">
{{ vorlage.bezeichnung }}
</option>
</select>
<button
class="btn btn-primary"
@click="actionArchive()"
:disabled="loading"
>
<i v-if="loading" class="fa fa-spinner fa-spin"></i>
{{ $p.t('stv/archiv_dokument_archivieren') }}
</button>
</div>
</template>
</core-filter-cmpt>
</div>`
};
//~ <konto-new ref="new" :config="config" @saved="updateData" :person-ids="personIds" :stg-kz="stg_kz"></konto-new>
//~ <konto-edit ref="edit" :config="config" @saved="updateData"></konto-edit>
+40
View File
@@ -38181,6 +38181,46 @@ array(
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'archiv_dokument_archivieren',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Dokument archivieren',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Archive document',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'stv',
'phrase' => 'tab_archive',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Archiv',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Archive',
'description' => '',
'insertvon' => 'system'
)
)
)
);