From abfd92e38748e9bf58a3e61063c587a47f566b89 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 28 Mar 2024 15:35:26 +0100 Subject: [PATCH 1/5] Fetch: check for intermediate properties of error object --- public/js/components/Fetch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/components/Fetch.js b/public/js/components/Fetch.js index 9a34e1a3f..f683c0655 100644 --- a/public/js/components/Fetch.js +++ b/public/js/components/Fetch.js @@ -99,7 +99,7 @@ export const CoreFetchCmpt = { * */ errorHandler: function(error) { - if (error.response.data.retval) + if (error.response?.data?.retval) this.setError(error.response.data.retval); else this.setError(error.message); From 24641d1444ce52aff0604000afb8e7da45224a3b Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 28 Mar 2024 15:35:43 +0100 Subject: [PATCH 2/5] Tabs: use computed on titles --- public/js/components/Tabs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/components/Tabs.js b/public/js/components/Tabs.js index 36b6406c5..085c63566 100644 --- a/public/js/components/Tabs.js +++ b/public/js/components/Tabs.js @@ -71,7 +71,7 @@ export default { tabs[key] = { component: Vue.markRaw(Vue.defineAsyncComponent(() => import(item.component))), - title: item.title || key, + title: Vue.computed(() => item.title || key), config: item.config, key } @@ -83,7 +83,7 @@ export default { tabs[key] = { component: Vue.markRaw(Vue.defineAsyncComponent(() => import(item.component))), - title: item.title || key, + title: Vue.computed(() => item.title || key), config: item.config, key } From 46fff01eaad1f3b7285d648cd952a818de9b883b Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 28 Mar 2024 15:37:50 +0100 Subject: [PATCH 3/5] Form_validation: extra functions --- application/helpers/hlp_common_helper.php | 38 +++++++++++++++++ .../language/english/form_validation_lang.php | 42 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 application/language/english/form_validation_lang.php diff --git a/application/helpers/hlp_common_helper.php b/application/helpers/hlp_common_helper.php index 3e682e56c..688fae965 100644 --- a/application/helpers/hlp_common_helper.php +++ b/application/helpers/hlp_common_helper.php @@ -422,3 +422,41 @@ function isValidDate($dateString) return false; } } + + +// ------------------------------------------------------------------------ +// Collection of utility functions for form validation purposes +// ------------------------------------------------------------------------ + +/** + * check if string can be converted to a date + */ +function is_valid_date($dateString) +{ + try + { + return (new DateTime($dateString)) !== false; + } + catch(Exception $e) + { + return false; + } +} + +/** + * check if given permissions are met + */ +function has_write_permissions($value, $permissions = '') +{ + if (!$permissions) + $permissions = $value; // TODO(chris): ?? + $permissions = explode(',', $permissions); + + $CI =& get_instance(); + $CI->load->library('PermissionLib'); + return $CI->permissionlib->hasAtLeastOne( + $permissions, + 'sometable', + 'w' + ); +} diff --git a/application/language/english/form_validation_lang.php b/application/language/english/form_validation_lang.php new file mode 100644 index 000000000..9e942f8e5 --- /dev/null +++ b/application/language/english/form_validation_lang.php @@ -0,0 +1,42 @@ + Date: Thu, 28 Mar 2024 15:38:50 +0100 Subject: [PATCH 4/5] Udf Component --- .../controllers/api/frontend/v1/Udf.php | 142 ++++++++++++++++ application/core/DB_Model.php | 17 ++ application/libraries/UDFLib.php | 156 ++++++++++++++++++ public/js/components/Udf/Udf.js | 156 ++++++++++++++++++ 4 files changed, 471 insertions(+) create mode 100644 application/controllers/api/frontend/v1/Udf.php create mode 100644 public/js/components/Udf/Udf.js diff --git a/application/controllers/api/frontend/v1/Udf.php b/application/controllers/api/frontend/v1/Udf.php new file mode 100644 index 000000000..99ed2a10d --- /dev/null +++ b/application/controllers/api/frontend/v1/Udf.php @@ -0,0 +1,142 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the UDFLib (back-end) + * Provides data to the ajax get calls about the Udf component + * Listens to ajax post calls to change the Udf data + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + */ +class Udf extends FHCAPI_Controller +{ + /** + * Calls the parent's constructor and prepares the UDFLib + */ + public function __construct() + { + // TODO(chris): permissions + // NOTE: UdfLib has its own permissions checks + parent::__construct([ + 'load' => 'admin:r',#self::PERM_LOGGED, + 'save' => 'admin:r'#self::PERM_LOGGED + ]); + + // Libraries + $this->load->library('form_validation'); + $this->load->library('UDFLib'); + + // Models + $this->load->model($this->getTargetModelPath(), 'TargetModel'); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * Load all UDFs for a dataset + * + * @return void + */ + public function load() + { + $pks = $this->TargetModel->getPks(); + foreach ($pks as $id) + $this->form_validation->set_rules($id, $id, 'required'); + + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + + $id = []; + foreach ($pks as $pk) + $id[$pk] = $this->input->post($pk); + if (!is_array($this->TargetModel->getPk())) + $id = current($id); + + $result = $this->udflib->getFieldArray($this->TargetModel, $id); + + #$fields = $this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result)); + $fields = $result->retval; + + $this->terminateWithSuccess($fields); + } + + /** + * Saves UDFs to a dataset + * + * @return void + */ + public function save() + { + $pks = $this->TargetModel->getPks(); + foreach ($pks as $id) + $this->form_validation->set_rules($id, $id, 'required'); + + $result = $this->udflib->getCiValidations($this->TargetModel, $this->input->post()); + + #$fieldValidations = $this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result)); + $fieldvalidations = $result->retval; + + $this->form_validation->set_rules($fieldvalidations); + + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + + $id = []; + $fields = $this->input->post(); + foreach ($pks as $pk) { + $id[$pk] = $fields[$pk]; + unset($fields[$pk]); + } + if (!is_array($this->TargetModel->getPk())) + $id = current($id); + + $result = $this->TargetModel->update($id, $fields); + + #$this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + + $this->terminateWithSuccess(array_fill_keys(array_keys($fields), '')); + } + + //------------------------------------------------------------------------------------------------------------------ + // Private methods + + /** + * Get the path to the target model from the url + * + * @return string + */ + private function getTargetModelPath() + { + $ci_model_path = array_slice($this->uri->rsegments, 2); + if ($ci_model_path) + $ci_model_path[] = ucfirst(array_pop($ci_model_path)) . '_model'; + return implode(DIRECTORY_SEPARATOR, $ci_model_path); + } +} \ No newline at end of file diff --git a/application/core/DB_Model.php b/application/core/DB_Model.php index 79040dc66..c799de516 100644 --- a/application/core/DB_Model.php +++ b/application/core/DB_Model.php @@ -827,6 +827,23 @@ class DB_Model extends CI_Model return $result; } + public function getDbTable() + { + return $this->dbTable; + } + + public function getPk() + { + return $this->pk; + } + + public function getPks() + { + if (is_array($this->pk)) + return $this->pk; + return [$this->pk]; + } + // ------------------------------------------------------------------------------------------ // Protected methods diff --git a/application/libraries/UDFLib.php b/application/libraries/UDFLib.php index c5f0d3e98..2fa5dd0d4 100644 --- a/application/libraries/UDFLib.php +++ b/application/libraries/UDFLib.php @@ -65,6 +65,8 @@ class UDFLib private $_udfUniqueId; // Property that contains the UDF widget unique id + private $_definition_cache = []; + /** * Gets CI instance */ @@ -613,6 +615,160 @@ class UDFLib ); } + /** + * Gets the UDF definitions for a model + * + * @param DB_Model $targetModel + * + * @return stdClass + */ + public function getDefinitionForModel($targetModel) + { + $dbTable = $targetModel->getDbTable(); + if (!isset($this->_definition_cache[$dbTable])) { + $this->_ci->load->model('system/UDF_model', 'UDFModel'); + list($schema, $table) = explode('.', $dbTable); + $result = $this->_ci->UDFModel->loadWhere([ + 'schema' => $schema, + 'table' => $table + ]); + if (isError($result)) + return $result; + + $this->_definition_cache[$dbTable] = json_decode(current($result->retval)->jsons, true); + } + return success($this->_definition_cache[$dbTable]); + } + + /** + * Gets the UDFs for db entry with translated params and resolved listValues for dropdowns + * + * @param DB_Model $targetModel + * @param mixed $id + * + * @return stdClass + */ + public function getFieldArray($targetModel, $id) + { + // Load Libraries + $this->_ci->load->library('PhrasesLib'); + $this->_ci->load->library('PermissionLib'); + + $result = $this->getDefinitionForModel($targetModel); + if (isError($result)) + return $result; + $definitions = getData($result); + + usort($definitions, function ($a, $b) { + return $a[self::SORT] - $b[self::SORT]; + }); + + $values = $targetModel->getUDFs($id); + + $fields = []; + foreach ($definitions as $field) { + // check read permissions + if (!$this->_ci->permissionlib->hasAtLeastOne( + $field[self::REQUIRED_PERMISSIONS_PARAMETER], + self::PERMISSION_TABLE_METHOD, + self::PERMISSION_TYPE_READ + )) + continue; + + // set value + if (isset($values[$field[self::NAME]])) { + $field['value'] = $values[$field[self::NAME]]; + } elseif (isset($field['defaultValue'])) { + $field['value'] = $field['defaultValue']; + } elseif (isset($field[self::TYPE]) && $field[self::TYPE] == 'checkbox') { + $field['value'] = false; + } else { + $field['value'] = ''; + } + + // translate params + foreach ([self::LABEL, self::TITLE, self::PLACEHOLDER] as $key) { + if (isset($field[$key])) { + $res = $this->_ci->phraseslib->getPhrases(self::PHRASES_APP_NAME, getUserLanguage(), $field[$key], null, null, 'no'); + if (hasData($res)) + $field[$key] = current(getData($res))->text; + } + } + + // check write permissions + $field['disabled'] = !$this->_ci->permissionlib->hasAtLeastOne( + $field[self::REQUIRED_PERMISSIONS_PARAMETER], + self::PERMISSION_TABLE_METHOD, + self::PERMISSION_TYPE_WRITE + ); + + // set listValues for dropdowns + if (isset($field[self::LIST_VALUES])) { + if (isset($field[self::LIST_VALUES]['enum'])) { + $field['options'] = $field[self::LIST_VALUES]['enum']; + } elseif (isset($field[self::LIST_VALUES]['sql'])) { + $res = $this->_ci->UDFModel->execReadOnlyQuery($field[self::LIST_VALUES]['sql']); + $field['options'] = hasData($res) ? getData($res) : []; + } + } + + // add to array + $fields[] = $field; + } + return success($fields); + } + + /** + * Gets a validation config array for CI form_validation + * + * @param DB_Model $targetModel + * @param array (optional) $filter + * + * @return stdClass + */ + public function getCiValidations($targetModel, $filter = null) + { + $result = $this->getDefinitionForModel($targetModel); + if (isError($result)) + return $result; + $definitions = getData($result); + + $result = []; + foreach ($definitions as $def) { + if ($filter && !isset($filter[$def['name']])) + continue; + $validations = []; + if (isset($def['requiredPermissions'])) + $validations[] = 'has_write_permissions[' . implode(',', $def['requiredPermissions']) . ']'; + if (isset($def['required'])) + $validations[] = 'required'; + if (isset($def['validation'])) { + if (isset($def['validation']['max-value'])) + $validations[] = 'less_than_equal_to[' . $def['validation']['max-value'] . ']'; + if (isset($def['validation']['min-value'])) + $validations[] = 'greater_than_equal_to[' . $def['validation']['min-value'] . ']'; + if (isset($def['validation']['max-length'])) + $validations[] = 'max_length[' . $def['validation']['max-length'] . ']'; + if (isset($def['validation']['min-length'])) + $validations[] = 'min_length[' . $def['validation']['min-length'] . ']'; + if (isset($def['validation']['regex']) && is_array($def['validation']['regex'])) { + foreach ($def['validation']['regex'] as $regex) { + if ($regex['language'] == 'php') { + $validations[] = 'regex_match[' . $regex['expression'] . ']'; + } + } + } + } + if ($validations) + $result[] = [ + 'field' => $def['name'], + 'label' => $def['title'], + 'rules' => $validations + ]; + } + return success($result); + } + // ------------------------------------------------------------------------------------------------- // Private methods // diff --git a/public/js/components/Udf/Udf.js b/public/js/components/Udf/Udf.js new file mode 100644 index 000000000..e9cc3c60a --- /dev/null +++ b/public/js/components/Udf/Udf.js @@ -0,0 +1,156 @@ +import { CoreFetchCmpt } from '../Fetch.js'; +import FormInput from '../Form/Input.js'; + + +export default { + components: { + CoreFetchCmpt, + FormInput + }, + emits: [ + 'update:modelValue', + 'load' + ], + props: { + // CodeIgniter model (eg: crm/prestudent) + ciModel: { + type: String, + required: true + }, + // Primarykey(s) of the record (eg: {prestudent_id: 12345}) + pk: { + type: Object, + required: true + }, + // The values as associative array + modelValue: Object, + // Show only fields with a name that exists in the filter + filter: [String, Array] + }, + data() { + return { + fields: [], + backupModelValue: {} + }; + }, + computed: { + filterArray() { + if (!this.filter || Array.isArray(this.filter)) + return this.filter; + return [this.filter]; + }, + filteredFields() { + if (!this.filterArray) + return this.fields; + return this.fields.filter(el => this.filterArray.includes(el.name)); + }, + filteredValues() { + return this.filteredFields.reduce((r,e) => (r[e.name] = this.internModelValue[e.name], r), {}); + }, + internModelValue: { + get() { + return this.modelValue || this.backupModelValue; + }, + set(value) { + this.backupModelValue = value; + this.$emit('update:modelValue', value); + this.originalValues + } + } + }, + watch: { + pk(n, o) { + if (!this.$refs.fetch) + return; // NOTE(chris): no initial load yet + + if (Object.keys(o).length == Object.keys(n).length + && Object.keys(o).every(key => n.hasOwnProperty(key) && o[key] === n[key])) + return; // NOTE(chris): old and new are the same + + this.$nextTick(this.$refs.fetch.fetchData); + } + }, + methods: { + loadF(params) { + // TODO(chris): move to fhcapi.factory + return this.$fhcApi + .post('/api/frontend/v1/udf/load/' + params.ciModel, params.pk); + }, + init(result) { + const fields = result.map(el => { + switch (el.type) { + case 'textfield': + el.type = 'text'; + break; + case 'date': + el.type = 'Datepicker'; + el.clearable = el.hasOwnProperty('clearable') ? el.clearable : false + el.autoApply = el.hasOwnProperty('autoApply') ? el.autoApply : true + el.enableTimePicker = el.hasOwnProperty('enableTimePicker') ? el.enableTimePicker : false + el.format = el.hasOwnProperty('format') ? el.format : "dd.MM.yyyy" + el.previewFormat = el.hasOwnProperty('previewFormat') ? el.previewFormat : "dd.MM.yyyy" + el.teleport = el.hasOwnProperty('teleport') ? el.teleport : true + break; + case 'multipledropdown': + el.multiple = true; + case 'dropdown': + el.type = 'select'; + el.options = el.options.map(item => { + if (Array.isArray(item)) + return { + id: item[0], + description: item[1] + }; + if (typeof item === 'object') + return item; + return { + id: item, + description: item + }; + }); + break; + } + return el; + }); + const values = fields.reduce((a,c) => { + a[c.name] = c.value; + return a; + }, {}); + + this.internModelValue = {...this.internModelValue, ...values}; + this.fields = fields; + this.$emit('load', values); + } + }, + template: ` +
+ + + +
` +} \ No newline at end of file From c6f4fbb0b864835555b58583f68ebfe99a1f0093 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 28 Mar 2024 15:39:19 +0100 Subject: [PATCH 5/5] Use Udf Component --- .../api/frontend/v1/stv/Student.php | 588 ++++++++++++++++++ .../Studentenverwaltung/Details/Details.js | 39 +- .../Stv/Studentenverwaltung/List/New.js | 15 +- 3 files changed, 622 insertions(+), 20 deletions(-) create mode 100644 application/controllers/api/frontend/v1/stv/Student.php diff --git a/application/controllers/api/frontend/v1/stv/Student.php b/application/controllers/api/frontend/v1/stv/Student.php new file mode 100644 index 000000000..101d34863 --- /dev/null +++ b/application/controllers/api/frontend/v1/stv/Student.php @@ -0,0 +1,588 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +use \DateTime as DateTime; + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about a Student + * Listens to ajax post calls to change the Student data + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + */ +class Student extends FHCAPI_Controller +{ + /** + * Calls the parent's constructor and prepares libraries and phrases + */ + public function __construct() + { + // TODO(chris): permissions + parent::__construct([ + 'get' => ['admin:r', 'assistenz:r'], + 'save' => ['admin:w', 'assistenz:w'], + 'check' => ['admin:w', 'assistenz:w'], + 'add' => ['admin:w', 'assistenz:w'] + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load language phrases + $this->loadPhrases([ + 'ui' + ]); + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + /** + * Get details for a prestudent + * + * @param string $prestudent_id + * @return void + */ + public function get($prestudent_id) + { + $studiensemester_kurzbz = $this->variablelib->getVar('semester_aktuell'); + + $this->load->model('crm/Prestudent_model', 'PrestudentModel'); + + $this->PrestudentModel->addSelect('p.*'); + $this->PrestudentModel->addSelect('s.student_uid'); + $this->PrestudentModel->addSelect('matrikelnr'); + $this->PrestudentModel->addSelect('b.aktiv'); + $this->PrestudentModel->addSelect('v.semester'); + $this->PrestudentModel->addSelect('v.verband'); + $this->PrestudentModel->addSelect('v.gruppe'); + $this->PrestudentModel->addSelect('b.alias'); + + if (defined('ACTIVE_ADDONS') && strpos(ACTIVE_ADDONS, 'bewerbung') !== false) { + $this->PrestudentModel->addSelect( + "( + SELECT kontakt + FROM public.tbl_kontakt + WHERE kontakttyp='email' + AND person_id=p.person_id + AND zustellung + ORDER BY kontakt_id + LIMIT 1 + ) AS email_privat", + false + ); + } + + $this->PrestudentModel->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT'); + $this->PrestudentModel->addJoin('public.tbl_benutzer b', 'student_uid = uid', 'LEFT'); + $this->PrestudentModel->addJoin( + 'public.tbl_studentlehrverband v', + 'b.uid = v.student_uid AND v.studiensemester_kurzbz = ' . $this->PrestudentModel->escape($studiensemester_kurzbz), + 'LEFT' + ); + $this->PrestudentModel->addJoin('public.tbl_person p', 'p.person_id = tbl_prestudent.person_id'); + + $result = $this->PrestudentModel->loadWhere(['prestudent_id' => $prestudent_id]); + + #$student = $this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + $student = $result->retval; + + if (!$student) + return show_404(); + + $this->terminateWithSuccess(current($student)); + } + + /** + * Saves data to a prestudent + * + * @param string $prestudent_id + * @return void + */ + public function save($prestudent_id) + { + $studiensemester_kurzbz = $this->variablelib->getVar('semester_aktuell'); + + $this->load->model('person/Person_model', 'PersonModel'); + $this->load->model('crm/Student_model', 'StudentModel'); + $this->load->model('crm/Prestudent_model', 'PrestudentModel'); + $this->load->model('education/Studentlehrverband_model', 'StudentlehrverbandModel'); + + $this->load->library('form_validation'); + + $this->form_validation->set_rules('gebdatum', 'Geburtsdatum', 'is_valid_date'); + + $this->form_validation->set_rules('semester', 'Semester', 'integer'); + + $this->load->library('UDFLib'); + + $result = $this->udflib->getCiValidations($this->PersonModel, $this->input->post()); + + #$fieldValidations = $this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result)); + $fieldvalidations = $result->retval; + + $this->form_validation->set_rules($fieldvalidations); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $result = $this->StudentModel->loadWhere(['prestudent_id' => $prestudent_id]); + + #$student = $this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + $student = $result->retval; + + $uid = $student ? current($student)->student_uid : null; + + $result = $this->PrestudentModel->loadWhere(['prestudent_id' => $prestudent_id]); + + #$person = $this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result)); + $person = $result->retval; + + $person_id = $person ? current($person)->person_id : null; + + + $array_allowed_props_lehrverband = ['verband', 'semester', 'gruppe']; + $update_lehrverband = array(); + foreach ($array_allowed_props_lehrverband as $prop) { + $val = $this->input->post($prop); + if ($val !== null) { + $update_lehrverband[$prop] = $val; + } + } + + $array_allowed_props_person = [ + 'anrede', + 'bpk', + 'titelpre', + 'titelpost', + 'nachname', + 'vorname', + 'vornamen', + 'wahlname', + 'gebdatum', + 'gebort', + 'geburtsnation', + 'svnr', + 'ersatzkennzeichen', + 'staatsbuergerschaft', + 'matr_nr', + 'sprache', + 'geschlecht', + 'familienstand', + 'foto', + 'anmerkung', + 'homepage' + ]; + + // add UDFs + $result = $this->udflib->getDefinitionForModel($this->PersonModel); + + #$definitions = $this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + $definitions = $result->retval; + + foreach ($definitions as $def) + $array_allowed_props_person[] = $def['name']; + + $update_person = array(); + foreach ($array_allowed_props_person as $prop) { + $val = $this->input->post($prop); + if ($val !== null) { + $update_person[$prop] = $val; + } + } + + $array_allowed_props_student = ['matrikelnr']; + $update_student = array(); + foreach ($array_allowed_props_student as $prop) { + $val = $this->input->post($prop); + if ($val !== null) { + $update_student[$prop] = $val; + } + } + + // Check PKs + if (count($update_lehrverband) + count($update_student) && $uid === null) { + // TODO(chris): phrase + $this->terminateWithValidationErrors(['' => "Kein/e StudentIn vorhanden!"]); + } + if (count($update_person) && $person_id === null) { + // TODO(chris): phrase + $this->terminateWithValidationErrors(['' => "Keine Person vorhanden!"]); + } + + // Do Updates + if (count($update_lehrverband)) { + $result = $this->StudentlehrverbandModel->update([ + 'studiensemester_kurzbz' => $studiensemester_kurzbz, + 'student_uid' => $uid + ], $update_lehrverband); + #$this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + if (count($update_person)) { + $result = $this->PersonModel->update( + $person_id, + $update_person + ); + #$this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + + if (count($update_student)) { + $result = $this->StudentModel->update( + [$uid], + $update_student + ); + #$this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + $this->terminateWithSuccess(array_fill_keys(array_merge( + array_keys($update_lehrverband), + array_keys($update_person), + array_keys($update_student) + ), '')); + } + + public function check() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('gebdatum', 'Geburtsdatum', 'callback_isValidDate', [ + 'isValidDate' => $this->p->t('ui', 'error_invalid_date') + ]); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $vorname = $this->input->post('vorname'); + $nachname = $this->input->post('nachname'); + $gebdatum = $this->input->post('gebdatum'); + + if (!$vorname && !$nachname && !$gebdatum) + $this->terminateWithValidationErrors(['At least one of vorname, nachname or gebdatum must be set']); + + $this->load->model('person/Person_model', 'PersonModel'); + + if ($gebdatum) + $this->PersonModel->db->where('gebdatum', (new DateTime($gebdatum))->format('Y-m-d')); + if ($vorname && $nachname) { + $this->PersonModel->db->or_group_start(); + $this->PersonModel->db->where('LOWER(nachname)', 'LOWER(' . $this->PersonModel->db->escape($nachname) . ')', false); + $this->PersonModel->db->where('LOWER(vorname)', 'LOWER(' . $this->PersonModel->db->escape($vorname) . ')', false); + $this->PersonModel->db->group_end(); + } elseif ($nachname) { + $this->PersonModel->db->or_where('LOWER(nachname)', 'LOWER(' . $this->PersonModel->escape($nachname) . ')', false); + } + + $result = $this->PersonModel->load(); + + #$data = $this->getDataOrTerminateWithError($result); + if (isError($result)) + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + $data = $result->retval; + + $this->terminateWithSuccess($data); + } + + public function add() + { + if (!$this->input->post('person_id')) { + if (!isset($_POST['address']) || !is_array($_POST['address'])) + $_POST['address'] = []; + $_POST['address']['func'] = 1; + } + if ($this->input->post('incoming')) { + $_POST['ausbildungssemester'] = 0; + } + + $this->load->library('form_validation'); + + $this->form_validation->set_rules('nachname', 'Nachname', 'callback_requiredIfNotPersonId', [ + 'requiredIfNotPersonId' => $this->p->t('ui', 'error_required') + ]); + $this->form_validation->set_rules('geschlecht', 'Geschlecht', 'callback_requiredIfNotPersonId', [ + 'requiredIfNotPersonId' => $this->p->t('ui', 'error_required') + ]); + $this->form_validation->set_rules('gebdatum', 'Geburtsdatum', 'callback_isValidDate', [ + 'isValidDate' => $this->p->t('ui', 'error_invalid_date') + ]); + $this->form_validation->set_rules('address[func]', 'Address', 'required|integer|less_than[2]|greater_than[-2]'); + $this->form_validation->set_rules('address[plz]', 'PLZ', 'callback_requiredIfAddressFunc', [ + 'requiredIfAddressFunc' => $this->p->t('ui', 'error_required') + ]); + $this->form_validation->set_rules('address[gemeinde]', 'Gemeinde', 'callback_requiredIfAddressFunc', [ + 'requiredIfAddressFunc' => $this->p->t('ui', 'error_required') + ]); + $this->form_validation->set_rules('address[ort]', 'Ort', 'callback_requiredIfAddressFunc', [ + 'requiredIfAddressFunc' => $this->p->t('ui', 'error_required') + ]); + $this->form_validation->set_rules('address[address]', 'Adresse', 'callback_requiredIfAddressFunc', [ + 'requiredIfAddressFunc' => $this->p->t('ui', 'error_required') + ]); + $this->form_validation->set_rules('email', 'E-Mail', 'valid_email'); + $this->form_validation->set_rules('studiengang_kz', 'Studiengang', 'required'); + $this->form_validation->set_rules('studiensemester_kurzbz', 'Studiensemester', 'required'); + $this->form_validation->set_rules('ausbildungssemester', 'Ausbildungssemester', 'required|integer|less_than[9]|greater_than[-1]'); + // TODO(chris): validate studienplan with studiengang, semester and orgform? + // TODO(chris): validate person_id, studiengang_kz, studiensemester_kurzbz, orgform_kurzbz, nation, gemeinde, ort, geschlecht? + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + // TODO(chris): This should be in a library + $this->load->model('crm/Prestudent_model', 'PrestudentModel'); + $this->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel'); + + $this->db->trans_start(); + + $result = $this->addInteressent(); + + $this->db->trans_complete(); + + if ($this->db->trans_status() === FALSE) + $this->terminateWithError('TODO(chris): TEXT', self::ERROR_TYPE_GENERAL); + + $this->terminateWithSuccess($result); + } + + protected function addInteressent() + { + // Person anlegen wenn nötig + $person_id = $this->input->post('person_id'); + if (!$person_id) { + $this->load->model('person/Person_model', 'PersonModel'); + + $data = [ + 'nachname' => $this->input->post('nachname'), + 'insertamum' => date('c'), + 'insertvon' => getAuthUID(), + 'zugangscode' => uniqid(), + 'aktiv' => true + ]; + if ($this->input->post('anrede')) + $data['anrede'] = $this->input->post('anrede'); + if ($this->input->post('titelpre')) + $data['titelpre'] = $this->input->post('titelpre'); + if ($this->input->post('titelpost')) + $data['titelpost'] = $this->input->post('titelpost'); + if ($this->input->post('vorname')) + $data['vorname'] = $this->input->post('vorname'); + if ($this->input->post('vornamen')) + $data['vornamen'] = $this->input->post('vornamen'); + if ($this->input->post('wahlname')) + $data['wahlname'] = $this->input->post('wahlname'); + if ($this->input->post('geschlecht')) + $data['geschlecht'] = $this->input->post('geschlecht'); + if ($this->input->post('gebdatum')) + $data['gebdatum'] = (new DateTime($this->input->post('datum_obj')))->format('Y-m-d'); + if ($this->input->post('geburtsnation')) + $data['geburtsnation'] = $this->input->post('geburtsnation'); + if ($this->input->post('staatsbuergerschaft')) + $data['staatsbuergerschaft'] = $this->input->post('staatsbuergerschaft'); + + $result = $this->PersonModel->insert($data); + if (isError($result)) + return $result; + $person_id = getData($result); + } + + // Addresse anlegen + $anlegen = $this->input->post('address[func]'); + if ($anlegen) { + $this->load->model('person/Adresse_model', 'AdresseModel'); + + $data = [ + 'nation' => $this->input->post('address[nation]'), + 'strasse' => $this->input->post('address[address]'), + 'plz' => $this->input->post('address[plz]'), + 'ort' => $this->input->post('address[ort]'), + 'gemeinde' => $this->input->post('address[gemeinde]'), + 'typ' => 'h', + 'zustelladresse' => true, + ]; + if ($anlegen < 0) { // Überschreiben + $this->AdresseModel->addOrder('zustelladresse', 'DESC'); + $this->AdresseModel->addOrder('sort'); + $result = $this->AdresseModel->loadWhere([ + 'person_id' => $person_id + ]); + if (isError($result)) + return $result; + if (hasData($result)) { + $address = current(getData($result)); + + $data['updateamum'] = date('c'); + $data['updatevon'] = getAuthUID(); + + $result = $this->AdresseModel->update($address->adresse_id, $data); + if (isError($result)) + return $result; + } else { + //Wenn keine Adrese vorhanden ist dann eine neue Anlegen + $anlegen = 1; + $data['heimatadresse'] = true; + } + } + if ($anlegen > 0) { + $data['person_id'] = $person_id; + $data['insertamum'] = date('c'); + $data['insertvon'] = getAuthUID(); + if (!isset($data['heimatadresse'])) + $data['heimatadresse'] = !$this->input->post('person_id'); + + $result = $this->AdresseModel->insert($data); + if (isError($result)) + return $result; + } + } + + // Kontaktdaten + $kontaktdaten = []; + foreach (['email', 'telefon', 'mobil'] as $k) { + $v = $this->input->post($k); + if ($v) + $kontaktdaten[$k] = $v; + } + if (count($kontaktdaten)) { + $this->load->model('person/Kontakt_model', 'KontaktModel'); + + foreach ($kontaktdaten as $typ => $kontakt) { + $data = [ + 'person_id' => $person_id, + 'kontakttyp' => $typ, + 'kontakt' => $kontakt, + 'zustellung' => true, + 'insertamum' => date('c'), + 'insertvon' => getAuthUID() + ]; + $result = $this->KontaktModel->insert($data); + if (isError($result)) + return $result; + } + } + + // Prestudent anlegen + $data = [ + 'aufmerksamdurch_kurzbz' => 'k.A.', + 'person_id' => $person_id, + 'studiengang_kz' => $this->input->post('studiengang_kz'), + 'ausbildungcode' => $this->input->post('letzteausbildung'), + 'anmerkung' => $this->input->post('anmerkungen'), + 'reihungstestangetreten' => false, + 'bismelden' => true + ]; + $ausbildungsart = $this->input->post('ausbildungsart'); + if ($ausbildungsart) + $data['anmerkung'] .= ' Ausbildungsart:' . $ausbildungsart; + // Incomings und ausserordentliche sind bei Meldung nicht förderrelevant + $incoming = $this->input->post('incoming'); + if ($incoming || substr($data['studiengang_kz'], 0, 1) == '9') + $data['foerderrelevant'] = false; + // Wenn die Person schon im System erfasst ist, dann die ZGV des Datensatzes uebernehmen + $this->PrestudentModel->addOrder('zgvmas_code'); + $this->PrestudentModel->addOrder('zgv_code', 'DESC'); + $this->PrestudentModel->addLimit(1); + $result = $this->PrestudentModel->loadWhere([ + 'person_id' => $person_id + ]); + if (isError($result)) + return $result; + if (hasData($result)) { + $prestudent = current(getData($result)); + if ($prestudent->zgv_code) { + $data['zgv_code'] = $prestudent->zgv_code; + $data['zgvort'] = $prestudent->zgvort; + $data['zgvdatum'] = $prestudent->zgvdatum; + + $data['zgvmas_code'] = $prestudent->zgvmas_code; + $data['zgvmaort'] = $prestudent->zgvmaort; + $data['zgvmadatum'] = $prestudent->zgvmadatum; + } + } + // Prestudent speichern + $result = $this->PrestudentModel->insert($data); + if (isError($result)) + return $result; + $prestudent_id = getData($result); + + // Prestudent Rolle Anlegen + $data = [ + 'prestudent_id' => $prestudent_id, + 'status_kurzbz' => $incoming ? 'Incoming' : 'Interessent', + 'studiensemester_kurzbz' => $this->input->post('studiensemester_kurzbz'), + 'ausbildungssemester' => $this->input->post('ausbildungssemester') ?: 0, + 'orgform_kurzbz' => $this->input->post('orgform_kurzbz') ?: null, + 'studienplan_id' => $this->input->post('studienplan_id') ?: null, + 'datum' => date('Y-m-d'), + 'insertamum' => date('c'), + 'insertvon' => getAuthUID() + ]; + $result = $this->PrestudentstatusModel->insert($data); + if (isError($result)) + return $result; + + if ($incoming) { + // TODO(chris): IMPLEMENT! + //Matrikelnummer und UID generieren + //Benutzerdatensatz anlegen + //Studentendatensatz anlegen + //StudentLehrverband anlegen + } + + // TODO(chris): DEBUG + /*$result = $this->PrestudentModel->loadWhere([ + 'pestudent_id' => 1 + ]); + if (isError($result)) { + return $result; + }*/ + + return success(true); + } + + public function requiredIfNotPersonId($value) + { + if (isset($_POST['person_id'])) + return true; + return !!$value; + } + + public function requiredIfAddressFunc($value) + { + if (!$_POST['address']['func']) + return true; + return !!$value; + } +} diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Details.js b/public/js/components/Stv/Studentenverwaltung/Details/Details.js index 0c988179f..7866f1c97 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Details.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Details.js @@ -1,14 +1,17 @@ import {CoreRESTClient} from '../../../../RESTClient.js'; -import FormForm from '../../../Form/Form.js'; +import CoreForm from '../../../Form/Form.js'; import FormInput from '../../../Form/Input.js'; import FormUploadImage from '../../../Form/Upload/Image.js'; +import CoreUdf from '../../../Udf/Udf.js'; + export default { components: { - FormForm, + CoreForm, FormInput, - FormUploadImage + FormUploadImage, + CoreUdf }, inject: { showBpk: { @@ -39,6 +42,7 @@ export default { }, data() { return { + test: {udf_viaf: 'TEST'}, familienstaende: { "": "--keine Auswahl--", "g": "geschieden", @@ -49,6 +53,7 @@ export default { original: null, data: null, changed: {}, + udfChanges: false, studentIn: null, gebDatumIsValid: false, gebDatumIsInvalid: false @@ -90,25 +95,33 @@ export default { }, methods: { updateStudent(n) { - CoreRESTClient - .get('components/stv/Student/get/' + n.prestudent_id) - .then(result => result.data) + // TODO(chris): move to fhcapi.factory + this.$fhcApi + .get('api/frontend/v1/stv/student/get/' + n.prestudent_id) .then(result => { - this.data = result; + this.data = result.data; if (!this.data.familienstand) this.data.familienstand = ''; - this.original = {...this.data}; + this.original = {...(this.original || {}), ...this.data}; }) .catch(this.$fhcAlert.handleSystemError); }, save() { + if (!this.changedLength) + return; + + this.$refs.form.clearValidation(); this.$refs.form - .send(CoreRESTClient.post('components/stv/Student/save/' + this.modelValue.prestudent_id, this.changed)) + .post('api/frontend/v1/stv/student/save/' + this.modelValue.prestudent_id, this.changed) .then(result => { this.original = {...this.data}; this.changed = {}; + this.$refs.form.setFeedback(true, result.data); }) - .catch(this.$fhcAlert.handleSystemError); + .catch(this.$fhcAlert.handleSystemError) + }, + udfsLoaded(udfs) { + this.original = {...(this.original || {}), ...udfs}; } }, created() { @@ -117,7 +130,7 @@ export default { //TODO(chris): Phrasen //TODO(chris): Geburtszeit? Anzahl der Kinder? template: ` - +
@@ -235,6 +248,7 @@ export default { :enable-time-picker="false" format="dd.MM.yyyy" preview-format="dd.MM.yyyy" + teleport > Loading... +
StudentIn @@ -441,5 +456,5 @@ export default { Loading...
-
` + ` }; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/List/New.js b/public/js/components/Stv/Studentenverwaltung/List/New.js index 7bba76e51..acc9781de 100644 --- a/public/js/components/Stv/Studentenverwaltung/List/New.js +++ b/public/js/components/Stv/Studentenverwaltung/List/New.js @@ -106,18 +106,16 @@ export default { return; this.abortController.suggestions = new AbortController(); - CoreRESTClient - .post('components/stv/student/check', { + // TODO(chris): move to fhcapi.factory + this.$fhcapi + .post('api/frontend/v1/stv/student/check', { vorname: this.formData.vorname, nachname: this.formData.nachname, gebdatum: this.formData.gebdatum }, { signal: this.abortController.suggestions.signal }) - .then(result => CoreRESTClient.getData(result.data) || []) - .then(result => { - this.suggestions = result; - }) + .then(result => this.suggestions = result.data) .catch(error => { // NOTE(chris): repeat request if (error.code != "ERR_CANCELED") @@ -191,13 +189,14 @@ export default { if (data.studiensemester_kurzbz === undefined) data.studiensemester_kurzbz = this.studiensemesterKurzbz; + // TODO(chris): move to fhcapi.factory this.$refs.form - .send(CoreRESTClient.post('components/stv/student/add', data)) + .send('api/frontend/v1/stv/student/add', data) .then(result => { this.$fhcAlert.alertSuccess('Gespeichert'); this.$refs.modal.hide(); }) - .catch(console.error); + .catch(this.$fhcAlert.handleSystemError); } }, created() {