From 091d8509f033812d8653b6d3b75263f363886914 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Tue, 13 Feb 2024 14:53:58 +0100 Subject: [PATCH 01/19] Send E-Mails after Wiederholer deadline --- application/controllers/jobs/AntragJob.php | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/application/controllers/jobs/AntragJob.php b/application/controllers/jobs/AntragJob.php index 0663be94b..393e67399 100644 --- a/application/controllers/jobs/AntragJob.php +++ b/application/controllers/jobs/AntragJob.php @@ -322,6 +322,39 @@ class AntragJob extends JOB_Controller $this->logError(getError($result)); } else { $count++; + + $datum_kp = new DateTime($prestudent->datum); + $dataMail = array( + 'name'=> trim($prestudent->vorname . ' '. $prestudent->nachname), + 'vorname' => $prestudent->vorname, + 'nachname' => $prestudent->nachname, + 'pers_kz'=> $prestudent->matrikelnr, + 'stg' => $prestudent->bezeichnung, + 'lvbezeichnung' => $prestudent->lvbezeichnung, + 'datum_kp' => $datum_kp->format('d.m.Y'), + 'studiensemester'=> $prestudent->studiensemester_kurzbz, + 'Orgform'=> $prestudent->orgform, + 'prestudent_id' => $prestudent->prestudent_id, + 'fristablauf' => $dateDeadline->format('d.m.Y') + ); + + $email = $this->StudentModel->getEmailFH($this->StudentModel->getUID($prestudent->prestudent_id)); + // Mail to Student + if (!sendSanchoMail('Sancho_Mail_Antrag_W_DL_Stud', $dataMail, $email, 'Wiederholung: Frist abgelaufen')) { + $this->logWarning("Failed to send Notification to " . $email); + } + + $result = $this->StudiengangModel->load($prestudent->studiengang_kz); + if (!hasData($result)) { + $this->logWarning('No Studiengang found'); + continue; + } + $studiengang = current(getData($result)); + $email = $studiengang->email; + // Mail to Assistenz + if (!sendSanchoMail('Sancho_Mail_Antrag_W_DL_Assist', $dataMail, $email, 'Wiederholung: Frist abgelaufen')) { + $this->logWarning("Failed to send Notification to " . $email); + } } } } From 75e05d6eedcc8fad9abc3ac14af4661a3caab20d Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Fri, 16 Feb 2024 09:27:42 +0100 Subject: [PATCH 02/19] PDF Download korrektur --- .../components/Studierendenantrag/Leitung/Table.js | 14 +++++++++++++- rdf/AntragAbmeldungStgl.xml.php | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/public/js/components/Studierendenantrag/Leitung/Table.js b/public/js/components/Studierendenantrag/Leitung/Table.js index 68f93cb11..1addc35fb 100644 --- a/public/js/components/Studierendenantrag/Leitung/Table.js +++ b/public/js/components/Studierendenantrag/Leitung/Table.js @@ -234,7 +234,19 @@ export default { container.className = "d-flex gap-2"; - if ((data.typ == 'Abmeldung' || data.typ == 'AbmeldungStgl' || data.typ == 'Unterbrechung') && (data.status == 'Genehmigt' || data.status == 'Beeinsprucht' || data.status == 'EinspruchAbgelehnt' || data.status == 'EmailVersandt')) { + let allowed_status_for_download = []; + switch (data.typ) { + case 'Abmeldung': + allowed_status_for_download = ['Genehmigt']; + break; + case 'AbmeldungStgl': + allowed_status_for_download = ['Genehmigt', 'Beeinsprucht', 'EinspruchAbgelehnt', 'Abgemeldet']; + break; + case 'Unterbrechung': + allowed_status_for_download = ['Genehmigt', 'EmailVersandt']; + break; + } + if (allowed_status_for_download.includes(data.status)) { // NOTE(chris): Download PDF let button = document.createElement('a'); // NOTE(chris): phrasen in attribues don't work if they are not preloaded diff --git a/rdf/AntragAbmeldungStgl.xml.php b/rdf/AntragAbmeldungStgl.xml.php index 4511be232..7288e4415 100644 --- a/rdf/AntragAbmeldungStgl.xml.php +++ b/rdf/AntragAbmeldungStgl.xml.php @@ -14,7 +14,7 @@ if (isset($_REQUEST["xmlformat"]) && $_REQUEST["xmlformat"] == "xml") $id = $_GET['id']; $where = " WHERE studierendenantrag_id = " . $db->db_add_param($id) . " - AND a.typ = 'AbmeldungStgl' AND campus.get_status_studierendenantrag(a.studierendenantrag_id) IN ('Genehmigt', 'Beeinsprucht', 'EinspruchAbgelehnt');"; + AND a.typ = 'AbmeldungStgl' AND campus.get_status_studierendenantrag(a.studierendenantrag_id) IN ('Genehmigt', 'Beeinsprucht', 'EinspruchAbgelehnt', 'Abgemeldet');"; $not_found_error = 'Studierendenantrag not found'. $id; } elseif(isset($_GET['uid']) && isset($_GET['prestudent_id'])) { $uid = $_GET['uid']; @@ -26,7 +26,7 @@ if (isset($_REQUEST["xmlformat"]) && $_REQUEST["xmlformat"] == "xml") $prestudent_id = (array_filter($prestudent_id, 'strlen')); $where = " WHERE a.prestudent_id in (" . $db->db_implode4SQL($prestudent_id) . ") - AND a.typ = 'AbmeldungStgl' AND campus.get_status_studierendenantrag(a.studierendenantrag_id) IN ('Genehmigt', 'Beeinsprucht', 'EinspruchAbgelehnt');"; + AND a.typ = 'AbmeldungStgl' AND campus.get_status_studierendenantrag(a.studierendenantrag_id) IN ('Genehmigt', 'Beeinsprucht', 'EinspruchAbgelehnt', 'Abgemeldet');"; $not_found_error = 'Studierendenantrag not found for: ' . implode(',', $uid); } else die('wrong parameters'); From ab2fc117976240596c02c44321c38df0d7a68daa Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Fri, 16 Feb 2024 11:44:27 +0100 Subject: [PATCH 03/19] PDF Download Wiederholung Deadline --- .../views/lehre/Antrag/Student/List.php | 35 +- content/fas.xul.php | 16 + content/student/studentoverlay.js.php | 96 +++++ locale/de-AT/fas.dtd | 8 + .../Studierendenantrag/Leitung/Table.js | 3 + rdf/AntragWiederholung.xml.php | 70 ++++ system/phrasesupdate.php | 19 + system/vorlage_zip/AntragWiederholung.odt | Bin 0 -> 37277 bytes system/xsl/AntragWiederholung.xsl | 378 ++++++++++++++++++ 9 files changed, 619 insertions(+), 6 deletions(-) create mode 100644 rdf/AntragWiederholung.xml.php create mode 100644 system/vorlage_zip/AntragWiederholung.odt create mode 100644 system/xsl/AntragWiederholung.xsl diff --git a/application/views/lehre/Antrag/Student/List.php b/application/views/lehre/Antrag/Student/List.php index 4ca99f951..768d5e9e2 100644 --- a/application/views/lehre/Antrag/Student/List.php +++ b/application/views/lehre/Antrag/Student/List.php @@ -101,12 +101,35 @@ $this->load->view( - typ != Studierendenantrag_model::TYP_WIEDERHOLUNG && in_array($antrag->status, [ - Studierendenantragstatus_model::STATUS_APPROVED, - Studierendenantragstatus_model::STATUS_OBJECTED, - Studierendenantragstatus_model::STATUS_OBJECTION_DENIED, - Studierendenantragstatus_model::STATUS_REMINDERSENT - ])) { ?> + typ) { + case Studierendenantrag_model::TYP_ABMELDUNG: + $allowed = [ + Studierendenantragstatus_model::STATUS_APPROVED + ]; + break; + case Studierendenantrag_model::TYP_ABMELDUNG_STGL: + $allowed = [ + Studierendenantragstatus_model::STATUS_APPROVED, + Studierendenantragstatus_model::STATUS_OBJECTED, + Studierendenantragstatus_model::STATUS_OBJECTION_DENIED, + Studierendenantragstatus_model::STATUS_DEREGISTERED + ]; + break; + case Studierendenantrag_model::TYP_UNTERBRECHUNG: + $allowed = [ + Studierendenantragstatus_model::STATUS_APPROVED, + Studierendenantragstatus_model::STATUS_REMINDERSENT + ]; + break; + case Studierendenantrag_model::TYP_WIEDERHOLUNG: + $allowed = [ + Studierendenantragstatus_model::STATUS_DEREGISTERED + ]; + break; + } + if (in_array($antrag->status, $allowed)) { ?> typ == Studierendenantrag_model::TYP_WIEDERHOLUNG && $antrag->status == Studierendenantragstatus_model::STATUS_APPROVED) { ?> diff --git a/content/fas.xul.php b/content/fas.xul.php index abe7f613e..29515eae6 100644 --- a/content/fas.xul.php +++ b/content/fas.xul.php @@ -141,7 +141,9 @@ foreach($addon_obj->result as $addon) + + @@ -518,6 +520,13 @@ foreach($addon_obj->result as $addon) command = "menu-dokumente-antrag-abmeldung:command" accesskey = "&menu-dokumente-antrag-abmeldung.accesskey;" /> + result as $addon) command = "menu-dokumente-antrag-unterbrechung:command" accesskey = "&menu-dokumente-antrag-unterbrechung.accesskey;" /> + diff --git a/content/student/studentoverlay.js.php b/content/student/studentoverlay.js.php index 1bf68c809..11a4d862f 100644 --- a/content/student/studentoverlay.js.php +++ b/content/student/studentoverlay.js.php @@ -6225,6 +6225,54 @@ function StudentPrintAntragAbmeldung(event) window.open('/content/pdfExport.php?xml=AntragAbmeldung.xml.php&xsl=AntragAbmeldung&uid='+student_uid+'&prestudent_id='+prestudent_id+'&output='+output,'AntragAbmeldung', 'height=200,width=350,left=0,top=0,hotkeys=0,resizable=yes,status=no,scrollbars=yes,toolbar=no,location=no,menubar=no,dependent=yes'); } +function StudentPrintAntragAbmeldungStgl(event) +{ + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + var tree = document.getElementById('student-tree'); + + + if (tree.currentIndex==-1) + return alert('Bitte eine/n Studierende/n auswaehlen'); + + //Uids aller markierten Studenten holen + var start = new Object(); + var end = new Object(); + var numRanges = tree.view.selection.getRangeCount(); + var prestudent_id= ''; + var student_uid= ''; + + for (var t = 0; t < numRanges; t++) + { + tree.view.selection.getRangeAt(t,start,end); + for (var v = start.value; v <= end.value; v++) + { + var col = tree.columns ? tree.columns["student-treecol-prestudent_id"] : "student-treecol-prestudent_id"; + var prestudentId=tree.view.getCellText(v,col); + prestudent_id += ';'+prestudentId; + + col = tree.columns ? tree.columns["student-treecol-uid"] : "student-treecol-uid"; + var uid=tree.view.getCellText(v,col); + student_uid += ';'+uid; + + } + } + + if (event.shiftKey) + { + var output='odt'; + } + else if (event.ctrlKey) + { + var output='doc'; + } + else + { + var output='pdf'; + } + + window.open('/content/pdfExport.php?xml=AntragAbmeldungStgl.xml.php&xsl=AntragAbmeldungStgl&uid='+student_uid+'&prestudent_id='+prestudent_id+'&output='+output,'AntragAbmeldung', 'height=200,width=350,left=0,top=0,hotkeys=0,resizable=yes,status=no,scrollbars=yes,toolbar=no,location=no,menubar=no,dependent=yes'); +} + function StudentPrintAntragUnterbrechung(event) { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); @@ -6273,6 +6321,54 @@ function StudentPrintAntragUnterbrechung(event) window.open('/content/pdfExport.php?xml=AntragUnterbrechung.xml.php&xsl=AntragUnterbrechung&uid='+student_uid+'&prestudent_id='+prestudent_id+'&output='+output,'AntragUnterbrechung', 'height=200,width=350,left=0,top=0,hotkeys=0,resizable=yes,status=no,scrollbars=yes,toolbar=no,location=no,menubar=no,dependent=yes'); } +function StudentPrintAntragWiederholung(event) +{ + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + var tree = document.getElementById('student-tree'); + + + if (tree.currentIndex==-1) + return alert('Bitte eine/n Studierende/n auswaehlen'); + + //Uids aller markierten Studenten holen + var start = new Object(); + var end = new Object(); + var numRanges = tree.view.selection.getRangeCount(); + var prestudent_id= ''; + var student_uid= ''; + + for (var t = 0; t < numRanges; t++) + { + tree.view.selection.getRangeAt(t,start,end); + for (var v = start.value; v <= end.value; v++) + { + var col = tree.columns ? tree.columns["student-treecol-prestudent_id"] : "student-treecol-prestudent_id"; + var prestudentId=tree.view.getCellText(v,col); + prestudent_id += ';'+prestudentId; + + col = tree.columns ? tree.columns["student-treecol-uid"] : "student-treecol-uid"; + var uid=tree.view.getCellText(v,col); + student_uid += ';'+uid; + + } + } + + if (event.shiftKey) + { + var output='odt'; + } + else if (event.ctrlKey) + { + var output='doc'; + } + else + { + var output='pdf'; + } + + window.open('/content/pdfExport.php?xml=AntragWiederholung.xml.php&xsl=AntragWiederholung&uid='+student_uid+'&prestudent_id='+prestudent_id+'&output='+output,'AntragUnterbrechung', 'height=200,width=350,left=0,top=0,hotkeys=0,resizable=yes,status=no,scrollbars=yes,toolbar=no,location=no,menubar=no,dependent=yes'); +} + // **** // * Erstellt den Ausbildungsvertrag fuer einen oder mehrere Studenten // **** diff --git a/locale/de-AT/fas.dtd b/locale/de-AT/fas.dtd index 3fd593833..b5598d85b 100644 --- a/locale/de-AT/fas.dtd +++ b/locale/de-AT/fas.dtd @@ -219,10 +219,18 @@ + + + + + + + + diff --git a/public/js/components/Studierendenantrag/Leitung/Table.js b/public/js/components/Studierendenantrag/Leitung/Table.js index 1addc35fb..d3957537b 100644 --- a/public/js/components/Studierendenantrag/Leitung/Table.js +++ b/public/js/components/Studierendenantrag/Leitung/Table.js @@ -245,6 +245,9 @@ export default { case 'Unterbrechung': allowed_status_for_download = ['Genehmigt', 'EmailVersandt']; break; + case 'Wiederholung': + allowed_status_for_download = ['Abgemeldet']; + break; } if (allowed_status_for_download.includes(data.status)) { // NOTE(chris): Download PDF diff --git a/rdf/AntragWiederholung.xml.php b/rdf/AntragWiederholung.xml.php new file mode 100644 index 000000000..939fdf513 --- /dev/null +++ b/rdf/AntragWiederholung.xml.php @@ -0,0 +1,70 @@ +db_add_param($id) . " + AND a.typ = 'Wiederholung' AND campus.get_status_studierendenantrag(a.studierendenantrag_id) = 'Abgemeldet';"; + $not_found_error = 'Studierendenantrag not found'. $id; + } elseif(isset($_GET['uid']) && isset($_GET['prestudent_id'])) { + $uid = $_GET['uid']; + $uid = explode(';', $uid); + $uid = (array_filter($uid, 'strlen')); + + $prestudent_id = $_GET['prestudent_id']; + $prestudent_id = explode(';', $prestudent_id); + $prestudent_id = (array_filter($prestudent_id, 'strlen')); + + $where = " WHERE a.prestudent_id in (" . $db->db_implode4SQL($prestudent_id) . ") + AND a.typ = 'Wiederholung' AND campus.get_status_studierendenantrag(a.studierendenantrag_id) = 'Abgemeldet';"; + $not_found_error = 'Studierendenantrag not found for: ' . implode(',', $uid); + } else + die('wrong parameters'); +} +else + die('Format not supported'); + + +$query = " + SELECT stg.bezeichnung, bezeichnung_mehrsprachig[(SELECT index FROM public.tbl_sprache WHERE sprache=" . $db->db_add_param(getSprache(), FHC_STRING) . ")], studierendenantrag_id, matrikelnr, studienjahr_kurzbz, a.studiensemester_kurzbz, vorname, nachname, studiengang_kz, public.get_absem_prestudent(a.prestudent_id, NULL) AS semester, (SELECT pt.text FROM system.tbl_phrase p JOIN system.tbl_phrasentext pt USING(phrase_id) WHERE p.category=" . $db->db_add_param('studierendenantrag', FHC_STRING) . " AND p.phrase=" . $db->db_add_param('grund_Wiederholung_deadline', FHC_STRING) . " AND pt.sprache=" . $db->db_add_param(getSprache(), FHC_STRING) . " LIMIT 1) AS grund + FROM + campus.tbl_studierendenantrag a + JOIN public.tbl_student USING (prestudent_id) + JOIN public.tbl_benutzer ON tbl_student.student_uid=uid + JOIN public.tbl_person USING (person_id) + JOIN public.tbl_studiengang stg USING (studiengang_kz) + JOIN public.tbl_studiensemester USING (studiensemester_kurzbz) + LEFT JOIN public.tbl_prestudentstatus pss ON (pss.prestudent_id = a.prestudent_id AND pss.studiensemester_kurzbz=a.studiensemester_kurzbz AND pss.status_kurzbz=get_rolle_prestudent(a.prestudent_id, a.studiensemester_kurzbz)) + LEFT JOIN lehre.tbl_studienplan plan USING (studienplan_id) + JOIN bis.tbl_orgform ON (tbl_orgform.orgform_kurzbz = COALESCE(plan.orgform_kurzbz, pss.orgform_kurzbz, stg.orgform_kurzbz))" . $where; + + +if (!$db->db_query($query) || !$db->db_num_rows()) + die('' . $not_found_error . ''); + +?> + + + db_fetch_object()) { ?> + + vorname . ' ' . $row->nachname); ?>]]> + bezeichnung; ?>]]> + bezeichnung_mehrsprachig; ?>]]> + matrikelnr; ?>]]> + studienjahr_kurzbz; ?>]]> + studiensemester_kurzbz; ?>]]> + semester; ?>]]> + grund; ?>]]> + + + diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index d8825ee0e..b96d85233 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -22688,6 +22688,25 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'studierendenantrag', + 'phrase' => 'grund_Wiederholung_deadline', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'negative kommissionelle Beurteilung und keine fristgerechte Bekanntgabe der Wiederholung des Studienjahres', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'negative assessment by the committee and no timely announcement of the repetition of the academic year', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), // Personalverwaltung begin array( 'app' => 'core', diff --git a/system/vorlage_zip/AntragWiederholung.odt b/system/vorlage_zip/AntragWiederholung.odt new file mode 100644 index 0000000000000000000000000000000000000000..2e11ca60ae49eb67a843672bf6e2c87e5d31517c GIT binary patch literal 37277 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 literal 0 HcmV?d00001 diff --git a/system/xsl/AntragWiederholung.xsl b/system/xsl/AntragWiederholung.xsl new file mode 100644 index 000000000..aa6a28073 --- /dev/null +++ b/system/xsl/AntragWiederholung.xsl @@ -0,0 +1,378 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Studiengang: + + + + + + Organisationsform: + + + + + + + + Abmeldung vom Studium durch Studiengang + + + + + + + Name der*des Studierenden + + + + + + + + + + + Personenkennzeichen + + + + + + + + + + Studienjahr + + + + + + + + + + Studiensemester + + + + + + + + + + Semester + + + + + + + + + + Grund der Abmeldung: + + + + + + + + + + + + + + + Wir weisen Sie darauf hin, dass Ihr FHTW Account noch 21 Tage aktiv ist. Wir bitten Sie, alle benötigte Dateien (Zeugnisse, Studienerfolgsbestätigungen, Studienbestätigungen, etc.) innerhalb dieses Zeitraums herunterzuladen. Für die Ausstellung von Duplikaten fallen nach Inaktivsetzung des CIS-Accounts Kosten an. + + + Sie sind gem. Ausbildungsvertrag verpflichtet, unverzüglich alle zur Verfügung gestellten Gerätschaften, Bücher, Schlüssel und sonstige Materialien zurückzugeben. + + Bei Abmeldung vor dem 01.09. bzw. 15.02. und bereits eingezahltem Studienbeitrag für das kommende Semester: Wir informieren Sie darüber, dass der Studienbeitrag für das kommende Semester von Ihnen zurückgefordert werden kann. Bitte geben Sie uns dafür innerhalb von 14 Tagen Ihre Bankdaten an folgende E-Mail-Adresse bekannt: billing@technikum-wien.at. + + + + + + + + \ No newline at end of file From 658212a6eb75354cc00b346cb859f300468305c0 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Fri, 16 Feb 2024 13:28:26 +0100 Subject: [PATCH 04/19] Show Antrag to Student when finished --- application/libraries/AntragLib.php | 10 ++++++++++ application/models/education/Pruefung_model.php | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/application/libraries/AntragLib.php b/application/libraries/AntragLib.php index 298c3652f..f094440e2 100644 --- a/application/libraries/AntragLib.php +++ b/application/libraries/AntragLib.php @@ -1413,6 +1413,16 @@ class AntragLib if (hasData($result)) return success(-1); + $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere([ + 'prestudent_id' => $prestudent_id, + 'typ' => Studierendenantrag_model::TYP_WIEDERHOLUNG, + 'campus.get_status_studierendenantrag(studierendenantrag_id)' => Studierendenantragstatus_model::STATUS_DEREGISTERED + ]); + if (isError($result)) + return $result; + if (hasData($result)) + return success(-1); + return success(0); } $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere(['prestudent_id' => $prestudent_id]); diff --git a/application/models/education/Pruefung_model.php b/application/models/education/Pruefung_model.php index 4debc3e28..1f7df2366 100644 --- a/application/models/education/Pruefung_model.php +++ b/application/models/education/Pruefung_model.php @@ -180,7 +180,11 @@ class Pruefung_model extends DB_Model $this->addJoin('bis.tbl_orgform o', 'COALESCE(plan.orgform_kurzbz, pss.orgform_kurzbz, g.orgform_kurzbz)=o.orgform_kurzbz'); $this->addJoin('campus.tbl_studierendenantrag a', 'ps.prestudent_id=a.prestudent_id and a.typ=' . $this->escape(Studierendenantrag_model::TYP_WIEDERHOLUNG), 'LEFT'); + $this->db->group_start(); $this->db->where_in("get_rolle_prestudent(ps.prestudent_id, null)", $this->config->item('antrag_prestudentstatus_whitelist')); + // NOTE(chris): If the status of the Antrag is DEREGISTERED the prestudentstatus must be Abbrecher which is not in the whitelist + $this->db->or_where("campus.get_status_studierendenantrag(a.studierendenantrag_id)", Studierendenantragstatus_model::STATUS_DEREGISTERED); + $this->db->group_end(); $this->db->where("g.aktiv", true); From c034efb8b0c1f78daefbab470c30b35ba2b8cbeb Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Wed, 21 Feb 2024 09:19:56 +0100 Subject: [PATCH 05/19] Unterbrechung bei laufender Unterbrechung --- .../components/Antrag/Unterbrechung.php | 4 +- application/libraries/AntragLib.php | 120 +++++++++--------- .../education/Studierendenantrag_model.php | 94 ++++++++++++++ .../Studierendenantrag/Form/Unterbrechung.js | 4 +- 4 files changed, 155 insertions(+), 67 deletions(-) diff --git a/application/controllers/components/Antrag/Unterbrechung.php b/application/controllers/components/Antrag/Unterbrechung.php index 33bd0035d..efc2c3a88 100644 --- a/application/controllers/components/Antrag/Unterbrechung.php +++ b/application/controllers/components/Antrag/Unterbrechung.php @@ -86,7 +86,7 @@ class Unterbrechung extends FHC_Controller $data = getData($result); - $data->studiensemester = $this->antraglib->getSemesterForUnterbrechung($data->studiensemester_kurzbz); + $data->studiensemester = $this->antraglib->getSemesterForUnterbrechung($prestudent_id, $data->studiensemester_kurzbz); $this->outputJsonSuccess($data); } @@ -136,7 +136,7 @@ class Unterbrechung extends FHC_Controller $datum_wiedereinstieg = $this->input->post('datum_wiedereinstieg'); $dms_id = null; - $result = $this->antraglib->getPrestudentUnterbrechungsBerechtigt($prestudent_id); + $result = $this->antraglib->getPrestudentUnterbrechungsBerechtigt($prestudent_id, $studiensemester, $datum_wiedereinstieg); if (isError($result)) { return $this->outputJsonError(['db' => getError($result)]); } diff --git a/application/libraries/AntragLib.php b/application/libraries/AntragLib.php index f094440e2..d8ef224db 100644 --- a/application/libraries/AntragLib.php +++ b/application/libraries/AntragLib.php @@ -1302,7 +1302,7 @@ class AntragLib * retval -2 means other Antrag pending; * retval -3 means in blacklist stg */ - public function getPrestudentUnterbrechungsBerechtigt($prestudent_id, $studiensemester_kurzbz = null) + public function getPrestudentUnterbrechungsBerechtigt($prestudent_id, $studiensemester_kurzbz = null, $datum_wiedereinstieg = null) { $result = $this->_ci->PrestudentModel->load($prestudent_id); if (isError($result)) @@ -1320,18 +1320,10 @@ class AntragLib if (!hasData($result)) return success(0); $result = current(getData($result)); + $prestudent_stdsem = $result->studiensemester_kurzbz; $datumStatus = $result->datum; - if (!in_array($result->status_kurzbz, $this->_ci->config->item('antrag_prestudentstatus_whitelist'))) { - $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere([ - 'prestudent_id' => $prestudent_id, - 'typ' => Studierendenantrag_model::TYP_UNTERBRECHUNG, - 'campus.get_status_studierendenantrag(studierendenantrag_id)' => Studierendenantragstatus_model::STATUS_APPROVED - ]); - if (isError($result)) - return $result; - if (hasData($result)) - return success(-1); - + if (!in_array($result->status_kurzbz, $this->_ci->config->item('antrag_prestudentstatus_whitelist')) + && $result->status_kurzbz != 'Unterbrecher') { return success(0); } $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere(['prestudent_id' => $prestudent_id]); @@ -1339,7 +1331,8 @@ class AntragLib return $result; if (!hasData($result)) return success(1); - $result= getData($result); + + $result = getData($result); foreach ($result as $antrag) { if ($antrag->typ == Studierendenantrag_model::TYP_ABMELDUNG || $antrag->typ == Studierendenantrag_model::TYP_ABMELDUNG_STGL) @@ -1349,11 +1342,11 @@ class AntragLib elseif($antrag->status == Studierendenantragstatus_model::STATUS_APPROVED && $antrag->datum > $datumStatus) return success(-2); } - if ($studiensemester_kurzbz && $antrag->typ == Studierendenantrag_model::TYP_UNTERBRECHUNG) + if ($antrag->typ == Studierendenantrag_model::TYP_UNTERBRECHUNG) { - // NOTE(chris): check if this is an old or canceled one - if ($antrag->studiensemester_kurzbz == $studiensemester_kurzbz && $antrag->status != Studierendenantragstatus_model::STATUS_CANCELLED) - return success(-1); + // NOTE(chris): Ignore canceled ones + if ($antrag->status == Studierendenantragstatus_model::STATUS_CANCELLED) + continue; } if ($antrag->typ == Studierendenantrag_model::TYP_WIEDERHOLUNG) { @@ -1362,6 +1355,17 @@ class AntragLib } } + if (!$studiensemester_kurzbz) { + $sems = $this->getSemesterForUnterbrechung($prestudent_id, $prestudent_stdsem); + if (!count(array_filter($sems, function ($item) { + return !$item['disabled']; + }))) + return success(-1); + } else { + if ($this->_ci->StudierendenantragModel->hasRunningUnterbrechungBetween($prestudent_id, $studiensemester_kurzbz, $datum_wiedereinstieg)) + return success(-1); + } + return success(1); } @@ -1552,53 +1556,43 @@ class AntragLib return success($resultDetails); } - public function getSemesterForUnterbrechung($studiensemester_kurzbz) + /** + * Rearrange the free semester slots for a new Unterbrechung + * + * @param integer $prestudent_id + * @param string $studiensemester_kurzbz + * + * @return array + */ + public function getSemesterForUnterbrechung($prestudent_id, $studiensemester_kurzbz) { - $this->_ci->load->model('organisation/Studiensemester_model', 'StudiensemesterModel'); - - $semester = []; - - $result = $this->_ci->StudiensemesterModel->getNextFrom($studiensemester_kurzbz); - if (!hasData($result)) - return $semester; - $nextSem = current(getData($result)); - - $semester[0] = [ - 'studiensemester_kurzbz' => $studiensemester_kurzbz, - 'wiedereinstieg' => [$nextSem] - ]; - - $result = $this->_ci->StudiensemesterModel->getNextFrom($nextSem->studiensemester_kurzbz); - if (!hasData($result)) - return $semester; - - $currSemester = current(getData($result)); - $followingSemester = [$currSemester]; - - $max = $this->_ci->config->item('unterbrecher_semester_max_length'); - if(!$max || $max < 1) - $max = 2; - - for ($i = 1; $i < $max; $i++) { - $result = $this->_ci->StudiensemesterModel->getNextFrom($currSemester->studiensemester_kurzbz); - if (!hasData($result)) - break; - $currSemester = current(getData($result)); - $followingSemester[] = $currSemester; - } - - $semester[1] = [ - 'studiensemester_kurzbz' => $nextSem->studiensemester_kurzbz, - 'wiedereinstieg' => $followingSemester - ]; - - //remove last Semester of the array - array_pop($followingSemester); - - foreach ($followingSemester as $sem) - $semester[0]['wiedereinstieg'][] = $sem; - - return $semester; + $result = $this->_ci->StudierendenantragModel->getFreeSlotsForUnterbrechung($prestudent_id, $studiensemester_kurzbz); + if (isError($result)) + return []; + $result = getData($result); + if (!$result) + return []; + return array_reduce($result, function ($carry, $item) { + if (!isset($carry[$item->von])) + $carry[$item->von] = [ + 'studiensemester_kurzbz' => $item->von, + 'wiedereinstieg' => [], + 'disabled' => true + ]; + + $carry[$item->von]['wiedereinstieg'][] = [ + 'studiensemester_kurzbz' => $item->bis, + 'start' => $item->ende, + 'disabled' => (boolean)$item->studierendenantrag_id + ]; + + if ($carry[$item->von]['disabled'] && !$item->studierendenantrag_id) { + $carry[$item->von]['disabled'] = false; + } + + return $carry; + }, []); + return $result; } public function getAktivePrestudentenInStgs($studiengaenge, $query) diff --git a/application/models/education/Studierendenantrag_model.php b/application/models/education/Studierendenantrag_model.php index ea481ebef..9edc7ef91 100644 --- a/application/models/education/Studierendenantrag_model.php +++ b/application/models/education/Studierendenantrag_model.php @@ -287,4 +287,98 @@ class Studierendenantrag_model extends DB_Model return $this->loadWhere($where); } + + /** + * Checks if the Prestudent has an active Unterbrechung between + * the start of the given semester and the given enddate. + * If the enddate is omitted the end of the given semester is used. + * + * @param integer $prestudent_id + * @param string $studiensemester_kurzbz + * @param string $enddate (optional) + * + * @return boolean + */ + public function hasRunningUnterbrechungBetween($prestudent_id, $studiensemester, $enddate = null) + { + $start = '(SELECT start FROM public.tbl_studiensemester WHERE studiensemester_kurzbz=' . $this->db->escape($studiensemester) . ')'; + $end = $enddate + ? $this->db->escape($enddate) + : '(SELECT ende FROM public.tbl_studiensemester WHERE studiensemester_kurzbz=' . $this->db->escape($studiensemester) . ')'; + + $this->addJoin('public.tbl_studiensemester', 'studiensemester_kurzbz'); + $this->db->where([ + 'prestudent_id' => $prestudent_id, + 'typ' => Studierendenantrag_model::TYP_UNTERBRECHUNG, + 'campus.get_status_studierendenantrag(studierendenantrag_id) !=' => Studierendenantragstatus_model::STATUS_CANCELLED, + 'start < ' . $end => null, + 'datum_wiedereinstieg > ' . $start => null, + ]); + return (boolean)$this->db->count_all_results($this->dbTable); + } + + /** + * Gets free semester slots for a new Unterbrechung. + * + * @param integer $prestudent_id + * @param string $studiensemester_kurzbz (optional) + * + * @return stdClass + */ + public function getFreeSlotsForUnterbrechung($prestudent_id, $studiensemester = null) + { + $max_starters = 2; + $max_length = max( + 2, + (integer)$this->config->item('unterbrecher_semester_max_length') + ); + + + $subquery = ''; + if ($studiensemester) + $subquery = 'SELECT start FROM public.tbl_studiensemester WHERE studiensemester_kurzbz=?'; + else + $subquery = 'SELECT start FROM public.tbl_studiensemester WHERE studiensemester_kurzbz=public.get_stdsem_prestudent (?, null)'; + + $sql = "WITH numbered_sems AS ( + SELECT + a.studiensemester_kurzbz AS von, + b.studiensemester_kurzbz AS bis, + a.start AS start, + b.start AS ende, + ROW_NUMBER() OVER ( + PARTITION BY a.studiensemester_kurzbz + ORDER BY b.start + ) AS row_number + FROM public.tbl_studiensemester a + LEFT JOIN public.tbl_studiensemester b ON (b.start > a.ende) + ), + last_sems AS ( + SELECT * + FROM numbered_sems + WHERE numbered_sems.row_number <= ? + ) + SELECT s.von, s.bis, s.start, s.ende, studierendenantrag_id + FROM last_sems s + LEFT JOIN ( + SELECT studierendenantrag_id, start, datum_wiedereinstieg AS ende + FROM campus.tbl_studierendenantrag + LEFT JOIN public.tbl_studiensemester USING(studiensemester_kurzbz) + WHERE typ=? + AND campus.get_status_studierendenantrag(studierendenantrag_id) != ? + AND prestudent_id=? + ) a ON (s.start < a.ende AND s.ende > a.start) + WHERE s.start >= (" . $subquery . ") + ORDER BY s.start, s.ende + LIMIT ?;"; + + return $this->execQuery($sql, [ + $max_length, + self::TYP_UNTERBRECHUNG, + Studierendenantragstatus_model::STATUS_CANCELLED, + $prestudent_id, + $studiensemester ?: $prestudent_id, + $max_length * $max_starters + ]); + } } diff --git a/public/js/components/Studierendenantrag/Form/Unterbrechung.js b/public/js/components/Studierendenantrag/Form/Unterbrechung.js index a0e84af63..b0544dd3f 100644 --- a/public/js/components/Studierendenantrag/Form/Unterbrechung.js +++ b/public/js/components/Studierendenantrag/Form/Unterbrechung.js @@ -256,7 +256,7 @@ export default { :id="'studierendenantrag-form-abmeldung-' + uuid + '-stsem'" @input="currentWiedereinstieg = ''" > - @@ -280,7 +280,7 @@ export default {
From 4bbac3554050b547edc962323a9def5fb951e1d7 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Mon, 26 Feb 2024 11:56:29 +0100 Subject: [PATCH 06/19] Status colors --- public/js/components/Studierendenantrag/Form/Abmeldung.js | 3 ++- .../components/Studierendenantrag/Form/AbmeldungStgl.js | 5 +++-- .../components/Studierendenantrag/Form/Unterbrechung.js | 8 +++++--- .../js/components/Studierendenantrag/Form/Wiederholung.js | 6 ++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/public/js/components/Studierendenantrag/Form/Abmeldung.js b/public/js/components/Studierendenantrag/Form/Abmeldung.js index 513b8b542..3ee72c3c7 100644 --- a/public/js/components/Studierendenantrag/Form/Abmeldung.js +++ b/public/js/components/Studierendenantrag/Form/Abmeldung.js @@ -29,8 +29,9 @@ export default { switch (this.data.status) { case 'Erstellt': return 'info'; + case 'Zurueckgezogen': return 'danger'; case 'Genehmigt': return 'success'; - default: return 'info'; + default: return 'warning'; } }, loadUrl() { diff --git a/public/js/components/Studierendenantrag/Form/AbmeldungStgl.js b/public/js/components/Studierendenantrag/Form/AbmeldungStgl.js index aafff8ac6..a52f43eb2 100644 --- a/public/js/components/Studierendenantrag/Form/AbmeldungStgl.js +++ b/public/js/components/Studierendenantrag/Form/AbmeldungStgl.js @@ -29,9 +29,10 @@ export default { switch (this.data?.status) { case 'Erstellt': return 'info'; - case 'Genehmigt': + case 'Zurueckgezogen': return 'danger'; + case 'EinspruchAbgelehnt': case 'Abgemeldet': return 'success'; - default: return 'info'; + default: return 'warning'; } }, loadUrl() { diff --git a/public/js/components/Studierendenantrag/Form/Unterbrechung.js b/public/js/components/Studierendenantrag/Form/Unterbrechung.js index b0544dd3f..ee0d8eb00 100644 --- a/public/js/components/Studierendenantrag/Form/Unterbrechung.js +++ b/public/js/components/Studierendenantrag/Form/Unterbrechung.js @@ -37,9 +37,11 @@ export default { switch (this.data.status) { case 'Erstellt': return 'info'; - case 'Genehmigt': return 'success'; - case 'Zurueckgezogen': return 'danger'; - default: return 'info'; + case 'Zurueckgezogen': + case 'Abgelehnt': return 'danger'; + case 'Genehmigt': + case 'EmailVersandt': return 'success'; + default: return 'warning'; } }, loadUrl() { diff --git a/public/js/components/Studierendenantrag/Form/Wiederholung.js b/public/js/components/Studierendenantrag/Form/Wiederholung.js index c1c668ecb..60026bb41 100644 --- a/public/js/components/Studierendenantrag/Form/Wiederholung.js +++ b/public/js/components/Studierendenantrag/Form/Wiederholung.js @@ -35,11 +35,13 @@ export default { statusSeverity() { switch (this.data.status) { - case 'Erstellt': return 'info'; + case 'Offen': + case 'Erstellt': + case 'ErsteAufforderungVersandt': return 'info'; case 'Genehmigt': return 'success'; case 'Verzichtet': case 'Abgemeldet': return 'danger'; - default: return 'info'; + default: return 'warning'; } }, loadUrl() { From 2d2098984a8a094e0a23c9af771a4c2ef70b9f4a Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 29 Feb 2024 08:34:58 +0100 Subject: [PATCH 07/19] Pause feature --- .../controllers/components/Antrag/Leitung.php | 90 +++++++++ .../components/Antrag/Wiederholung.php | 12 ++ application/controllers/jobs/AntragJob.php | 8 + application/libraries/AntragLib.php | 178 ++++++++++++++++-- .../models/education/Pruefung_model.php | 8 +- .../education/Studierendenantrag_model.php | 67 ++++++- .../Studierendenantragstatus_model.php | 157 +++++++++++++++ application/views/lehre/Antrag/Create.php | 4 +- .../views/lehre/Antrag/Student/List.php | 86 ++++++++- .../Studierendenantrag/Form/Abmeldung.js | 7 +- .../Studierendenantrag/Form/AbmeldungStgl.js | 7 +- .../Studierendenantrag/Form/Unterbrechung.js | 7 +- .../Studierendenantrag/Form/Wiederholung.js | 7 +- .../components/Studierendenantrag/Leitung.js | 40 ++++ .../Studierendenantrag/Leitung/Table.js | 87 ++++++++- .../27351_digitalisierung_formulare.php | 16 ++ system/phrasesupdate.php | 140 ++++++++++++++ 17 files changed, 876 insertions(+), 45 deletions(-) diff --git a/application/controllers/components/Antrag/Leitung.php b/application/controllers/components/Antrag/Leitung.php index 8d333d5b8..437030d08 100644 --- a/application/controllers/components/Antrag/Leitung.php +++ b/application/controllers/components/Antrag/Leitung.php @@ -116,6 +116,96 @@ class Leitung extends FHC_Controller $this->outputJsonSuccess($studierendenantrag_id); } + public function pauseAntrag() + { + $this->load->library('form_validation'); + + $_POST = json_decode($this->input->raw_input_stream, true); + + $this->form_validation->set_rules( + 'studierendenantrag_id', + 'Studierenden Antrag', + [ + 'required', + [ + 'isEntitledToPauseAntrag', + [$this->antraglib, 'isEntitledToPauseAntrag'] + ], + [ + 'antragCanBeManualPaused', + [$this->antraglib, 'antragCanBeManualPaused'] + ] + ], + [ + 'isEntitledToPauseAntrag' => $this->p->t('studierendenantrag', 'error_no_right'), + 'antragCanBeManualPaused' => $this->p->t( + 'studierendenantrag', + 'error_not_pauseable', + ['id' => $this->input->post('studierendenantrag_id')] + ) + ] + ); + + if ($this->form_validation->run() == false) + { + return $this->outputJsonError($this->form_validation->error_array()); + } + + $studierendenantrag_id = $this->input->post('studierendenantrag_id'); + + $result = $this->antraglib->pauseAntrag($studierendenantrag_id, getAuthUID()); + + if (isError($result)) + return $this->outputJsonError(['studierendenantrag_id' => getError($result)]); + + $this->outputJsonSuccess($studierendenantrag_id); + } + + public function unpauseAntrag() + { + $this->load->library('form_validation'); + + $_POST = json_decode($this->input->raw_input_stream, true); + + $this->form_validation->set_rules( + 'studierendenantrag_id', + 'Studierenden Antrag', + [ + 'required', + [ + 'isEntitledToUnpauseAntrag', + [$this->antraglib, 'isEntitledToUnpauseAntrag'] + ], + [ + 'antragCanBeManualUnpaused', + [$this->antraglib, 'antragCanBeManualUnpaused'] + ] + ], + [ + 'isEntitledToUnpauseAntrag' => $this->p->t('studierendenantrag', 'error_no_right'), + 'antragCanBeManualUnpaused' => $this->p->t( + 'studierendenantrag', + 'error_not_paused', + ['id' => $this->input->post('studierendenantrag_id')] + ) + ] + ); + + if ($this->form_validation->run() == false) + { + return $this->outputJsonError($this->form_validation->error_array()); + } + + $studierendenantrag_id = $this->input->post('studierendenantrag_id'); + + $result = $this->antraglib->unpauseAntrag($studierendenantrag_id, getAuthUID()); + + if (isError($result)) + return $this->outputJsonError(['studierendenantrag_id' => getError($result)]); + + $this->outputJsonSuccess($studierendenantrag_id); + } + public function objectAntrag() { $this->load->library('form_validation'); diff --git a/application/controllers/components/Antrag/Wiederholung.php b/application/controllers/components/Antrag/Wiederholung.php index 418d05f45..22f4f1bac 100644 --- a/application/controllers/components/Antrag/Wiederholung.php +++ b/application/controllers/components/Antrag/Wiederholung.php @@ -161,6 +161,18 @@ class Wiederholung extends FHC_Controller { return $this->outputJsonError(['db' => $this->p->t('studierendenantrag', 'error_no_student')]); } + elseif ($result == -1) + { + $result = $this->PrestudentstatusModel->getLastStatus($prestudent_id); + if (isError($result)) + return $this->outputJsonError(['db' => getError($result)]); + if (!hasData($result)) + return $this->outputJsonError(['db' => $this->p->t('studierendenantrag', 'error_no_prestudentstatus', [ + 'prestudent_id' => $prestudent_id + ])]); + if (!in_array(current(getData($result))->status_kurzbz, $this->_ci->config->item('antrag_prestudentstatus_whitelist'))) + return $this->outputJsonError(['db' => $this->p->t('studierendenantrag', 'error_no_student')]); + } elseif ($result == -2) { return $this->outputJsonError(['db' => $this->p->t('studierendenantrag', 'error_antrag_exists')]); diff --git a/application/controllers/jobs/AntragJob.php b/application/controllers/jobs/AntragJob.php index 393e67399..ce6345e4c 100644 --- a/application/controllers/jobs/AntragJob.php +++ b/application/controllers/jobs/AntragJob.php @@ -316,6 +316,10 @@ class AntragJob extends JOB_Controller } else { $deregisterStatus = getData($result); + $result = $this->antraglib->pauseAntrag($prestudent->studierendenantrag_id, Studierendenantragstatus_model::INSERTVON_DEREGISTERED); + if (isError($result)) + $this->logError(getError($result)); + $result = $this->prestudentlib->setAbbrecher($prestudent->prestudent_id, '', $insertvon); if (isError($result)) { $this->StudierendenantragstatusModel->delete($deregisterStatus); @@ -426,6 +430,10 @@ class AntragJob extends JOB_Controller else { $deregisterStatus = getData($result); + $result = $this->antraglib->pauseAntrag($antrag->studierendenantrag_id, Studierendenantragstatus_model::INSERTVON_DEREGISTERED); + if (isError($result)) + $this->logError(getError($result)); + $result = $this->prestudentlib->setAbbrecher( $antrag->prestudent_id, $antrag->studiensemester_kurzbz, diff --git a/application/libraries/AntragLib.php b/application/libraries/AntragLib.php index d8ef224db..732220f60 100644 --- a/application/libraries/AntragLib.php +++ b/application/libraries/AntragLib.php @@ -62,9 +62,11 @@ class AntragLib 'insertvon' => $insertvon ]); - // NOTE(chris): remove "preabbrecher" statusgrund for Stgl-Abmeldungen if set + // NOTE(chris): remove "preabbrecher" statusgrund and paused stati for sibling Anträge for Stgl-Abmeldungen if set $res = $this->_ci->StudierendenantragModel->load($antrag_id); if (hasData($res) && current(getData($res))->typ == Studierendenantrag_model::TYP_ABMELDUNG_STGL) { + $this->unpauseAntrag($antrag_id, Studierendenantragstatus_model::INSERTVON_ABMELDUNGSTGL); + $this->_ci->PrestudentstatusModel->addSelect('tbl_status_grund.statusgrund_kurzbz'); $res = $this->_ci->PrestudentstatusModel->getLastStatusWithStgEmail(current(getData($res))->prestudent_id, '', 'Student'); if (hasData($res) && current(getData($res))->statusgrund_kurzbz == 'preabbrecher') { @@ -83,6 +85,67 @@ class AntragLib return $result; } + /** + * @param integer $antrag_id + * @param string $insertvon + * + * @return stdClass + */ + public function pauseAntrag($antrag_id, $insertvon) + { + switch ($insertvon) { + case Studierendenantragstatus_model::INSERTVON_ABMELDUNGSTGL: + $result = $this->_ci->StudierendenantragstatusModel->stopAntraegeForAbmeldungStgl($antrag_id); + break; + case Studierendenantragstatus_model::INSERTVON_DEREGISTERED: + $result = $this->_ci->StudierendenantragstatusModel->stopAntraegeForAbbruchBy($antrag_id); + break; + default: + $result = $this->_ci->StudierendenantragstatusModel->insert([ + 'studierendenantrag_id' => $antrag_id, + 'studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_PAUSE, + 'insertvon' => $insertvon + ]); + break; + } + + return $result; + } + + /** + * @param integer $antrag_id + * @param string $insertvon + * + * @return stdClass + */ + public function unpauseAntrag($antrag_id, $insertvon) + { + if ($insertvon == Studierendenantragstatus_model::INSERTVON_DEREGISTERED) + return error($this->p->t('studierendenantrag', 'error_no_right')); + if ($insertvon == Studierendenantragstatus_model::INSERTVON_ABMELDUNGSTGL) { + return $this->_ci->StudierendenantragstatusModel->resumeAntraegeForAbmeldungStgl($antrag_id); + } + // NOTE(chris): get last status that is not pause + $this->_ci->StudierendenantragstatusModel->addOrder('insertamum'); + $this->_ci->StudierendenantragstatusModel->addLimit(1); + $result = $this->_ci->StudierendenantragstatusModel->loadWhere([ + 'studierendenantrag_id' => $antrag_id, + 'studierendenantrag_statustyp_kurzbz !=' => Studierendenantragstatus_model::STATUS_PAUSE + ]); + if (isError($result)) + return $result; + if (!hasData($result)) + return error($this->_ci->p->t('studierendenantrag', 'error_no_antragstatus', ['id' => $antrag_id])); + $status = current(getData($result)); + + $result = $this->_ci->StudierendenantragstatusModel->insert([ + 'studierendenantrag_id' => $antrag_id, + 'studierendenantrag_statustyp_kurzbz' => $status->studierendenantrag_statustyp_kurzbz, + 'insertvon' => $insertvon + ]); + return $result; + } + /** * NOTE(chris): permissions & verification must be handled outside * @@ -190,6 +253,10 @@ class AntragLib $vorlage ='Sancho_Mail_Antrag_A_Approve'; $subject = $this->_ci->p->t('studierendenantrag', 'mail_subject_A_Approve'); + $result = $this->pauseAntrag($studierendenantrag_id, Studierendenantragstatus_model::INSERTVON_DEREGISTERED); + if (isError($result)) + $errors[] = getError($result); + $result = $this->_ci->prestudentlib->setAbbrecher( $antrag->prestudent_id, $antrag->studiensemester_kurzbz, @@ -229,6 +296,10 @@ class AntragLib sendSanchoMail($vorlage, $data, $prestudent_status->email, $subject); } } else { // ($antrag->typ == Studierendenantrag_model::TYP_ABMELDUNG_STGL) + $result = $this->pauseAntrag($studierendenantrag_id, Studierendenantragstatus_model::INSERTVON_ABMELDUNGSTGL); + if (isError($result)) + $errors[] = getError($result); + $result = $this->_ci->PrestudentstatusModel->getLastStatusWithStgEmail($antrag->prestudent_id, '', 'Student'); if (isError($result)) { @@ -340,6 +411,10 @@ class AntragLib if (isError($result)) return $result; else { + $result = $this->pauseAntrag($studierendenantrag_id, Studierendenantragstatus_model::INSERTVON_DEREGISTERED); + // NOTE(chris): here we should have error handling but at the + // moment there is no way to notify the user for "soft" errors + $result = $this->_ci->prestudentlib->setAbbrecher( $antrag->prestudent_id, $antrag->studiensemester_kurzbz, @@ -1224,10 +1299,10 @@ class AntragLib * @param integer $prestudent_id * * @return \stdClass on success retval 0 means not a student; - * retval 1 means Berechtigt; - * retval -1 means has already an Antrag pending; - * retval -2 means other Antrag pending; - * retval -3 means in blacklist stg + * retval 1 means Berechtigt; + * retval -1 means has already an Antrag pending; + * retval -2 means other Antrag pending; + * retval -3 means in blacklist stg */ public function getPrestudentAbmeldeBerechtigt($prestudent_id) { @@ -1251,12 +1326,24 @@ class AntragLib if (!in_array($result->status_kurzbz, $this->_ci->config->item('antrag_prestudentstatus_whitelist_abmeldung'))) { $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere([ - 'prestudent_id' => $prestudent_id, - 'campus.get_status_studierendenantrag(studierendenantrag_id)' => Studierendenantragstatus_model::STATUS_APPROVED - ], [ - Studierendenantrag_model::TYP_ABMELDUNG, - Studierendenantrag_model::TYP_ABMELDUNG_STGL - ]); + 'prestudent_id' => $prestudent_id, + 's.studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_APPROVED + ], [ + Studierendenantrag_model::TYP_ABMELDUNG, + Studierendenantrag_model::TYP_ABMELDUNG_STGL + ]); + if (isError($result)) + return $result; + if (hasData($result)) + return success(-1); + + $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere([ + 'prestudent_id' => $prestudent_id, + 's.studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_PAUSE + ], [ + Studierendenantrag_model::TYP_ABMELDUNG, + Studierendenantrag_model::TYP_ABMELDUNG_STGL + ]); if (isError($result)) return $result; if (hasData($result)) @@ -1297,7 +1384,7 @@ class AntragLib * @param string $studiensemester_kurzbz (optional) * * @return \stdClass on success retval 0 means not a student; - * retval 1 means Berechtigt; + * retval 1 means Berechtigt; * retval -1 means has already an Antrag pending; * retval -2 means other Antrag pending; * retval -3 means in blacklist stg @@ -1410,7 +1497,7 @@ class AntragLib $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere([ 'prestudent_id' => $prestudent_id, 'typ' => Studierendenantrag_model::TYP_WIEDERHOLUNG, - 'campus.get_status_studierendenantrag(studierendenantrag_id)' => Studierendenantragstatus_model::STATUS_APPROVED + 's.studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_APPROVED ]); if (isError($result)) return $result; @@ -1420,7 +1507,17 @@ class AntragLib $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere([ 'prestudent_id' => $prestudent_id, 'typ' => Studierendenantrag_model::TYP_WIEDERHOLUNG, - 'campus.get_status_studierendenantrag(studierendenantrag_id)' => Studierendenantragstatus_model::STATUS_DEREGISTERED + 's.studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_DEREGISTERED + ]); + if (isError($result)) + return $result; + if (hasData($result)) + return success(-1); + + $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere([ + 'prestudent_id' => $prestudent_id, + 'typ' => Studierendenantrag_model::TYP_WIEDERHOLUNG, + 's.studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_PAUSE ]); if (isError($result)) return $result; @@ -1510,6 +1607,7 @@ class AntragLib $resultDetails->status = $resultAntrag->status; $resultDetails->statustyp = $resultAntrag->statustyp; + $resultDetails->status_insertvon = $resultAntrag->status_insertvon; $resultDetails->grund = $resultAntrag->grund; $resultDetails->studierendenantrag_id = $resultAntrag->studierendenantrag_id; $resultDetails->typ = $resultAntrag->typ; @@ -1522,7 +1620,7 @@ class AntragLib public function getDetailsForAntrag($studierendenantrag_id) { $where = [ - 'studierendenantrag_id' => $studierendenantrag_id + 's.studierendenantrag_id' => $studierendenantrag_id ]; $result = $this->_ci->StudierendenantragModel->loadWithStatusWhere($where); @@ -1899,6 +1997,26 @@ class AntragLib return $this->hasAccessToAntrag($antrag_id, 'student/studierendenantrag'); } + /** + * @param integer $antrag_id + * + * @return boolean + */ + public function isEntitledToPauseAntrag($antrag_id) + { + return ($this->hasAccessToAntrag($antrag_id, 'student/antragfreigabe') || $this->hasAccessToAntrag($antrag_id, 'student/studierendenantrag')); + } + + /** + * @param integer $antrag_id + * + * @return boolean + */ + public function isEntitledToUnpauseAntrag($antrag_id) + { + return $this->hasAccessToAntrag($antrag_id, 'student/studierendenantrag'); + } + /** * @param integer $antrag_id * @@ -1939,6 +2057,36 @@ class AntragLib return $this->hasAccessToAntrag($antrag_id, 'student/antragfreigabe'); } + /** + * @param integer $antrag_id + * + * @return boolean + */ + public function antragCanBeManualPaused($antrag_id) + { + $this->_ci->StudierendenantragModel->db->where_not_in('campus.get_status_studierendenantrag(studierendenantrag_id)', [ + Studierendenantragstatus_model::STATUS_DEREGISTERED, + Studierendenantragstatus_model::STATUS_APPROVED, + Studierendenantragstatus_model::STATUS_PAUSE + ]); + $result = $this->_ci->StudierendenantragModel->loadWhere([ + 'studierendenantrag_id' => $antrag_id, + 'typ' => Studierendenantrag_model::TYP_WIEDERHOLUNG + ]); + + return hasData($result); + } + + /** + * @param integer $antrag_id + * + * @return boolean + */ + public function antragCanBeManualUnpaused($antrag_id) + { + return $this->_ci->StudierendenantragModel->isManuallyPaused($antrag_id); + } + /** * @param integer $antrag_id * @param string|array $status diff --git a/application/models/education/Pruefung_model.php b/application/models/education/Pruefung_model.php index 1f7df2366..958ed54f2 100644 --- a/application/models/education/Pruefung_model.php +++ b/application/models/education/Pruefung_model.php @@ -180,12 +180,6 @@ class Pruefung_model extends DB_Model $this->addJoin('bis.tbl_orgform o', 'COALESCE(plan.orgform_kurzbz, pss.orgform_kurzbz, g.orgform_kurzbz)=o.orgform_kurzbz'); $this->addJoin('campus.tbl_studierendenantrag a', 'ps.prestudent_id=a.prestudent_id and a.typ=' . $this->escape(Studierendenantrag_model::TYP_WIEDERHOLUNG), 'LEFT'); - $this->db->group_start(); - $this->db->where_in("get_rolle_prestudent(ps.prestudent_id, null)", $this->config->item('antrag_prestudentstatus_whitelist')); - // NOTE(chris): If the status of the Antrag is DEREGISTERED the prestudentstatus must be Abbrecher which is not in the whitelist - $this->db->or_where("campus.get_status_studierendenantrag(a.studierendenantrag_id)", Studierendenantragstatus_model::STATUS_DEREGISTERED); - $this->db->group_end(); - $this->db->where("g.aktiv", true); $statusgruende = $this->config->item('status_gruende_wiederholer'); @@ -242,6 +236,8 @@ class Pruefung_model extends DB_Model $this->db->where("b.aktiv", true); + $this->db->where_in("get_rolle_prestudent(ps.prestudent_id, null)", $this->config->item('antrag_prestudentstatus_whitelist')); + if (is_array($status)) { if (in_array(null, $status)) { $status = array_filter($status); diff --git a/application/models/education/Studierendenantrag_model.php b/application/models/education/Studierendenantrag_model.php index 9edc7ef91..5c7ef3785 100644 --- a/application/models/education/Studierendenantrag_model.php +++ b/application/models/education/Studierendenantrag_model.php @@ -46,6 +46,8 @@ class Studierendenantrag_model extends DB_Model $this->addSelect('datum_wiedereinstieg'); $this->addSelect($this->dbTable . '.typ'); $this->addSelect('st.studierendenantrag_statustyp_kurzbz as status'); + $this->addSelect('s.insertvon as status_insertvon'); + $this->addSelect('s.insertamum as status_insertamum'); $this->addSelect('dms_id'); $this->addSelect('st.bezeichnung[(' . $sql . ')] as statustyp'); @@ -85,7 +87,8 @@ class Studierendenantrag_model extends DB_Model Studierendenantragstatus_model::STATUS_APPROVED, Studierendenantragstatus_model::STATUS_REJECTED, Studierendenantragstatus_model::STATUS_OBJECTION_DENIED, - Studierendenantragstatus_model::STATUS_DEREGISTERED + Studierendenantragstatus_model::STATUS_DEREGISTERED, + Studierendenantragstatus_model::STATUS_PAUSE ]); $this->db->or_group_start(); $this->db->where('s.studierendenantrag_statustyp_kurzbz', Studierendenantragstatus_model::STATUS_APPROVED); @@ -133,12 +136,17 @@ class Studierendenantrag_model extends DB_Model $lang = 'SELECT index FROM public.tbl_sprache WHERE sprache=' . $this->escape(getUserLanguage()); $this->addSelect('*'); - $this->addSelect('campus.get_status_studierendenantrag(studierendenantrag_id) status'); + $this->addSelect('s.studierendenantrag_statustyp_kurzbz status'); + $this->addSelect('s.insertvon status_insertvon'); $this->addSelect('t.bezeichnung[(' . $lang . ')] statustyp'); + $this->addJoin( + 'campus.tbl_studierendenantrag_status s', + 'campus.get_status_id_studierendenantrag(' . $this->dbTable . '.studierendenantrag_id)=s.studierendenantrag_status_id' + ); $this->addJoin( 'campus.tbl_studierendenantrag_statustyp t', - 'campus.get_status_studierendenantrag(studierendenantrag_id)=t.studierendenantrag_statustyp_kurzbz' + 's.studierendenantrag_statustyp_kurzbz=t.studierendenantrag_statustyp_kurzbz' ); if ($types && is_array($types)) { @@ -233,6 +241,7 @@ class Studierendenantrag_model extends DB_Model $this->addSelect($this->dbTable . '.datum_wiedereinstieg'); $this->addSelect($this->dbTable . '.grund'); $this->addSelect($this->dbTable . '.dms_id'); + $this->addSelect('s.insertvon AS status_insertvon'); $this->addSelect("(SELECT count(1) FROM campus.tbl_studierendenantrag_status WHERE studierendenantrag_id = " . $this->dbTable . ".studierendenantrag_id AND studierendenantrag_statustyp_kurzbz = 'Genehmigt') AS isapproved", false); $this->addJoin('public.tbl_prestudent p', 'prestudent_id', 'RIGHT'); @@ -240,9 +249,14 @@ class Studierendenantrag_model extends DB_Model $this->addJoin('public.tbl_prestudentstatus ps', 'ps.prestudent_id=p.prestudent_id AND ps.studiensemester_kurzbz=' . $this->dbTable . '.studiensemester_kurzbz AND ps.status_kurzbz=get_rolle_prestudent(p.prestudent_id, ' . $this->dbTable . '.studiensemester_kurzbz)', 'LEFT'); $this->addJoin('lehre.tbl_studienplan plan', 'studienplan_id', 'LEFT'); $this->addJoin('bis.tbl_orgform of', 'of.orgform_kurzbz=COALESCE(plan.orgform_kurzbz, ps.orgform_kurzbz, stg.orgform_kurzbz)'); + $this->addJoin( + 'campus.tbl_studierendenantrag_status s', + 'campus.get_status_id_studierendenantrag(' . $this->dbTable . '.studierendenantrag_id)=s.studierendenantrag_status_id', + 'LEFT' + ); $this->addJoin( 'campus.tbl_studierendenantrag_statustyp st', - 'campus.get_status_studierendenantrag(studierendenantrag_id)=st.studierendenantrag_statustyp_kurzbz', + 's.studierendenantrag_statustyp_kurzbz=st.studierendenantrag_statustyp_kurzbz', 'LEFT' ); @@ -381,4 +395,49 @@ class Studierendenantrag_model extends DB_Model $max_length * $max_starters ]); } + + /** + * Returns if an Antrag is manually paused + * + * @param integer $antrag_id + * + * @return boolean + */ + public function isManuallyPaused($antrag_id) + { + $this->addJoin( + 'campus.tbl_studierendenantrag_status s', + 'campus.get_status_id_studierendenantrag(' . $this->dbTable . '.studierendenantrag_id)=s.studierendenantrag_status_id' + ); + + $this->db->where([ + 's.studierendenantrag_id' => $antrag_id, + 's.studierendenantrag_statustyp_kurzbz' => Studierendenantragstatus_model::STATUS_PAUSE + ]); + + $this->db->group_start(); + $this->db->where_not_in('s.insertvon', [ + Studierendenantragstatus_model::INSERTVON_DEREGISTERED, + Studierendenantragstatus_model::INSERTVON_ABMELDUNGSTGL + ]); + $this->db->or_group_start(); + $this->db->where('s.insertvon', Studierendenantragstatus_model::INSERTVON_ABMELDUNGSTGL); + $this->db->where('1 !=', '( + SELECT COUNT(*)%2 + FROM campus.tbl_studierendenantrag_status i + WHERE i.studierendenantrag_id = s.studierendenantrag_id + AND i.insertamum > ( + SELECT ii.insertamum + FROM campus.tbl_studierendenantrag_status ii + WHERE ii.studierendenantrag_id = s.studierendenantrag_id + AND ii.insertvon <> ' . $this->escape(Studierendenantragstatus_model::INSERTVON_ABMELDUNGSTGL) . ' + ORDER BY ii.insertamum DESC + LIMIT 1 + ) + )', false); + $this->db->group_end(); + $this->db->group_end(); + + return hasData($this->load()); + } } diff --git a/application/models/education/Studierendenantragstatus_model.php b/application/models/education/Studierendenantragstatus_model.php index c134cc4ee..cf9cce1be 100644 --- a/application/models/education/Studierendenantragstatus_model.php +++ b/application/models/education/Studierendenantragstatus_model.php @@ -15,6 +15,10 @@ class Studierendenantragstatus_model extends DB_Model const STATUS_OBJECTED = 'Beeinsprucht'; const STATUS_OBJECTION_DENIED = 'EinspruchAbgelehnt'; const STATUS_DEREGISTERED = 'Abgemeldet'; + const STATUS_PAUSE = 'Pause'; + + const INSERTVON_ABMELDUNGSTGL = "AbmeldungStgl"; + const INSERTVON_DEREGISTERED = "Studienabbruch"; /** * Constructor @@ -49,4 +53,157 @@ class Studierendenantragstatus_model extends DB_Model return $this->loadWhere($where); } + + public function stopAntraegeForAbmeldungStgl($antrag_id) + { + $sql = 'INSERT INTO campus.tbl_studierendenantrag_status + (studierendenantrag_id, studierendenantrag_statustyp_kurzbz, insertvon, insertamum) + SELECT studierendenantrag_id, ?, ?, ( + SELECT insertamum + FROM campus.tbl_studierendenantrag_status + WHERE studierendenantrag_status_id = campus.get_status_id_studierendenantrag(?) + ) + FROM campus.tbl_studierendenantrag + WHERE prestudent_id = ( + SELECT prestudent_id + FROM campus.tbl_studierendenantrag + WHERE studierendenantrag_id = ? + ) + AND studierendenantrag_id <> ? + AND ( + ( + typ = ? + AND campus.get_status_studierendenantrag(studierendenantrag_id) IN ? + ) OR ( + typ = ? + AND campus.get_status_studierendenantrag(studierendenantrag_id) IN ? + ) OR ( + typ = ? + AND campus.get_status_studierendenantrag(studierendenantrag_id) IN ? + ) + )'; + + return $this->execQuery($sql, [ + self::STATUS_PAUSE, + self::INSERTVON_ABMELDUNGSTGL, + $antrag_id, + $antrag_id, + $antrag_id, + Studierendenantrag_model::TYP_ABMELDUNG, + [ + Studierendenantragstatus_model::STATUS_CREATED + ], + Studierendenantrag_model::TYP_UNTERBRECHUNG, + [ + Studierendenantragstatus_model::STATUS_CREATED + ], + Studierendenantrag_model::TYP_WIEDERHOLUNG, + [ + Studierendenantragstatus_model::STATUS_REQUESTSENT_1, + Studierendenantragstatus_model::STATUS_REQUESTSENT_2, + Studierendenantragstatus_model::STATUS_CREATED, + Studierendenantragstatus_model::STATUS_LVSASSIGNED, + Studierendenantragstatus_model::STATUS_PAUSE + ], + ]); + } + + public function resumeAntraegeForAbmeldungStgl($antrag_id) + { + $sql = 'INSERT INTO campus.tbl_studierendenantrag_status + (studierendenantrag_id, studierendenantrag_statustyp_kurzbz, insertvon, insertamum) + SELECT studierendenantrag_id, ( + SELECT studierendenantrag_statustyp_kurzbz + FROM campus.tbl_studierendenantrag_status s + WHERE s.studierendenantrag_id=a.studierendenantrag_id + AND campus.get_status_id_studierendenantrag(a.studierendenantrag_id) <> studierendenantrag_status_id + ORDER BY insertamum DESC + LIMIT 1 + ), ?, ( + SELECT insertamum + FROM campus.tbl_studierendenantrag_status + WHERE studierendenantrag_status_id = campus.get_status_id_studierendenantrag(?) + ) + FROM campus.tbl_studierendenantrag a + WHERE prestudent_id = ( + SELECT prestudent_id + FROM campus.tbl_studierendenantrag + WHERE studierendenantrag_id = ? + ) + AND typ <> ? + AND campus.get_status_studierendenantrag(studierendenantrag_id) = ? + '; + + return $this->execQuery($sql, [ + self::INSERTVON_ABMELDUNGSTGL, + $antrag_id, + $antrag_id, + Studierendenantrag_model::TYP_ABMELDUNG_STGL, + Studierendenantragstatus_model::STATUS_PAUSE + ]); + } + + public function stopAntraegeForAbbruchBy($antrag_id) + { + $sql = 'INSERT INTO campus.tbl_studierendenantrag_status + (studierendenantrag_id, studierendenantrag_statustyp_kurzbz, insertvon, insertamum) + SELECT studierendenantrag_id, ?, ?, ( + SELECT insertamum + FROM campus.tbl_studierendenantrag_status + WHERE studierendenantrag_status_id = campus.get_status_id_studierendenantrag(?) + ) + FROM campus.tbl_studierendenantrag + WHERE prestudent_id = ( + SELECT prestudent_id + FROM campus.tbl_studierendenantrag + WHERE studierendenantrag_id = ? + ) + AND studierendenantrag_id <> ? + AND ( + ( + typ = ? + AND campus.get_status_studierendenantrag(studierendenantrag_id) NOT IN ? + ) OR ( + typ = ? + AND campus.get_status_studierendenantrag(studierendenantrag_id) NOT IN ? + ) OR ( + typ = ? + AND campus.get_status_studierendenantrag(studierendenantrag_id) NOT IN ? + ) OR ( + typ = ? + AND campus.get_status_studierendenantrag(studierendenantrag_id) NOT IN ? + ) + )'; + + return $this->execQuery($sql, [ + self::STATUS_PAUSE, + self::INSERTVON_DEREGISTERED, + $antrag_id, + $antrag_id, + $antrag_id, + Studierendenantrag_model::TYP_ABMELDUNG, + [ + Studierendenantragstatus_model::STATUS_APPROVED, + Studierendenantragstatus_model::STATUS_CANCELLED + ], + Studierendenantrag_model::TYP_UNTERBRECHUNG, + [ + Studierendenantragstatus_model::STATUS_APPROVED, + Studierendenantragstatus_model::STATUS_CANCELLED, + Studierendenantragstatus_model::STATUS_REMINDERSENT, + Studierendenantragstatus_model::STATUS_REJECTED + ], + Studierendenantrag_model::TYP_ABMELDUNG_STGL, + [ + Studierendenantragstatus_model::STATUS_CANCELLED, + Studierendenantragstatus_model::STATUS_DEREGISTERED, + Studierendenantragstatus_model::STATUS_OBJECTION_DENIED + ], + Studierendenantrag_model::TYP_WIEDERHOLUNG, + [ + Studierendenantragstatus_model::STATUS_DEREGISTERED, + Studierendenantragstatus_model::STATUS_APPROVED + ], + ]); + } } diff --git a/application/views/lehre/Antrag/Create.php b/application/views/lehre/Antrag/Create.php index 2c9d0d382..d87b0ce4b 100644 --- a/application/views/lehre/Antrag/Create.php +++ b/application/views/lehre/Antrag/Create.php @@ -31,9 +31,9 @@ $this->load->view(
load->view(

p->t('studierendenantrag', 'calltoaction_' . $type); ?>


- + p->t('studierendenantrag', 'antrag_typ_' . $type); ?>
@@ -63,7 +66,16 @@ $this->load->view( studierendenantrag_id; ?> p->t('studierendenantrag', 'antrag_typ_' . $antrag->typ); ?> - status_bezeichnung; ?> + + status == Studierendenantragstatus_model::STATUS_PAUSE + && $antrag->status_insertvon == Studierendenantragstatus_model::INSERTVON_DEREGISTERED + ) + ? $this->p->t('studierendenantrag', 'status_stop') + : $antrag->status_bezeichnung; + ?> + studiensemester_kurzbz; ?> datum))->format('d.m.Y'); ?> datum_wiedereinstieg ? (new DateTime($antrag->datum_wiedereinstieg))->format('d.m.Y') : ''; ?> @@ -74,15 +86,32 @@ $this->load->view( -