Compare commits

...

179 Commits

Author SHA1 Message Date
ma0048 7f91e20d03 lvverwaltung: vilesci lehrveranstaltung verwaltung verlinkt 2026-03-04 15:15:12 +01:00
ma0048 233a6e8b89 lvverwaltung:
- event hinzugefuegt damit tabs erweitert werden können
- berichte hinzugefuegt
2026-02-25 15:25:36 +01:00
Andreas Österreicher 9b114c5fb1 Merge branch 'feature-71344/LVEvaluierungZeitfenster' 2026-02-23 09:51:47 +01:00
Andreas Österreicher 2228b4d683 Sancho Header LV-Evaluierung 2026-02-20 13:29:01 +01:00
kindlm 32fc029bd3 Merge remote-tracking branch 'origin/master' 2026-02-20 11:20:19 +01:00
kindlm f4e0516d89 Prüfung ob jede Fragen mindestens 2 Vorschläge hat auf Warning geändert
- Kleines Padding vor "Blättern"-Text
2026-02-20 11:20:09 +01:00
Harald Bamberger d98b7fd67a fix duplicates in lvplan for special groups 2026-02-19 18:34:27 +01:00
Harald Bamberger 7169cb68a2 fix bug when sending multi messages introduced by loading time optimisation 2026-02-19 09:20:10 +01:00
Harald Bamberger f8da0b0915 Merge branch 'feature-69551/Einmeldeformular_als_VueJS_Component_fuer_LV-Evaluierung' 2026-02-19 07:46:15 +01:00
Andreas Österreicher 6ec4737b22 Merge branch 'bug-71685/bewerbungstool_login_zugangscode' 2026-02-18 12:47:02 +01:00
Andreas Österreicher bb273d10bd Merge branch 'bug-71662/Bug_cancelVertrag_Berechtigungspruefung' 2026-02-18 10:50:59 +01:00
ma0048 1d8c4b7159 bug behoben, login wieder nur mit zugangscode moeglich 2026-02-17 16:49:09 +01:00
Alexei Karpenko c58674d133 Projektarbeiten cancelVertrag permission check bugfix (added array_column to get oes) 2026-02-17 15:15:30 +01:00
Harald Bamberger 4825c75b5d revert changes made in commit b1a1cdf235 2026-02-17 12:11:37 +01:00
Harald Bamberger f6fd5ab678 Merge branch 'feature-71645/StudVw_MessageTab_Ladezeit' 2026-02-17 10:29:10 +01:00
Harald Bamberger 3d1aef617f add phrase error.opproject_does_not_exists in category kvp 2026-02-17 08:13:07 +01:00
Harald Bamberger e12b7e1ed5 add indexes for person_id to table msg_message and msg_recipient, ensure tabulator data request is made before requests of create msg components 2026-02-17 08:06:30 +01:00
Harald Bamberger 0496eb7cc9 use union instead of or to avoid parallel seq scan 2026-02-16 15:56:40 +01:00
Harald Bamberger 962cbf4e78 join person table for sender and recipient instead of using subselect 2026-02-16 15:16:49 +01:00
Harald Bamberger 5415180b2c fetch count and paginated data in one query 2026-02-16 14:18:59 +01:00
Andreas Österreicher 827b6148a7 Merge branch 'feature-61164/AbgabetoolQualityGates' 2026-02-16 08:47:30 +01:00
Johann Hoffmann 3831f3c1d7 consistent use of :optionDisabled="getOptionDisabled" for paabgabetyp dropdowns in assistenz view 2026-02-16 03:41:30 +01:00
Johann Hoffmann 60294dd8f2 paBenotet evaluation fix 2026-02-16 03:22:39 +01:00
Andreas Österreicher 9d5adc1ed2 Merge branch 'feature-61164/AbgabetoolQualityGates' 2026-02-13 13:58:30 +01:00
Johann Hoffmann 632866c8c4 reset newTermin object when switching projektarbeit so they are assigned to the correct student 2026-02-13 13:45:12 +01:00
Andreas Österreicher 7f029ec8b5 Merge branch 'einspielen_12_02_2026' 2026-02-13 11:20:22 +01:00
ma0048 d9d15c1ed3 neue tag farben 2026-02-13 11:10:16 +01:00
Johann Hoffmann 043b1bcf11 extracted email split method from stv/kontakt component to helperfile; adjusted that method to take subject param & make phrasen/alert call via parameter reference; 2026-02-12 17:38:00 +01:00
Johann Hoffmann 5c1e967d5e dont reset z-index on opening tiered menu (email button) by setting its autoZIndex property to false 2026-02-12 16:05:02 +01:00
Harald Bamberger 664b0a81bb use vilesci base url in combinePeople component 2026-02-12 15:52:11 +01:00
Harald Bamberger fa807f37ae gehaltsbestandteil chart data: spezialfall neuer Gehaltsbestandteil und sofort valorisiert 2026-02-12 14:47:07 +01:00
Harald Bamberger 8f62d0d351 use correct category for phrase notiz_edit 2026-02-12 13:20:54 +01:00
Harald Bamberger e016deb042 add more space between download and delete button 2026-02-12 13:14:23 +01:00
Harald Bamberger ed170645df use plsql function public.get_rolle_prestudent instead of local sql 2026-02-12 11:27:50 +01:00
Andreas Österreicher 3a441228b8 Merge branch 'feature-61164/AbgabetoolQualityGates' 2026-02-12 11:07:36 +01:00
Andreas Österreicher 0a97e5781e Nicht beurteilt aus Default Config entfernt 2026-02-12 11:02:16 +01:00
ma0048 3465e299f7 tag - helper and formatter 2026-02-12 08:16:31 +01:00
Andreas Österreicher 136d6f9f28 Fix von BFI übernommen 2026-02-11 16:08:15 +01:00
Johann Hoffmann 67838eb630 load projektarbeit.note correctly for mitarbeiter; evaluate projektarbeit termin editability correctly and define a notenarray which does NOT count (currently "Nicht beurteilt" & "Noch nicht eingetragen"). such rules apply for betreuer, assistenz is allowed to do whatever they want since we never defined an actual business process anywhere and people do whatever they want anyways 2026-02-11 13:39:23 +01:00
Johann Hoffmann 609e226057 place email buttons inside a tiered menu (dropdown for some) to save space in the ui; 2026-02-10 14:25:45 +01:00
kindlm ffaff361b7 Merge remote-tracking branch 'origin/master' 2026-02-10 11:51:24 +01:00
kindlm d17206fe40 Admin-Seite: Padding bei Vorschau; Login Bugfix Fallback STG-Name 2026-02-10 11:51:02 +01:00
Andreas Österreicher 34e8b2e36d Merge branch 'feature-69180/AnwUID4CSV' 2026-02-10 11:14:39 +01:00
Andreas Österreicher fbea5a9306 Merge branch 'master' into feature-69180/AnwUID4CSV 2026-02-10 11:02:20 +01:00
Johann Hoffmann 6da19585ff optional sammelmail buttons assistenz abgabetool 2026-02-09 13:45:01 +01:00
Andreas Österreicher f4ae8dd8e1 Korrektur zur Ermittlung der (legacy) Anwesenheit von BFI übernommen 2026-02-06 13:10:31 +01:00
Johann Hoffmann 1eda652fba remove old code 2026-02-04 17:34:21 +01:00
Johann Hoffmann cc302ed5a1 lazyload signatur status for assistenz view to avoid worst case loading times due to 50 x 30mb signatur server payload 2026-02-04 17:32:17 +01:00
Andreas Österreicher f2308a32c8 Merge branch 'feature-61164/AbgabetoolQualityGates' 2026-02-04 11:37:57 +01:00
Johann Hoffmann c8cb484299 add missing email template public.tbl_vorlage inserts to checksystem script; checked template naming convention for higher case 2026-02-04 11:34:36 +01:00
Andreas Österreicher 7c67e65c9b Merge branch 'feature-61164/AbgabetoolQualityGates' 2026-02-04 11:11:59 +01:00
Johann Hoffmann a4dcf9e935 use phrase c4betrart + pa.betrart_kurzbz for betreuer/zweitbetreuer: text 2026-02-04 11:04:55 +01:00
Andreas Österreicher 6f99f493ea Merge branch 'feature-61164/AbgabetoolQualityGates' 2026-02-04 10:46:10 +01:00
Andreas Österreicher 023c2a10be Merge branch 'master' into feature-61164/AbgabetoolQualityGates 2026-02-04 10:21:17 +01:00
Harald Bamberger a7cd6c35f1 Merge branch 'feature-71399/Javscript_Caching_Problem_fhc-build-version_in_URL_und_mod_rewrite' 2026-02-03 16:29:05 +01:00
Harald Bamberger 87ff7acef0 use absoluteJsImportUrl helper instead of APP_ROOT constant to build js components file path 2026-02-03 14:27:02 +01:00
Harald Bamberger d192489c6f use correct filename in comment 2026-02-03 13:52:04 +01:00
Harald Bamberger 7028fe0ac8 Merge branch 'master' into feature-71399/Javscript_Caching_Problem_fhc-build-version_in_URL_und_mod_rewrite 2026-02-03 13:49:48 +01:00
Harald Bamberger a3a03f6362 update check system to create or update widget path to start with public/... 2026-02-03 13:49:06 +01:00
Johann Hoffmann 6391bf5a45 tippfehler in phrase "c4tooltipStandard" ausgebessert: 'Termin mehr als 12 Tag entfernt' => 'Termin mehr als 12 Tage entfernt' 2026-02-03 12:52:25 +01:00
Johann Hoffmann d774335bcf assistenz view query now also checks for betreuerart_kurzbz ("Erstbegutachter", "Begutachter", "Betreuer", "Erstbetreuer", "Senatsvorsitz") instead of only ("Erstbegutachter", "Begutachter") 2026-02-03 12:35:06 +01:00
Andreas Österreicher b4f28d5426 Abstract zum Default Config für Betreuer hinzugefügt 2026-02-03 11:12:50 +01:00
Andreas Österreicher 3e1b9865b1 Merge branch 'master' into feature-61164/AbgabetoolQualityGates 2026-02-03 10:33:54 +01:00
Andreas Österreicher 3534118261 Merge branch 'feature-71355/constructor_config_bewerbungstool_datenschutz' 2026-02-03 08:50:20 +01:00
Harald Bamberger 5d73f051ba sample .htaccess to use with config option use_fhcomplete_build_version_in_path 2026-02-03 08:13:57 +01:00
Harald Bamberger 85a10e27cc fix setFirstStudent if GENERATE_ALIAS_STUDENT is false 2026-02-02 17:59:15 +01:00
Johann Hoffmann 1b35569797 Merge branch 'master' into feature-69180/AnwUID4CSV 2026-02-02 10:22:10 +01:00
Harald Bamberger 77abcb6129 use absoluteJsImportUrl in Dashboard Widget Api Endpoints, send widget setup and arguments column values as json to frontend, remove JSON.parse from frontend dashboard code 2026-01-30 18:58:20 +01:00
Harald Bamberger 63f198098d use absoluteJsImportUrl Helper for calendar Event renderers 2026-01-30 18:55:25 +01:00
Harald Bamberger f7478ff05c add config to insertr fhc-build-version into path or add it as query string 2026-01-30 18:53:31 +01:00
Johann Hoffmann 366cb16b61 anw phrasen "studentByLVATitle" & "kontrolliertVon", slight adjustment of fullscreen modal button so it looks similar 2026-01-30 14:10:28 +01:00
ma0068 326d4b3923 Revert "refactor in computed section: Tabs Betriebsmittel, Abschlusspruefung, Anrechnungen, Aufnahmetermine, Kontakt, Prestudent, Status, bugfix Lehrveranstaltungstermine: sorter for Lektor as string"
This reverts commit f2107a377f.
2026-01-30 10:31:38 +01:00
ma0068 f2107a377f refactor in computed section: Tabs Betriebsmittel, Abschlusspruefung, Anrechnungen, Aufnahmetermine, Kontakt, Prestudent, Status, bugfix Lehrveranstaltungstermine: sorter for Lektor as string 2026-01-30 10:29:19 +01:00
Johann Hoffmann fbe10cc2a1 PersonModel loadAllStudentUIDSForPersonID used in anw extension AdministrationApi aka Entschuldigungsmanagement 2026-01-29 15:08:24 +01:00
ma0048 783c4fc5f8 constructor warning config hinzugefuegt
testtool englisch uebersetzung datenschutz hinzugefuegt
2026-01-29 08:15:35 +01:00
Harald Bamberger f1aa5382cf check editZgv Permission for Stg and also check it in api function 2026-01-28 17:04:11 +01:00
Harald Bamberger 7fe36d59e3 apply edit Zgv permissions only to ZGV dropdowns 2026-01-28 11:32:03 +01:00
kindlm c724c6a20f MathML auf Darstellungs-Testseite überarbeitet 2026-01-27 16:53:33 +01:00
kindlm b9bfa5c3c5 Merge remote-tracking branch 'origin/master' 2026-01-27 16:52:16 +01:00
Johann Hoffmann 101613ecdd detail view infos - student: {student} titel: {titel} betreuer: {betreuer} 2026-01-27 14:47:51 +01:00
Johann Hoffmann edd0c46186 target blank on students stg moodle link; added new classes to assistenz table cell formatters 2026-01-27 11:50:29 +01:00
Johann Hoffmann 07dd9e3a77 abgabedatum logic fix in dateStyles 2026-01-27 11:31:56 +01:00
Johann Hoffmann 06139b14d6 bestanden style from purple to green 2026-01-27 11:25:41 +01:00
Harald Bamberger 3e46e94736 quick fix to prevent pruefunglist from being empty due to js error in combination with tabulator sort persistence 2026-01-27 11:07:46 +01:00
Johann Hoffmann fc468ca34a URL_ASSISTENZ for links in email jobs 2026-01-27 10:52:15 +01:00
Johann Hoffmann 84db668566 compare notenOpt in student view if necessary for datestyle 2026-01-27 10:30:00 +01:00
Johann Hoffmann 3c82cd1282 icon template statusses 2026-01-27 09:58:48 +01:00
Johann Hoffmann 709f64e292 2 new dateStyle status regarding QG benotung 2026-01-27 09:31:14 +01:00
Johann Hoffmann 709aba5783 Merge branch 'master' into feature-61164/AbgabetoolQualityGates
# Conflicts:
#	application/models/organisation/Studiengang_model.php
2026-01-26 15:06:22 +01:00
Harald Bamberger f867e60702 fix use of config FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE to override studiengang_kz in public.tbl_konto 2026-01-21 17:37:23 +01:00
Johann Hoffmann 668f0a6618 fixed betreuerart bezeichnung formatting discrepancy between first and second assesor 2026-01-21 16:26:26 +01:00
Johann Hoffmann 48cd37058e also block uploads and termin changes for projektarbeiten with a note on backend 2026-01-21 14:53:44 +01:00
Johann Hoffmann 4d97127539 remove Abgabedatum column from Assistenz/Betreuer update sammelmail. Keep it for UPLOADS sammelmail since it is relevant there; defined relevant_types in abgabetool config to filter for in the sammelmail for each role student/assistenz/betreuer; block saving/deleting/uploading ui components when projektarbeit has a note, since this indicates that the project is over; 2026-01-21 14:05:31 +01:00
Johann Hoffmann c9bcf9b9b0 fixed note init by object/object key on 2nd time opening the details modal in the assistenz view; 2026-01-21 10:34:17 +01:00
Johann Hoffmann 2471d37dd3 fix loginfo statements betreuer->assistenz 2026-01-19 16:54:10 +01:00
Johann Hoffmann ad7808eb21 fix typo in speed dial disabled computed (based on being a zweitbegutachter 2026-01-19 16:49:03 +01:00
Johann Hoffmann 9a07e7c804 wrote Studiengang_model.php -> getAssistenzForStudiengangKZ() that does just that but is not used currently since we retrieve assistenz in AbgabetoolJob.php via oe_kurzbz; added new getAssistenzForOE() method in Organisationseinheit_model; added new job "notifyAssistenzAboutChangedAbgaben" that does just that to AbgabetoolJob.php; removed console.log/debugger statements in cis4 code; 2026-01-19 16:12:54 +01:00
Johann Hoffmann 6c82741341 remove var_dump() statement 2026-01-16 12:00:30 +01:00
Johann Hoffmann c3f7f7223a add student name info to betreuer sammelemail jobs table; WIP adapting same logic for assistenz; 2026-01-16 11:59:46 +01:00
Johann Hoffmann fbd0a4685e fix jobs & fancy formatting; check paabgabetyp instead of bezeichnung to enable enduploads for users in english lmao 2026-01-15 18:40:51 +01:00
Johann Hoffmann eb15d6b841 getStudentConfig Api Method; moodle link config entry; write abgabetool termin noten into header; uniform getDateStyleClass logic in all 3 views; WIP refining AbgabetoolJob; 2026-01-14 16:57:57 +01:00
Johann Hoffmann bbb90f6dc4 added fixtermin variable to Paabgabe->update() statement; email logic for sancho emails towards betreuer: return $email[0]->uid ? $email[0]->uid.'@'.DOMAIN : $email[0]->private_email; phrasen wordings; reworked assistenz config api promises as allSettled to avoid race conditions; nachreichen möglich is always the default everywhere; WIP enabling the same status logic workflow everywhere; 2026-01-13 18:20:05 +01:00
Johann Hoffmann e89fcbab31 phrase typo fix 2026-01-13 13:05:58 +01:00
Johann Hoffmann 7c1f239dcb configurable paabgabe types for signature check; finetuned signature response message; phrasen & code cleanup; 2026-01-13 12:32:41 +01:00
Johann Hoffmann cc0f38b276 fix the comment describing the "fix" 2026-01-12 17:14:03 +01:00
Johann Hoffmann a56335f4f9 "fix" the signatur check 2026-01-12 17:11:48 +01:00
Johann Hoffmann 954c55ba3e finetuning 2026-01-12 17:06:35 +01:00
Johann Hoffmann c7250959d0 Merge remote-tracking branch 'origin/feature-61164/AbgabetoolQualityGates' into feature-61164/AbgabetoolQualityGates 2026-01-12 12:52:31 +01:00
Johann Hoffmann 6c8318ead2 wip 2026-01-12 12:51:34 +01:00
Andreas Österreicher 3095f7ea8b Notice für UID entfernt 2026-01-12 10:44:57 +01:00
Andreas Österreicher 8f98d0c5a1 Fixed UID 2026-01-12 10:31:50 +01:00
Johann Hoffmann 13232015c3 more sophisticate zuordnung check & place it in every sensitive API endpoint; WIP: check STGentitlement when querying projektarbeiten for certein stg, since only dropdown info is pulled with this berechtigungslogic in mind but not the fetch itself; 2026-01-09 14:13:26 +01:00
Johann Hoffmann db861e81b2 rewrote post param acces from $id = $_POST['id'] to $id = $this->input->post('id') and subsequently rewrote safety checks from !isset($id) || isEmptyString($id) to $id === NULL || trim((string)$id) === '' to avoid ASCII conversion of low integers when passing them to isEmptyString function 2026-01-08 17:45:02 +01:00
Johann Hoffmann 7eb147085f typo fix in getDateStyleClass; fix selectAll button in Assistenz view; change the upload_allowed checkbox on change as expected; give fhcAlert primevue btn classes instead of bootstrap so they actually get applied; save convert js date to iso string to avoid timezone shenanigans; created zusatzdaten edit phrase; 2026-01-08 16:05:24 +01:00
Johann Hoffmann 80175f46cb typo fix in getDateStyleClass; fix selectAll button in Assistenz view; 2026-01-08 14:40:24 +01:00
Johann Hoffmann 3d82d69bfc fixed student_uid variable aquisition inside StudentComponent; No allowedToSave check in client for Betreuer at all, everything done at backend level (WIP); added legacy phrasen to phrasesupdate to guarantee their existence; 2026-01-08 13:40:58 +01:00
Andreas Österreicher f845809e6b Fixed Variable Check in Abgabetool 2026-01-07 16:02:06 +01:00
Andreas Österreicher 67b03dd29f ReAdded Lost Phrases 2025-12-18 12:55:08 +01:00
Andreas Österreicher 47e3c83909 Merge branch 'master' into feature-61164/AbgabetoolQualityGates 2025-12-18 12:25:05 +01:00
kindlm 2a84999e56 Merge remote-tracking branch 'origin/master' 2025-12-15 14:39:47 +01:00
Johann Hoffmann 756a51defa selectable check during selectAll to block it for Zweitbetreuer -> custom formatted/handled selection column for mitarbeiter & assistenz; custom sticky-col css; 2025-12-15 13:16:12 +01:00
Johann Hoffmann 735a6654b9 projektbeurteilung_check_available event when loading studentprojektarbeiten handled in extension 2025-12-12 13:45:01 +01:00
Johann Hoffmann 635da9f8d9 checksystem paabgabetyp default values for upload_allowed, benotbar & aktiv only on first checksystem run by checking availability of qgate2 typ; english phrasen for betreuerart, paabgabetyp & projekttyp; disable new termin for zweitbetreuer (WIP disabling serientermin); QGate1/2 Status column -> text only for now; 2025-12-12 12:51:07 +01:00
Johann Hoffmann eade9b7beb fixed accordion header padding size; student sees public mail, lektor gets notifs to private; remove unused injections; rewrite viewData validation for old Cis to remove console.warnings; clear abgabetermin.kurzbz when changing to smth not quality gate to avoid leaky kurzbz; fixed accordion header style offset for both cis environments; tooltip fix; only show abgabedatum if termin has upload_allowed; lower max-width in old cis; activated custom persistence for assistenz page + stricter promise handling around tableBuilt; activeIndex Handling on Student Page in case of several Projektarbetien; Phrasen gendering; 2025-12-10 17:41:01 +01:00
Johann Hoffmann bb689a6d48 Merge branch 'master' into feature-61164/AbgabetoolQualityGates
# Conflicts:
#	public/css/Cis4/Cis.css
#	system/dbupdate_3.4.php
#	system/phrasesupdate.php
2025-12-09 17:03:18 +01:00
Andreas Österreicher e1959b9e40 Quality Gate Aenderungen im DBUpdate hinzugefügt 2025-12-05 13:21:46 +01:00
Johann Hoffmann ca152dc1ea add projektarbeit_id & student_uid to projektbeurteilung_formular_link event in projektarbeitsbeurteilungsextension 2025-12-04 17:28:16 +01:00
Johann Hoffmann ec7ebc8286 allowedNoten config integer instead of string so javascript array.includes() doesnt miss the noten options 2025-12-04 16:57:24 +01:00
Johann Hoffmann 035e844fab added tbl_vorlage insert for PAAChangesBetSM.txt 2025-12-04 15:40:45 +01:00
Johann Hoffmann e7a737b7aa write stg_kz & semester_kurzbz into sammelmail about each projektarbeit; move method from abgabe.php api controller to abgabejob.php 2025-12-04 15:36:24 +01:00
Johann Hoffmann e3c1287664 WIP 2025-12-04 14:49:05 +01:00
Johann Hoffmann 0c5af137db WIP notifyBetreuerAboutNewOrChangedAbgabenForBetreuedProjektarbeiten Email Job; allowed abgabetypen & allowed noten as per config now via db primary key; new ProjektbetreuerModel method "getAllBetreuerOfProjektarbeit" to do just that; 2025-12-03 16:49:49 +01:00
Johann Hoffmann 541d6d78cc projektbeurteilung_formular_link event for benotung link AbgabetoolMitarbeiterDetail.js; projektarbeit_is_current event for abstracted logic in Abgabetool; show endupload missing message on benoten if neither old nor new link are eligable; 2025-12-03 11:35:49 +01:00
Johann Hoffmann 7747857583 AbgabetoolStudent beurteilung erstbetreuer/zweitbetreuer switch + get the actual link from the projektarbeitsbeurteilung extension via event 'projektbeurteilung_download_link' + fallback from config in case that fails; WIP implementing the switch for lektor beurteilungstemplate based on isCurrent/Semester etc in an Event in the pabu extension; 2025-12-02 16:07:26 +01:00
Johann Hoffmann 6359dc0fc9 use sendUploadMail on endupload, no events for that; remove extensive form validation from student detail; serientermin update table format fix; WIP new job informing betreuer about changes to thei betreued PA's; zweitbetreuer/erstbetreuer beurteilungslink switch in abgabetoolStudent; WIP getting that link from event not hardcoded; fix checkUploadSize calculation bytes to megabytes; added beurteilungerforderlich class to accordion headers; 2025-12-02 13:13:42 +01:00
Johann Hoffmann aee6ace42e termine without uploads can now only have 'standard' or 'abgegeben' status based on datum; FHC_Api_Controller method checkUploadSize() -> checks input->server('CONTENT_LENGTH') against min($max_upload, $max_post, $memory_limit) and throws a designated filesizeExceeded error message; added fhc-orange palette for new status 'beurteilungerforderlich'; 2025-11-28 14:09:53 +01:00
Johann Hoffmann 095d5acbc5 load all studiensemester for assistenz; load paabgabetyp benotbar for all paabgaben; datediff calc luxon; new dateclass 'beurteilungrequired'; 2nd quality gate validation logic option; filter notenoptions as per config; filter abgabetypoptions as per config; upload_allowed checkbox for serientermine; serientermin modal layout rearranged; abgabetoolJob fixes; 23:59 in the descriptive col, not datepicker; zusatzdaten are required; activeIndex for accordion calulated on demand by method instead of reading a computed value; 2025-11-27 16:53:50 +01:00
Johann Hoffmann 8888b6991f Merge branch 'master' into feature-61164/AbgabetoolQualityGates
# Conflicts:
#	application/models/education/Projektarbeit_model.php
#	public/js/helpers/StringHelpers.js
2025-11-24 14:25:43 +01:00
Johann Hoffmann 9ac7ff2a4c allowed to upload on endtermin switch to production logic (date & quality gates) 2025-11-24 14:16:07 +01:00
Johann Hoffmann f4ca34f247 remove email sent to alert on serientermin since we do this in cronjob now; 2025-11-24 14:14:46 +01:00
Johann Hoffmann 3b7ed523b4 postStudentProjektarbeitZusatzdaten in detail view for Betreuer/Assistenz; 2025-11-21 12:36:03 +01:00
Johann Hoffmann c447fb9632 properly center loadingOverlay in center of screen, not center of content + put that template away in a FhcOverlay.js component 2025-11-20 11:33:30 +01:00
Johann Hoffmann 352638ed90 code cleanup; 2025-11-20 10:36:23 +01:00
Johann Hoffmann a4fee77301 link url in abgabetoolJob per config; comment out addMeta; 2025-11-18 15:01:43 +01:00
Johann Hoffmann 96fdc357de move getDateStyleClass to AbgabetoolStudent.js from AbgabeStudentDetail.js 2025-11-17 16:39:19 +01:00
Johann Hoffmann 6cd0e3a574 email tbl_vorlage inserts dbupdate script; index.ci.php/Abgabetool/Assistenz/stg_kz? optional param route in old cis & cis4; no data placeholder phrase fix; 2025-11-17 15:57:53 +01:00
Johann Hoffmann 1120b823d2 log email count and early job termination in case of new events to notify for 2025-11-17 12:06:07 +01:00
Johann Hoffmann f89a53b156 fix getProjektbetreuerAnrede function since betreuer are not necessarily mitarbeiter with such an uid; notify students config from milliseconds to pgsql interval '1 day'; ported notifyBetreuerMail & notifyStudentMail to AbgabetoolJob.php; 2025-11-17 11:47:00 +01:00
Johann Hoffmann cff71ec829 AbgabetoolJob notifyStudentMail function to send Emails to students about their changed abgabetermine inside the configurable threshold; 2025-11-13 17:16:51 +01:00
Johann Hoffmann 8b851221b0 cis4 main content element shrinkability with style="min-width: 0;" 2025-11-12 12:05:48 +01:00
Johann Hoffmann 2920c68f05 wip 2025-11-11 15:59:42 +01:00
Johann Hoffmann 74937db204 WIP email logs to read/send in job; 2025-11-11 15:52:04 +01:00
Johann Hoffmann 410f0c4b6a fixtermin -> "kein neichreichen erlaubt" phrase/bool logic change; signatur message on enduploads fetched from signatur server on every load request; Inplace toggle for further infos in offcanvas timeline & student details on mobile view(tooltips); reworked col/row structure on detail views for mobile; 2025-11-11 13:44:47 +01:00
Johann Hoffmann cef0046acd speeddial icon/positioning; textarea styles; fixtermin phrase; wip inverting fixtermin bool logic to avoid double negative semantics 2025-11-07 14:19:17 +01:00
Johann Hoffmann a61d5b1d62 orgform/studstatus cols; 2025-11-07 11:17:59 +01:00
Johann Hoffmann b1a1cdf235 studiensemester dropdown filter, default all, options are current/next and op to 10 back; benotet/unbenotet/alle fetch parameter; WIP orgform/studstatus cols; 2025-11-06 16:29:24 +01:00
Johann Hoffmann 3878fce625 wip abgabetool; 2025-11-05 15:34:10 +01:00
Johann Hoffmann 3d51753419 new bootstrap offcanvas component; projektarbeit abgabetermine timeline & status legende in offcanvas; fixtermin toggle & allowed to edit all termine for assistenz; studiengang selection + filter for getSTG_isEntitledFor('basis/abgabe_assistenz:rw'); moved filedownload from Cis/Abgabetool Auth Controller to Abgabe.php Api Controller; status symbol in table columns prevTermin/nextTermin; get_betreuer_details pgsql function to avoid rewriting the same subquery for every betreuer anrede; 2025-10-31 11:14:39 +01:00
Johann Hoffmann ac1e0a8aa3 Merge remote-tracking branch 'origin/master' into feature-61164/AbgabetoolQualityGates
# Conflicts:
#	system/phrasesupdate.php
2025-10-27 14:51:52 +01:00
Johann Hoffmann 259c2aec14 load projektarbeiten for studiengänge -> assistenz page; speed dial position rework; automagicmodal logic fix; activeTabIndex by date in detail views; tooltips on icons; $capitalize phrasen to ensure capitalization; phrasenpromise & resolve similar to anw; modal component emits fullscreen event now; 2025-10-27 14:40:56 +01:00
Johann Hoffmann 0d2e41cf2f added paabgabetyp columns "aktiv", "upload_allowed", "aktiv"; setting sensible default values for existing typen that are just the developers best guess really; accordion header with icons & tooltips; logLib in Abgabe API controller logging all successful delete/insert/update requests; show arbitrary '23:59' string after target date so it is clear until when the upload should be fulfilled, even though we still dont do anything technically different; new Termine can only be made with aktiv paabgabe typen; note & benotungsnotiz now tied to paabgabetyp benotbar flag instead of hardcoded for qgate1 & 2; added "noch nicht abgegeben" text in case the abgabedatum is null; modal now spawns in xl with fullscreen optionally enabled; 2025-10-14 16:45:28 +02:00
Johann Hoffmann 14aad56d5e datepicker css overflow fix; returned modalMagic to old form since its simpler now 2025-10-09 12:40:53 +02:00
Johann Hoffmann f03411c668 legacy cis abgabetool routes; abgabetool.css for legacy cis; endupload & fixtermin logic enabled; paabgabe template rearranged; legacy view, controller has cis4 switch; viewData & router props workaround with CI3 router logic; wrapper app legacy cis; fix enduplaod validation; 2025-10-08 17:41:00 +02:00
kindlm 3549fc1b1b Merge remote-tracking branch 'origin/master' 2025-10-07 18:43:11 +02:00
kindlm 06788bafc8 ParseHTML bei statistik.class ergänzt. SVNR aus BIS-Checks entfernt 2025-10-07 18:42:50 +02:00
Johann Hoffmann 1e23b6de61 download files with window.open instead of window.location; fix signature mail student_modal info loading; check length of every entry field when doing endupload and force user to accept or cancel upon notification; berechtigung check für Abgabetool.php controller; phrasen everywhere; 2025-10-06 13:58:26 +02:00
Johann Hoffmann dab34eff35 klickibunti farben in accordion headers je nach datum/abgabedatum combo; more color definitions cis4 default.css; nomore legacy classes in Abgabe.php, CI3 models only; confirm delete Termin prompt; endupload validation stub, not sure about the technical min requirements here; mitarbeiter table format fix; show noten in projektarbeit view for students so there is some distinction; fhc isMobile computed revamp; order projektarbeiten by insertamum DESC to get most recent ones at the top of lists; 2025-10-02 16:53:41 +02:00
Johann Hoffmann c796536417 notiz -> beurteilungsnotiz; some missing phrasen; more color definitions (WIP); finalize automagic modal logic; added validation stub for endupload; loading spinner on every upload; added custom header classes to code unexpanded paabgabe accordion tabs by their abgabedatum or lack thereof (WIP); 2025-09-30 16:47:05 +02:00
Johann Hoffmann 14f5a651a4 abgabetool rechte; WIP magic modal for next QG Termin logic handling; 2025-09-22 10:47:12 +02:00
Johann Hoffmann ce9f0536d6 Merge branch 'master' into feature-61164/AbgabetoolQualityGates 2025-09-17 16:40:53 +02:00
Johann Hoffmann 48f7a04d81 wip abgabetool 2025-09-11 16:27:24 +02:00
Johann Hoffmann 4e598a321d wip add new abgabe wizard 2025-09-08 10:44:41 +02:00
Johann Hoffmann 49ca538381 student page redesign; wip mitarbeiter page + add new modal 2025-09-04 17:38:32 +02:00
Johann Hoffmann 5f1c7537fb WIP 2025-09-04 11:04:12 +02:00
Johann Hoffmann a560c335b8 WIP responsive tabulator collapse abgabetool; paabgabe notiz column; 2025-09-02 13:35:42 +02:00
Johann Hoffmann 1f0fe08b69 upload_required => allowed; hardcoded links => config; styling; endupload/qualgate logic; phrasen; 2025-08-29 14:15:20 +02:00
Johann Hoffmann 63390b192c qualgate 1&2 dbupdate script, note fkey reference & upload required flag in paabgabe; noten api duplicate from notentool for now; WIP more emails; qualgates benotbar & saveable aswell as upload flag; 2025-08-26 17:15:37 +02:00
Johann Hoffmann e97c26022f Merge remote-tracking branch 'origin/master' into feature-61164/AbgabetoolQualityGates 2025-08-25 12:00:30 +02:00
Johann Hoffmann 483662726d abgabetool api/controller refactor; quality gates dbupdate script; load types from backend instead of hardcoded; WIP email check for externe betreuer; 2025-08-22 14:40:58 +02:00
100 changed files with 11203 additions and 1835 deletions
+12 -12
View File
@@ -6,30 +6,30 @@ use CI3_Events as Events;
Events::on('loadRenderers', function ($renderers) {
$fhc_core_renderers =& $renderers();
$fhc_core_renderers["lehreinheit"] = array(
'calendarEvent' => APP_ROOT.'public/js/components/Cis/Renderer/Lehreinheit/calendarEvent.js',
'modalTitle' => APP_ROOT.'public/js/components/Cis/Renderer/Lehreinheit/modalTitle.js',
'modalContent' => APP_ROOT.'public/js/components/Cis/Renderer/Lehreinheit/modalContent.js',
'calendarEventStyles' => APP_ROOT.'public/css/Cis4/CoreCalendarEvents.css'
'calendarEvent' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Lehreinheit/calendarEvent.js'),
'modalTitle' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Lehreinheit/modalTitle.js'),
'modalContent' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Lehreinheit/modalContent.js'),
'calendarEventStyles' => APP_ROOT . 'public/css/Cis4/CoreCalendarEvents.css'
);
});
Events::on('loadRenderers', function ($renderers) {
$fhc_core_renderers =& $renderers();
$fhc_core_renderers["reservierung"] = array(
'calendarEvent' => APP_ROOT.'public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js',
'modalTitle' => APP_ROOT.'public/js/components/Cis/Renderer/Reservierungen/modalTitle.js',
'modalContent' => APP_ROOT.'public/js/components/Cis/Renderer/Reservierungen/modalContent.js',
'calendarEventStyles' => APP_ROOT.'public/css/Cis4/CoreCalendarEvents.css'
'calendarEvent' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js'),
'modalTitle' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Reservierungen/modalTitle.js'),
'modalContent' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Reservierungen/modalContent.js'),
'calendarEventStyles' => APP_ROOT . 'public/css/Cis4/CoreCalendarEvents.css'
);
});
Events::on('loadRenderers', function ($renderers) {
$fhc_core_renderers =& $renderers();
$fhc_core_renderers["ferien"] = array(
'calendarEvent' => APP_ROOT.'public/js/components/Cis/Renderer/Feiertage/calendarEvent.js',
'modalTitle' => APP_ROOT.'public/js/components/Cis/Renderer/Feiertage/modalTitle.js',
'modalContent' => APP_ROOT.'public/js/components/Cis/Renderer/Feiertage/modalContent.js',
'calendarEventStyles' => APP_ROOT.'public/css/Cis4/CoreCalendarEvents.css'
'calendarEvent' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Feiertage/calendarEvent.js'),
'modalTitle' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Feiertage/modalTitle.js'),
'modalContent' => absoluteJsImportUrl('public/js/components/Cis/Renderer/Feiertage/modalContent.js'),
'calendarEventStyles' => APP_ROOT . 'public/css/Cis4/CoreCalendarEvents.css'
);
});
+43
View File
@@ -0,0 +1,43 @@
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
$config['turnitin_link'] = 'https://technikum-wien.turnitin.com/sso/sp/redwood/saml/5IyfmBr2OcSIaWQTKlFCGj/start';
$config['old_abgabe_beurteilung_link'] = 'https://moodle.technikum-wien.at/mod/page/view.php?id=1005052';
$config['PAABGABE_EMAIL_JOB_INTERVAL'] = '1 day';
// used as APP_ROOT.URL_STUDENTS -> cis4
$config['URL_STUDENTS'] = 'cis.php/Cis/Abgabetool/Student';
// used as APP_ROOT.URL_MITARBEITER -> old cis
$config['URL_MITARBEITER'] = 'index.ci.php/Cis/Abgabetool/Mitarbeiter';
// used as APP_ROOT.URL_MITARBEITER -> old cis
$config['URL_ASSISTENZ'] = 'index.ci.php/Cis/Abgabetool/Assistenz';
// lehre.tbl_paabgabetyp bezeichnung
//$config['ALLOWED_ABGABETYPEN_BETREUER'] = ['Zwischenabgabe', 'Quality Gate 1', 'Quality Gate 2'];
$config['ALLOWED_ABGABETYPEN_BETREUER'] = ['abstract','zwischen', 'qualgate1', 'qualgate2']; // tbl_paabgabetyp pk
// paabgabetypen for which betreuer is benachrichtigt via sammelmail
$config['RELEVANT_PAABGABETYPEN_SAMMELMAIL_BETREUER'] = ['qualgate1', 'qualgate2', 'end'];
// paabgabetypen for which assistenz is benachrichtigt via sammelmail
$config['RELEVANT_PAABGABETYPEN_SAMMELMAIL_ASSISTENZ'] = ['end'];
// paabgabetypen for which student is benachrichtigt via sammelmail -> basically all of them but still defined for consistency
$config['RELEVANT_PAABGABETYPEN_SAMMELMAIL_STUDENT'] = ['qualgate1', 'qualgate2', 'zwischen', 'note', 'abstract', 'end', 'enda'];
//$config['ALLOWED_NOTEN_ABGABETOOL'] = ['Bestanden', 'Nicht bestanden'];
$config['ALLOWED_NOTEN_ABGABETOOL'] = [10, 14]; // tbl_note pk
// benotete projektarbeiten sperren weitere terminanlage & bearbeitung, diese noten sind ausnahmen dieser Regel
// wie zB "Nicht beurteilt" & "Noch nicht eingetragen"
$config['NONFINAL_NOTEN_ABGABETOOL'] = [9];
$config['beurteilung_link_fallback'] = 'addons/fhtw/content/projektbeurteilung/projektbeurteilungDocumentExport.php?projektarbeit_id=?&betreuerart_kurzbz=?&person_id=?';
$config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ERSTBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter';
$config['PROJEKTARBEITSBEURTEILUNG_MAIL_BASELINK_ZWEITBEGUTACHTER'] = 'index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter';
$config['SIGNATUR_CHECK_PAABGABETYPEN'] = ['end'];
// to be used as "https://moodle.technikum-wien.at/course/view.php?idnumber=dl{$stg_kz}" for stg specific moodle routing
$config['STG_MOODLE_LINK'] = 'https://moodle.technikum-wien.at/course/view.php?idnumber=dl';
$config['ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'] = true;
$config['ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'] = true;
+1 -1
View File
@@ -4,7 +4,7 @@ if (! defined('BASEPATH')) exit('No direct script access allowed');
// CMS Content Id for CIS4 Menu Root
$config['cis_menu_root_content_id'] = 11087;
$config['cis_menu_root_content_id'] = 11091;
// send Mails for ProfilUpdate
$config['cis_send_profil_update_mails'] = true;
// Vilesci CI BaseUrl
+3
View File
@@ -7,3 +7,6 @@ $config['use_vuejs_dev_version'] = false;
$config['use_bundled_javascript'] = false;
// systemerror_mailto use in FHC-Alert Plugin - if empty Link will not be rendered
$config['systemerror_mailto'] = '';
// use fhcomplete_build_version as path element after public (requires apache mod_rewrite)
// see <fhc_base_dir>/public/.htaccess_sample for details
$config['use_fhcomplete_build_version_in_path'] = false;
+7
View File
@@ -65,6 +65,13 @@ $route['Cis/LvPlan/.*'] = 'Cis/LvPlan/index/$1';
$route['Cis/MyLvPlan/.*'] = 'Cis/MyLvPlan/index/$1';
$route['Cis/MyLv/.*'] = 'Cis/MyLv/index/$1';
$route['Abgabetool/Assistenz'] = 'Cis/Abgabetool/Assistenz';
$route['Abgabetool/Assistenz/(:any)'] = 'Cis/Abgabetool/Assistenz/$1';
$route['Abgabetool/Mitarbeiter'] = 'Cis/Abgabetool/Mitarbeiter';
$route['Abgabetool/Student'] = 'Cis/Abgabetool/Student';
$route['Abgabetool/Student/(:any)'] = 'Cis/Abgabetool/Student/$1';
$route['Abgabetool/Deadlines'] = 'Cis/Abgabetool/Deadlines';
// Studierendenverwaltung List Routes
$route['api/frontend/v1/stv/[sS]tudents/inout'] = 'api/frontend/v1/stv/Students/index';
$route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})'] = 'api/frontend/v1/stv/Students/index';
+39 -50
View File
@@ -14,10 +14,10 @@ class Abgabetool extends Auth_Controller
{
parent::__construct([
'index' => self::PERM_LOGGED,
'getStudentProjektarbeitAbgabeFile' => self::PERM_LOGGED,
'Mitarbeiter' => self::PERM_LOGGED,
'Student' => self::PERM_LOGGED,
'Deadlines' => self::PERM_LOGGED
'Mitarbeiter' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'Assistenz' => array('basis/abgabe_assistenz:rw'),
'Student' => array('basis/abgabe_student:rw', 'basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw'),
'Deadlines' => array('basis/abgabe_lektor:rw', 'basis/abgabe_assistenz:rw')
]);
}
@@ -29,80 +29,69 @@ class Abgabetool extends Auth_Controller
*/
public function index()
{
// TODO: routing from index based on berechtigung?
$viewData = array(
'uid'=>getAuthUID(),
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Abgabetool']);
if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'Abgabetool']);
} else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'Abgabetool']);
}
}
public function Student()
public function Student($student_uid_prop = '')
{
$viewData = array(
'uid'=>getAuthUID(),
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolStudent']);
if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolStudent']);
} else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolStudent', 'student_uid_prop' => $student_uid_prop]);
}
}
public function Mitarbeiter()
{
$viewData = array(
'uid'=>getAuthUID(),
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolMitarbeiter']);
if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolMitarbeiter']);
} else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolMitarbeiter']);
}
}
public function Assistenz($stg_kz_prop = '')
{
$viewData = array(
'uid'=>getAuthUID(),
);
if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'AbgabetoolAssistenz']);
} else {
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'AbgabetoolAssistenz', 'stg_kz_prop' => $stg_kz_prop]);
}
}
public function Deadlines()
{
$viewData = array(
'uid'=>getAuthUID(),
);
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'DeadlinesOverview']);
}
public function getStudentProjektarbeitAbgabeFile()
{
$this->_ci =& get_instance();
$this->_ci->load->helper('download');
$paabgabe_id = $this->_ci->input->get('paabgabe_id');
$student_uid = $this->_ci->input->get('student_uid');
if (!isset($paabgabe_id) || isEmptyString($paabgabe_id) || !isset($student_uid) || isEmptyString($student_uid))
$this->terminateWithJsonError($this->p->t('global', 'wrongParameters'), 'general');
$this->_ci->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$isZugeteilterBetreuer = count($this->_ci->ProjektarbeitModel->checkZuordnung($student_uid, getAuthUID())->retval) > 0;
if(getAuthUID() == $student_uid || $isZugeteilterBetreuer) {
$file_path = PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf';
if(file_exists($file_path)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Disposition: attachment; filename="'.basename($file_path).'"');
header('Content-Length: ' . filesize($file_path));
flush(); // send headers first just in case
readfile($file_path); // read file content to output buffer
} else {
$this->terminateWithJsonError('File not found');
}
if(defined('CIS4') && CIS4) {
$this->load->view('CisRouterView/CisRouterView.php', ['viewData' => $viewData, 'route' => 'DeadlinesOverview']);
} else {
$this->terminateWithJsonError('Keine Zuordnung!');
$this->load->view('Cis/Abgabetool.php', ['uid' => getAuthUID(), 'route' => 'DeadlinesOverview']);
}
}
}
@@ -32,9 +32,9 @@ class Studentenverwaltung extends Auth_Controller
'student/keine_studstatuspruefung' => $this->permissionlib->isBerechtigt('student/keine_studstatuspruefung'),
'lehre/reihungstestAufsicht' => $this->permissionlib->isBerechtigt('lehre/reihungstestAufsicht'),
'system/change_outputformat' => $this->permissionlib->getOE_isEntitledFor('system/change_outputformat'),
'student/editBakkZgv' => $this->permissionlib->isBerechtigt('student/editBakkZgv'),
'student/editMakkZgv' => $this->permissionlib->isBerechtigt('student/editMakkZgv'),
'student/editDokZgv' => $this->permissionlib->isBerechtigt('student/editDokZgv'),
'student/editBakkZgv' => $this->permissionlib->getSTG_isEntitledFor('student/editBakkZgv') ?: array(),
'student/editMakkZgv' => $this->permissionlib->getSTG_isEntitledFor('student/editMakkZgv') ?: array(),
'student/editDokZgv' => $this->permissionlib->getSTG_isEntitledFor('student/editDokZgv') ?: array(),
'student/editBismelden' => $this->permissionlib->isBerechtigt('student/editBismelden')
],
'variables' => [
File diff suppressed because it is too large Load Diff
@@ -38,34 +38,9 @@ class Lehre extends FHCAPI_Controller
parent::__construct([
'lvStudentenMail' => self::PERM_LOGGED,
'LV' => self::PERM_LOGGED,
'Pruefungen' => self::PERM_LOGGED,
'getStudentProjektarbeiten' => self::PERM_LOGGED, // TODO: abgabetool berechtigung?
'getStudentProjektabgaben' => self::PERM_LOGGED,
'postStudentProjektarbeitZwischenabgabe' => self::PERM_LOGGED,
'postStudentProjektarbeitEndupload' => self::PERM_LOGGED,
'getMitarbeiterProjektarbeiten' => self::PERM_LOGGED,
'postProjektarbeitAbgabe' => self::PERM_LOGGED,
'deleteProjektarbeitAbgabe' => self::PERM_LOGGED,
'postSerientermin' => self::PERM_LOGGED,
'fetchDeadlines' => self::PERM_LOGGED // TODO: mitarbeiter recht prüfen
'Pruefungen' => self::PERM_LOGGED
]);
$this->load->library('PhrasesLib');
$this->loadPhrases(
array(
'global',
'ui',
'abgabetool'
)
);
$this->load->helper('hlp_sancho_helper');
require_once(FHCPATH . 'include/studiengang.class.php');
require_once(FHCPATH . 'include/student.class.php');
require_once(FHCPATH . 'include/projektarbeit.class.php');
require_once(FHCPATH . 'include/projektbetreuer.class.php');
}
//------------------------------------------------------------------------------------------------------------------
@@ -125,557 +100,5 @@ class Lehre extends FHCAPI_Controller
$this->terminateWithSuccess($result);
}
/**
* fetches all projektabgabetermine for a given projektarbeit_id used in cis4 student abgabetool
*/
public function getStudentProjektabgaben() {
$projektarbeit_id = $this->input->get("projektarbeit_id",TRUE);
// TODO: error messages
if (!isset($projektarbeit_id) || isEmptyString($projektarbeit_id))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
$projektarbeit_obj = new projektarbeit();
if($projektarbeit_id==-1)
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
if(!$projektarbeit_obj->load($projektarbeit_id))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
$paIsCurrent = $projektarbeit_obj->projektarbeitIsCurrent($projektarbeit_id);
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$ret = $this->ProjektarbeitModel->getProjektarbeitAbgabetermine($projektarbeit_id);
// TODO: fetch zweitbetreuer
$this->terminateWithSuccess(array($ret, $paIsCurrent));
}
/**
* fetches all projektarbeiten and betreuer for a given student_uid used in cis4 student abgabetool
*/
public function getStudentProjektarbeiten($uid)
{
$this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel');
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
if (!isset($uid) || isEmptyString($uid))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
$isZugeteilterBetreuer = count($this->ProjektarbeitModel->checkZuordnung($uid, getAuthUID())->retval) > 0;
$this->addMeta('isZugeteilterBetreuer', $isZugeteilterBetreuer);
$isMitarbeiter = $this->MitarbeiterModel->isMitarbeiter(getAuthUID());
if ($isMitarbeiter && $isZugeteilterBetreuer){
$projektarbeiten = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer($uid);
} else {
$projektarbeiten = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer(getAuthUID());
}
$this->terminateWithSuccess(array($projektarbeiten, DOMAIN, $uid));
}
/**
* projektarbeit - upload for zwischenabgaben in cis4 student abgabetool
*/
public function postStudentProjektarbeitZwischenabgabe()
{
$projektarbeit_id = $_POST['projektarbeit_id'];
$paabgabe_id = $_POST['paabgabe_id'];
$student_uid = $_POST['student_uid'];
$bperson_id = $_POST['bperson_id'];
$paabgabetyp_kurzbz = $_POST['paabgabetyp_kurzbz'];
if (!isset($projektarbeit_id) || isEmptyString($projektarbeit_id)
|| !isset($paabgabe_id) || isEmptyString($paabgabe_id)
|| !isset($student_uid) || isEmptyString($student_uid)
|| !isset($paabgabetyp_kurzbz) || isEmptyString($paabgabetyp_kurzbz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
if ((isset($_FILES) and isset($_FILES['file']) and ! $_FILES['file']['error'])) {
move_uploaded_file($_FILES['file']['tmp_name'], PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf');
if(file_exists(PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf')) {
exec('chmod 640 "'.PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf'.'"');
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$res = $this->PaabgabeModel->update($paabgabe_id, array(
'abgabedatum' => date('Y-m-d'),
'updatevon' => getAuthUID(),
'updateamum' => date('Y-m-d H:i:s')
));
$this->sendUploadEmail($bperson_id, $projektarbeit_id, $paabgabetyp_kurzbz, $student_uid);
$this->terminateWithSuccess($res);
} else {
$this->terminateWithError('Error moving File');
}
} else {
$this->terminateWithError('File missing');
}
}
/**
* upload für finale abgaben aka Endupload in cis4 student abgabetool
*/
public function postStudentProjektarbeitEndupload()
{
$projektarbeit_id = $_POST['projektarbeit_id'];
$paabgabe_id = $_POST['paabgabe_id'];
$student_uid = $_POST['student_uid'];
$sprache = $_POST['sprache'];
$abstract = $_POST['abstract'];
$abstract_en = $_POST['abstract_en'];
$schlagwoerter = $_POST['schlagwoerter'];
$schlagwoerter_en = $_POST['schlagwoerter_en'];
$seitenanzahl = $_POST['seitenanzahl'];
$bperson_id = $_POST['bperson_id'];
$paabgabetyp_kurzbz = $_POST['paabgabetyp_kurzbz'];
if (!isset($projektarbeit_id) || isEmptyString($projektarbeit_id)
|| !isset($paabgabe_id) || isEmptyString($paabgabe_id)
|| !isset($student_uid) || isEmptyString($student_uid)
|| !isset($paabgabetyp_kurzbz) || isEmptyString($paabgabetyp_kurzbz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
// TODO: maybe check for other params aswell?
if ((isset($_FILES) and isset($_FILES['file']) and ! $_FILES['file']['error'])) {
move_uploaded_file($_FILES['file']['tmp_name'], PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf');
if(file_exists(PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf')) {
// Loads Libraries
$this->load->library('SignatureLib');
// Check if the document is signed
$signaturVorhanden = true;
$signList = SignatureLib::list(PAABGABE_PATH.$paabgabe_id.'_'.$student_uid.'.pdf');
if (is_array($signList) && count($signList) > 0)
{
// The document is signed
$uploadedDocumentSigned = 'The document is signed';
}
elseif ($signList === null)
{
$uploadedDocumentSigned = 'WARNING: signature server error';
}
else
{
$signaturVorhanden = false;
$uploadedDocumentSigned = 'No document signature found';
}
$this->addMeta('signaturInfo', $uploadedDocumentSigned);
if ($signaturVorhanden === false)
{
$this->signaturFehltEmail($student_uid);
}
// TODO error handle get data has data the updates
// update projektarbeit cols
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$this->ProjektarbeitModel->updateProjektarbeit($projektarbeit_id,$sprache,$abstract,$abstract_en
,$schlagwoerter, $schlagwoerter_en, $seitenanzahl);
// update paabgabe datum
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$res = $this->PaabgabeModel->update($paabgabe_id, array(
'abgabedatum' => date('Y-m-d'),
'updatevon' => getAuthUID(),
'updateamum' => date('Y-m-d H:i:s')
));
$this->sendUploadEmail($bperson_id, $projektarbeit_id, $paabgabetyp_kurzbz, $student_uid);
$this->terminateWithSuccess($res);
} else {
$this->terminateWithError('Error moving File');
}
} else {
$this->terminateWithError('File missing');
}
}
private function signaturFehltEmail($student_uid) {
// Mail an Studiengang wenn keine Signatur gefunden wurde
$student = new student();
if(!$student->load($student_uid))
$this->terminateWithError($this->p->t('global','userNichtGefunden'), 'general');
$stg_obj = new studiengang();
if(!$stg_obj->load($student->studiengang_kz))
$this->terminateWithError($this->p->t('global','fehlerBeimLesenAusDatenbank'), 'general');
$subject = 'Abgabe ohne Signatur';
$tomail = $stg_obj->email;
$data = array(
'vorname' => $student->vorname,
'nachname' => $student->nachname,
'studiengang' => $stg_obj->bezeichnung
);
$mailres = sendSanchoMail(
'ParbeitsbeurteilungSiganturFehlt',
$data,
$tomail,
$subject,
'sancho_header_min_bw.jpg',
'sancho_footer_min_bw.jpg'
);
}
private function sendUploadEmail($bperson_id, $projektarbeit_id, $paabgabetyp_kurzbz, $student_uid) {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$resBetr = $this->ProjektarbeitModel->getProjektbetreuerAnrede($bperson_id);
$projektarbeit_obj = new projektarbeit();
if(!$projektarbeit_obj->load($projektarbeit_id))
$this->terminateWithError('Ungueltiger Eintrag');
$num_rows_sem = $projektarbeit_obj->projektarbeitIsCurrent($projektarbeit_id);
if( null === $num_rows_sem || false === $num_rows_sem )
{
$this->terminateWithError($this->p->t('abgabetool','fehlerAktualitaetProjektarbeit'), 'general');
}
foreach($resBetr->retval as $betreuerRow) {
// query student benutzer view for every betreuer row
$studentUser = $this->ProjektarbeitModel->getProjektarbeitBenutzer($student_uid)->retval[0];
// TODO: hasdata, getData etc
// 1. Begutachter mail ohne Token
$mail_baselink = APP_ROOT."index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/ProjektarbeitsbeurteilungErstbegutachter";
$mail_fulllink = "$mail_baselink?projektarbeit_id=".$projektarbeit_id."&uid=".$studentUser->uid;
$projekttyp_kurzbz = $projektarbeit_obj->projekttyp_kurzbz;
$subject = $projektarbeit_obj->projekttyp_kurzbz == 'Diplom' ? 'Masterarbeitsbetreuung' : 'Bachelorarbeitsbetreuung';
$abgabetyp = $paabgabetyp_kurzbz == 'end' ? 'Endabgabe' : 'Zwischenabgabe';
$maildata = array();
$maildata['geehrt'] = "geehrte".($betreuerRow->anrede=="Herr"?"r":"");
$maildata['anrede'] = $betreuerRow->anrede;
$maildata['betreuer_voller_name'] = $betreuerRow->first;
$maildata['student_anrede'] = $studentUser->anrede;
$maildata['student_voller_name'] = trim($studentUser->titelpre." ".$studentUser->vorname." ".$studentUser->nachname." ".$studentUser->titelpost);
$maildata['abgabetyp'] = $abgabetyp;
$maildata['parbeituebersichtlink'] = "<p><a href='".APP_ROOT."cis/private/lehre/abgabe_lektor_frameset.html'>Zur Projektarbeitsübersicht</a></p>";
$maildata['bewertunglink'] = $num_rows_sem >= 1 && $paabgabetyp_kurzbz == 'end' ? "<p><a href='$mail_fulllink'>Zur Beurteilung der Arbeit</a></p>" : "";
$maildata['token'] = "";
$mailres = sendSanchoMail(
'ParbeitsbeurteilungEndupload',
$maildata,
$betreuerRow->mitarbeiter_uid."@".DOMAIN,
$subject,
'sancho_header_min_bw.jpg',
'sancho_footer_min_bw.jpg',
get_uid()."@".DOMAIN);
if(!$mailres)
{
$this->terminateWithError($this->p->t('abgabetool', 'fehlerMailBegutachter'), 'general');
}
// 2. Begutachter mail, wenn Endabgabe, mit Token wenn extern
if ($paabgabetyp_kurzbz == 'end')
{
// Zweitbegutachter holen
$zweitbegutachter = new projektbetreuer();
$zweitbegutachterRes = $zweitbegutachter->getZweitbegutachterWithToken($bperson_id, $projektarbeit_id, $studentUser->uid);
if ($zweitbegutachterRes)
{
$zweitbegutachterResults = $zweitbegutachter->result;
foreach ($zweitbegutachterResults as $begutachter)
{
// token generieren, wenn noch nicht vorhanden und notwendig (wird in methode überprüft)
$tokenGenRes = $zweitbegutachter->generateZweitbegutachterToken($begutachter->person_id, $projektarbeit_id);
if (!$tokenGenRes)
{
$this->terminateWithError($this->p->t('abgabetool', 'fehlerMailZweitBegutachter'), 'general');
}
// Zweitbegutachter (evtl. mit Token) holen
$zweitbegutachterMitToken = new projektbetreuer();
$begutachterMitTokenRes = $zweitbegutachterMitToken->getZweitbegutachterWithToken($bperson_id, $projektarbeit_id, $studentUser->uid, $begutachter->person_id);
if (!$begutachterMitTokenRes)
{
$this->terminateWithError($this->p->t('abgabetool', 'fehlerMailZweitBegutachter'), 'general');
}
// Email an Zweitbegutachter senden
if (isset($zweitbegutachterMitToken->result[0]))
{
$begutachterMitToken = $zweitbegutachterMitToken->result[0];
$path = $begutachterMitToken->betreuerart_kurzbz == 'Zweitbegutachter' ? 'ProjektarbeitsbeurteilungZweitbegutachter' : 'ProjektarbeitsbeurteilungErstbegutachter';
$mail_baselink = APP_ROOT."index.ci.php/extensions/FHC-Core-Projektarbeitsbeurteilung/$path";
$mail_fulllink = "$mail_baselink?projektarbeit_id=".$projektarbeit_id."&uid=".$studentUser->uid;
$intern = isset($begutachterMitToken->uid);
$mail_link = $intern ? $mail_fulllink : $mail_baselink;
$zweitbetmaildata = array();
$zweitbetmaildata['geehrt'] = "geehrte" . ($begutachterMitToken->anrede == "Herr" ? "r" : "");
$zweitbetmaildata['anrede'] = $begutachterMitToken->anrede;
$zweitbetmaildata['betreuer_voller_name'] = $begutachterMitToken->voller_name;
$zweitbetmaildata['student_anrede'] = $maildata['student_anrede'];
$zweitbetmaildata['student_voller_name'] = $maildata['student_voller_name'];
$zweitbetmaildata['abgabetyp'] = $abgabetyp;
$zweitbetmaildata['parbeituebersichtlink'] = $intern ? $maildata['parbeituebersichtlink'] : "";
$zweitbetmaildata['bewertunglink'] = $num_rows_sem >= 1 ? "<p><a href='$mail_link'>Zur Beurteilung der Arbeit</a></p>" : "";
$zweitbetmaildata['token'] = $num_rows_sem >= 1 && isset($begutachterMitToken->zugangstoken) && !$intern ? "<p>Zugangstoken: " . $begutachterMitToken->zugangstoken . "</p>" : "";
$mailres = sendSanchoMail(
'ParbeitsbeurteilungEndupload',
$zweitbetmaildata,
$begutachterMitToken->email,
$subject,
'sancho_header_min_bw.jpg',
'sancho_footer_min_bw.jpg',
get_uid()."@".DOMAIN
);
if (!$mailres)
{
$this->terminateWithError($this->p->t('abgabetool', 'fehlerMailBegutachter'), 'general');
}
}
}
}
}
}
}
public function getMitarbeiterProjektarbeiten() {
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$boolParamStr = $this->input->get('showall');
$trueStrings = ['true', '1'];
$falseStrings = ['false', '0'];
// Handle missing or invalid parameter
if ($boolParamStr === null) {
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
}
$boolParamStrLower = strtolower($boolParamStr);
if (in_array($boolParamStrLower, $trueStrings, true)) {
$showAllBool = true;
} elseif (in_array($boolParamStrLower, $falseStrings, true)) {
$showAllBool = false;
} else {
// $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
}
$projektarbeiten = $this->ProjektarbeitModel->getMitarbeiterProjektarbeiten(getAuthUID(), $showAllBool);
$this->terminateWithSuccess(array($projektarbeiten, DOMAIN));
}
public function postProjektarbeitAbgabe() {
$projektarbeit_id = $_POST['projektarbeit_id'];
$paabgabe_id = $_POST['paabgabe_id'];
$paabgabetyp_kurzbz = $_POST['paabgabetyp_kurzbz'];
$datum = $_POST['datum'];
$fixtermin = $_POST['fixtermin'];
$kurzbz = $_POST['kurzbz'];
if (!isset($projektarbeit_id) || isEmptyString($projektarbeit_id)
|| !isset($paabgabe_id) || isEmptyString($paabgabe_id)
|| !isset($datum) || isEmptyString($datum)
|| !isset($datum) || isEmptyString($datum)
|| !isset($paabgabetyp_kurzbz) || isEmptyString($paabgabetyp_kurzbz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
if($paabgabe_id == -1) {
$result = $this->PaabgabeModel->insert(
array(
'projektarbeit_id' => $projektarbeit_id,
'paabgabetyp_kurzbz' => $paabgabetyp_kurzbz,
'fixtermin' => $fixtermin,
'datum' => $datum,
'kurzbz' => $kurzbz,
'insertvon' => getAuthUID(),
'insertamum' => date('Y-m-d H:i:s')
)
);
$this->terminateWithSuccess($result);
} else {
$result = $this->PaabgabeModel->update(
$paabgabe_id,
array(
'paabgabetyp_kurzbz' => $paabgabetyp_kurzbz,
'datum' => $datum,
'kurzbz' => $kurzbz,
'updatevon' => getAuthUID(),
'updateamum' => date('Y-m-d H:i:s')
)
);
$this->terminateWithSuccess($result);
}
}
public function deleteProjektarbeitAbgabe() {
$paabgabe_id = $_POST['paabgabe_id'];
if (!isset($paabgabe_id) || isEmptyString($paabgabe_id))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$result = $this->PaabgabeModel->load($paabgabe_id);
$result = $this->getDataOrTerminateWithError($result);
if(count($result) == 0)
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
// TODO: berechtigung?
if($result[0]->insertvon === getAuthUID()) {
$result = $this->PaabgabeModel->delete($paabgabe_id);
$result = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($result);
}
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
}
/**
* endpoint for adding the same paabgabe for multiple projektarbeiten
* can be slow for large n since it queries twice per projektarbeit_id
*/
public function postSerientermin() {
$projektarbeit_ids = $_POST['projektarbeit_ids'];
$datum = $_POST['datum'];
$paabgabetyp_kurzbz = $_POST['paabgabetyp_kurzbz'];
$bezeichnung = $_POST['bezeichnung'];
$kurzbz = $_POST['kurzbz'];
if (!isset($projektarbeit_ids) || !is_array($projektarbeit_ids) || empty($projektarbeit_ids)
|| !isset($datum) || isEmptyString($datum)
|| !isset($kurzbz) || isEmptyString($kurzbz)
|| !isset($bezeichnung) || isEmptyString($bezeichnung)
|| !isset($paabgabetyp_kurzbz) || isEmptyString($paabgabetyp_kurzbz))
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
// old script checks if there already are tbl_paabgabe entries with exact date, type & kurzbz
// for each termin - good to check that in principle but should not matter in this place. if necessary
// duplicate abgabetermine can be easily deleted manually, also via cronjob@night.
// since this entry includes the kurzbz string match, it should have only ever mattered when there were
// multiple users entering the exact same set of (date, type, kurzbz) - which is a much more narrow case than the
// general "saveMultiple" function should handle
// old script afterwards again queries if user is not the zweitbetreuer of any id - this is blocked in the ui
// and should never unintentionally happen
// TODO: check berechtigung &/|| zuordnung?
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$res = [];
foreach ($projektarbeit_ids as $projektarbeit_id) {
$result = $this->PaabgabeModel->insert(
array(
'projektarbeit_id' => $projektarbeit_id,
'paabgabetyp_kurzbz' => $paabgabetyp_kurzbz,
'fixtermin' => false,
'datum' => $datum,
'kurzbz' => $kurzbz,
'insertvon' => getAuthUID(),
'insertamum' => date('Y-m-d H:i:s')
)
);
$data = $this->getDataOrTerminateWithError($result);
// $res[] = $data;
// send mail to student
$result = $this->ProjektarbeitModel->getStudentInfoForProjektarbeitId($projektarbeit_id);
$data = $this->getDataOrTerminateWithError($result);
// $this->addMeta('emaildata'.$projektarbeit_id, $data);
$datetime = new DateTime($datum);
$dateEmailFormatted = $datetime->format('d.m.Y');
$anredeFillString = $data[0]->anrede=="Herr"?"r":"";
$fullFormattedNameString = trim($data[0]->titelpre." ".$data[0]->vorname." ".$data[0]->nachname." ".$data[0]->titelpost);
$res[] = $fullFormattedNameString;
// Prepare mail content
$body_fields = array(
'anrede' => $data[0]->anrede,
'anredeFillString' => $anredeFillString,
'datum' => $dateEmailFormatted,
'bezeichnung' => $bezeichnung,
'fullFormattedNameString' => $fullFormattedNameString,
'kurzbz' => $kurzbz
);
$email = $data[0]->uid."@".DOMAIN;
sendSanchoMail(
'neuerAbgabetermin',
$body_fields,
$email,
$this->p->t('abgabetool', 'neuerTerminBachelorMasterbetreuung')
);
}
$this->terminateWithSuccess($res);
}
public function fetchDeadlines() {
$person_id = $_POST['person_id'];
if (!isset($person_id) || isEmptyString($person_id))
$person_id = getAuthPersonId();
if($person_id !== getAuthPersonId()) {
$this->load->library('PermissionLib');
$isAdmin = $this->permissionlib->isBerechtigt('admin');
if(!$isAdmin) $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), 'general');
}
$this->load->model('education/Paabgabe_model', 'PaabgabeModel');
$result = $this->PaabgabeModel->getDeadlines($person_id);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
}
@@ -19,6 +19,8 @@
if (!defined('BASEPATH'))
exit('No direct script access allowed');
use CI3_Events as Events;
class Setup extends FHCAPI_Controller
{
private $_ci;
@@ -47,24 +49,29 @@ class Setup extends FHCAPI_Controller
{
$tabs['details'] = array (
'title' => 'Details',
'component' => APP_ROOT . 'public/js/components/LVVerwaltung/Tabs/Details.js',
'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/Details.js'),
'config' => []
);
$tabs['gruppen'] = array (
'title' => 'Gruppen',
'component' => APP_ROOT . 'public/js/components/LVVerwaltung/Tabs/Gruppen.js',
'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/Gruppen.js'),
'config' => []
);
$tabs['lektor'] = array (
'title' => 'LektorInnenzuteilung',
'component' => APP_ROOT . 'public/js/components/LVVerwaltung/Tabs/Lektor.js',
'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/Lektor.js'),
'config' => []
);
$tabs['notiz'] = array (
'title' => 'Notizen',
'component' => APP_ROOT . 'public/js/components/LVVerwaltung/Tabs/Notiz.js',
'component' => absoluteJsImportUrl('public/js/components/LVVerwaltung/Tabs/Notiz.js'),
'config' => []
);
Events::trigger('lvv_conf_tabs', function & () use (&$tabs) {
return $tabs;
});
$this->terminateWithSuccess($tabs);
}
@@ -25,7 +25,8 @@ class Studiensemester extends FHCAPI_Controller
array(
'getAll' => self::PERM_LOGGED,
'getAktNext' => self::PERM_LOGGED,
'getStudienjahrByStudiensemester' => self::PERM_LOGGED
'getStudienjahrByStudiensemester' => self::PERM_LOGGED,
'getAllStudiensemesterAndAktOrNext' => self::PERM_LOGGED
)
);
// Load model StudiensemesterModel
@@ -152,4 +153,17 @@ class Studiensemester extends FHCAPI_Controller
$this->terminateWithSuccess((getData(success($studienjahrObj))));
}
public function getAllStudiensemesterAndAktOrNext() {
$this->load->model('organisation/Studiensemester_model', 'StudiensemesterModel');
$this->StudiensemesterModel->addOrder("start", "DESC");
$result = $this->StudiensemesterModel->getAktOrNextSemester();
$aktuell = getData($result)[0];
$this->StudiensemesterModel->addSelect('*');
$result = $this->StudiensemesterModel->load();
$studiensemester = getData($result);
$this->terminateWithSuccess(array($studiensemester, $aktuell));
}
}
@@ -504,7 +504,7 @@ class Config extends FHCAPI_Controller
{
$result['combinePeople'] = [
'title' => $this->p->t('stv', 'tab_combine_people'),
'component' => './Stv/Studentenverwaltung/Details/CombinePeople.js',
'component' => absoluteJsImportUrl('public/js/components/Stv/Studentenverwaltung/Details/CombinePeople.js'),
'config' => $config['combinePeople']
];
}
@@ -239,7 +239,7 @@ class Konto extends FHCAPI_Controller
$data[$field] = $this->input->post($field);
if (defined('FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE') && isset(unserialize(FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE)[$data['buchungstyp_kurzbz']])) {
$data['kostenstelle'] = unserialize(FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE)[$data['buchungstyp_kurzbz']];
$data['studiengang_kz'] = unserialize(FAS_BUCHUNGSTYP_FIXE_KOSTENSTELLE)[$data['buchungstyp_kurzbz']];
}
$result = [];
@@ -43,7 +43,7 @@ class Prestudent extends FHCAPI_Controller
// Load language phrases
$this->loadPhrases([
'ui', 'studierendenantrag', 'lehre'
'ui', 'studierendenantrag', 'lehre', 'global'
]);
}
@@ -98,11 +98,9 @@ class Prestudent extends FHCAPI_Controller
'person_id',
'berufstaetigkeit_code',
'ausbildungcode',
'zgv_code',
'zgvort',
'zgvdatum',
'zgvnation',
'zgvmas_code',
'zgvmaort',
'zgvmadatum',
'zgvmanation',
@@ -110,7 +108,6 @@ class Prestudent extends FHCAPI_Controller
'bismelden',
'anmerkung',
'dual',
'zgvdoktor_code',
'zgvdoktorort',
'zgvdoktordatum',
'zgvdoktornation',
@@ -125,6 +122,57 @@ class Prestudent extends FHCAPI_Controller
'standort_code'
];
// add zgv code fields only if user has permission
$this->load->library('PermissionLib');
$prestudentres = $this->PrestudentModel->load($prestudent_id);
if(!hasData($prestudentres))
{
$this->terminateWithError($this->p->t('ui', 'error_fieldNotFound', ['field' => 'Prestudent ' . $prestudent_id]));
}
$prestudent = (getData($prestudentres))[0];
$bakkZgvStg = $this->permissionlib->getSTG_isEntitledFor('student/editBakkZgv') ?: array();
$makkZgvStg = $this->permissionlib->getSTG_isEntitledFor('student/editMakkZgv') ?: array();
$dokZgvStg = $this->permissionlib->getSTG_isEntitledFor('student/editDokZgv') ?: array();
if(in_array($prestudent->studiengang_kz, $bakkZgvStg))
{
$array_allowed_props_prestudent[] = 'zgv_code';
}
else if(!is_null($this->input->post('zgv_code')))
{
$this->terminateWithError(
$this->p->t('global', 'zgv')
. ' - ' .
$this->p->t('ui', 'error_keineBerechtigungStg')
);
}
if(in_array($prestudent->studiengang_kz, $makkZgvStg))
{
$array_allowed_props_prestudent[] = 'zgvmas_code';
}
else if(!is_null($this->input->post('zgvmas_code')))
{
$this->terminateWithError(
$this->p->t('lehre', 'zgvMaster')
. ' - ' .
$this->p->t('ui', 'error_keineBerechtigungStg')
);
}
if(in_array($prestudent->studiengang_kz, $dokZgvStg))
{
$array_allowed_props_prestudent[] = 'zgvdoktor_code';
}
else if(!is_null($this->input->post('zgvdoktor_code')))
{
$this->terminateWithError(
$this->p->t('lehre', 'zgvDoktor')
. ' - ' .
$this->p->t('ui', 'error_keineBerechtigungStg')
);
}
// add UDFs
$result = $this->udflib->getDefinitionForModel($this->PrestudentModel);
@@ -636,7 +636,7 @@ class Status extends FHCAPI_Controller
$this->load->library('PrestudentLib');
$this->prestudentlib->setFirstStudent(
$resFirstStudent = $this->prestudentlib->setFirstStudent(
$prestudent_id,
$lastAufgenommener->studiensemester_kurzbz,
$lastAufgenommener->ausbildungssemester,
@@ -645,9 +645,8 @@ class Status extends FHCAPI_Controller
$this->input->post('statusgrund_id')
);
$this->getDataOrTerminateWithError($result);
$this->db->trans_commit();
$this->db->trans_complete();
$this->getDataOrTerminateWithError($resFirstStudent);
return $this->outputJsonSuccess(true);
}
@@ -136,14 +136,9 @@ class Student extends FHCAPI_Controller
);
}
$this->PrestudentModel->addSelect(
"(
SELECT status_kurzbz
FROM public.tbl_prestudentstatus pss
WHERE pss.prestudent_id = public.tbl_prestudent.prestudent_id
AND pss.studiensemester_kurzbz = " . $this->PrestudentModel->escape($studiensemester_kurzbz) . "
ORDER BY GREATEST(pss.datum, '0001-01-01') DESC
LIMIT 1
) AS statusofsemester"
"public.get_rolle_prestudent(public.tbl_prestudent.prestudent_id, "
. $this->PrestudentModel->escape($studiensemester_kurzbz)
. ") AS statusofsemester"
);
$this->PrestudentModel->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT');
@@ -801,14 +801,9 @@ class Students extends FHCAPI_Controller
//add status per semester
$this->PrestudentModel->addSelect(
"(
SELECT status_kurzbz
FROM public.tbl_prestudentstatus pss
WHERE pss.prestudent_id = public.tbl_prestudent.prestudent_id
AND pss.studiensemester_kurzbz = " . $this->PrestudentModel->escape($studiensemester_kurzbz) . "
ORDER BY GREATEST(pss.datum, '0001-01-01') DESC
LIMIT 1
) AS statusofsemester"
"public.get_rolle_prestudent(public.tbl_prestudent.prestudent_id, "
. $this->PrestudentModel->escape($studiensemester_kurzbz)
. ") AS statusofsemester"
);
$this->addSelectPrioRel();
@@ -897,14 +892,9 @@ class Students extends FHCAPI_Controller
//add status per semester
$this->PrestudentModel->addSelect(
"(
SELECT status_kurzbz
FROM public.tbl_prestudentstatus pss
WHERE pss.prestudent_id = public.tbl_prestudent.prestudent_id
AND pss.studiensemester_kurzbz = " . $this->PrestudentModel->escape($studiensemester_kurzbz) . "
ORDER BY GREATEST(pss.datum, '0001-01-01') DESC
LIMIT 1
) AS statusofsemester"
"public.get_rolle_prestudent(public.tbl_prestudent.prestudent_id, "
. $this->PrestudentModel->escape($studiensemester_kurzbz)
. ") AS statusofsemester"
);
$this->PrestudentModel->addSelect('UPPER(stg.typ || stg.kurzbz) AS studiengang');
@@ -76,9 +76,7 @@ class Vertrag extends FHCAPI_Controller
if (isError($allOe)) $this->terminateWithError(getError($allOe), self::ERROR_TYPE_GENERAL);
$allOe = hasData($allOe) ? getData($allOe) : [];
$this->addMeta('oe', $allOe);
$allOe = hasData($allOe) ? array_column(getData($allOe), 'oe_kurzbz') : [];
// * then check if the user has permissions to cancel the corresponding lv-organisational units
if (!$this->permissionlib->isBerechtigtMultipleOe('admin', $allOe, 'suid') &&
+33 -8
View File
@@ -33,19 +33,26 @@ class Widget extends Auth_Controller
return $this->outputJsonSuccess([
"widget_id" => 0,
"widget_kurzbz" => "notfound",
"arguments" => json_encode([
"arguments" => [
"className" => 'alert-danger',
"title" => 'Widget Not Found',
"msg" => 'The widget with the id ' . $widget_id . ' could not be found'
]),
"setup" => json_encode([
],
"setup" => [
"name" => 'Widget Not Found',
"file" => 'DashboardWidget/Default.js',
"file" => absoluteJsImportUrl('public/js/components/DashboardWidget/Default.js'),
"width" => 1,
"height" => 1
])
]
]);
return $this->outputJsonSuccess(current(getData($widget)));
$widget = current(getData($widget));
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
return $this->outputJsonSuccess($widget);
}
public function getAll()
@@ -56,7 +63,16 @@ class Widget extends Auth_Controller
if (isError($result))
return $this->outputJsonError(getError($result));
$this->outputJsonSuccess(getData($result) ?: []);
$tmpwidgets = getData($result) ?: [];
$widgets = array_map(function($widget) {
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
return $widget;
}, $tmpwidgets);
$this->outputJsonSuccess($widgets);
}
public function getWidgetsForDashboard()
@@ -71,7 +87,16 @@ class Widget extends Auth_Controller
]);
}
$this->outputJsonSuccess(getData($result) ?: []);
$tmpwidgets = getData($result) ?: [];
$widgets = array_map(function($widget) {
$widget->arguments = json_decode($widget->arguments);
$tmpsetup = json_decode($widget->setup);
$tmpsetup->file = absoluteJsImportUrl($tmpsetup->file);
$widget->setup = $tmpsetup;
return $widget;
}, $tmpwidgets);
$this->outputJsonSuccess($widgets);
}
public function setAllowed()
@@ -0,0 +1,621 @@
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
class AbgabetoolJob extends JOB_Controller
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->_ci =& get_instance();
$this->_ci->load->helper('hlp_sancho_helper');
$this->_ci->load->model('education/Projektarbeit_model', 'ProjektarbeitModel');
$this->_ci->load->model('education/Projektbetreuer_model', 'ProjektbetreuerModel');
$this->_ci->load->model('education/Paabgabe_model', 'PaabgabeModel');
$this->_ci->load->model('crm/Student_model', 'StudentModel');
$this->_ci->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->_ci->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel');
$this->_ci->load->config('abgabe');
$this->loadPhrases([
'abgabetool'
]);
}
public function notifyAssistenzAboutChangedAbgaben() {
$this->_ci->logInfo('Start job FHC-Core->notifyAssistenzAboutChangedAbgaben');
$interval = $this->_ci->config->item('PAABGABE_EMAIL_JOB_INTERVAL');
$relevantTypes = $this->_ci->config->item('RELEVANT_PAABGABETYPEN_SAMMELMAIL_ASSISTENZ');
// get all new or changed termine in interval
$result = $this->_ci->PaabgabeModel->findAbgabenNewOrUpdatedSince($interval, $relevantTypes);
$retval = getData($result);
if(count($retval) == 0) {
$this->_ci->logInfo("Keine Emails an Assistenzen über neue oder veränderte Termine versandt");
return;
}
// group changed/new abgaben for projektarbeiten
$projektarbeiten = [];
foreach($retval as $newOrChangedAbgabe) {
// Check if the current item has a 'projektarbeit_id' field.
// Replace 'projektarbeit_id' with the actual key name if it's different.
if (isset($newOrChangedAbgabe->projektarbeit_id)) {
$projektarbeitId = $newOrChangedAbgabe->projektarbeit_id;
// If the 'projektarbeit_id' is not yet a key in $projektarbeiten,
// initialize it as an empty array.
if (!isset($projektarbeiten[$projektarbeitId])) {
$projektarbeiten[$projektarbeitId] = [];
}
// Add the current row to the array associated with its 'projektarbeit_id'.
$projektarbeiten[$projektarbeitId][] = $newOrChangedAbgabe;
}
}
// for each projektarbeit fetch their assistenz and same them in their own dictionary to avoid too many mails
$assistenzMap = [];
// for each projektarbeit fetch their betreuer and save them in their own dictionary to avoid too many mails
$projektarbeitBetreuerMap = [];
forEach($projektarbeiten as $projektarbeit_id => $abgaben) {
$assistenzResult = $this->_ci->OrganisationseinheitModel->getAssistenzForOE($abgaben[0]->stg_oe_kurzbz);
forEach($assistenzResult->retval as $assistenzRow) {
if (!isset($assistenzMap[$assistenzRow->person_id])) {
$assistenzMap[$assistenzRow->person_id] = [];
}
// Add the current $assistenzRow to the $assistenzMap as an array associated with its projektarbeit_id.
$assistenzMap[$assistenzRow->person_id][] = [$projektarbeit_id, $assistenzRow];
}
$betreuerResult = $this->_ci->ProjektbetreuerModel->getAllBetreuerOfProjektarbeit($projektarbeit_id);
forEach($betreuerResult->retval as $betreuerRow) {
if (!isset($projektarbeitBetreuerMap[$projektarbeit_id])) {
$projektarbeitBetreuerMap[$projektarbeit_id] = [];
}
// Add the current betreuerRow to the betreuerMap as an array associated with its projektarbeit_id.
$projektarbeitBetreuerMap[$projektarbeit_id][] = $betreuerRow;
}
}
$count = 0;
foreach($assistenzMap as $assistenz_person_id => $tupelArr) {
$abgabenString = '<div style="font-family: Arial, sans-serif; color: #333;">';
foreach($tupelArr as $tupel) {
$projektarbeit_id = $tupel[0];
$assistenzRow = $tupel[1];
$betreuerArray = $projektarbeitBetreuerMap[$projektarbeit_id] ?? [];
$changedAbgaben = $projektarbeiten[$projektarbeit_id];
$relevantAbgaben = array_values(array_filter($changedAbgaben, function($abgabetermin) use ($assistenzRow) {
if($abgabetermin->updatevon == null && $abgabetermin->insertvon != $assistenzRow->uid) {
return $abgabetermin;
} else if($abgabetermin->updatevon != null && $abgabetermin->updatevon != $assistenzRow->uid) {
return $abgabetermin;
}
}));
if(count($relevantAbgaben) == 0) {
continue;
}
// Format the Student Name
$s = $relevantAbgaben[0];
$nameParts = [];
if (!empty($s->titelpre)) $nameParts[] = $s->titelpre;
$nameParts[] = $s->vorname;
$nameParts[] = $s->nachname;
if (!empty($s->titelpost)) $nameParts[] = $s->titelpost;
$studentFullName = implode(' ', $nameParts);
// Format the Supervisors string
$betreuerStrings = [];
foreach($betreuerArray as $b) {
$bNameParts = [];
if (!empty($b->titelpre)) $bNameParts[] = $b->titelpre;
$bNameParts[] = $b->vorname;
$bNameParts[] = $b->nachname;
if (!empty($b->titelpost)) $bNameParts[] = $b->titelpost;
$bFullName = implode(' ', $bNameParts);
$betreuerStrings[] = "{$bFullName} ({$b->betreuerart_kurzbz})";
}
$allBetreuerFormatted = implode(', ', $betreuerStrings);
$projektarbeit_titel = $s->titel ?? 'Kein Titel vergeben';
// Project Header Section
$abgabenString .= "
<div style='margin-top: 25px; padding: 12px; background-color: #f8f9fa; border-left: 4px solid #007bff; border-bottom: 1px solid #eee;'>
<strong style='font-size: 16px; color: #0056b3;'>Projekt: {$projektarbeit_titel}</strong><br/>
<div style='margin-top: 5px; font-size: 14px;'>
<strong>Studierende/r:</strong> {$studentFullName}
</div>
<div style='margin-top: 3px; font-size: 14px;'>
<strong>Betreuer:</strong> {$allBetreuerFormatted}
</div>
<span style='color: #666; font-size: 12px;'>
ID: {$projektarbeit_id} | Stg: {$s->stgtyp}{$s->stgkz} ({$s->studiensemester_kurzbz})
</span>
</div>";
// Start Table
$abgabenString .= '
<table style="width: 100%; border-collapse: collapse; margin-bottom: 25px;">
<thead>
<tr style="background-color: #eee; text-align: left;">
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px; width: 20%;">Zieldatum</th>
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px;">Bezeichnung</th>
</tr>
</thead>
<tbody>';
foreach ($relevantAbgaben as $abgabe) {
$dateEmailFormatted = (new DateTime($abgabe->datum))->format('d.m.Y');
$abgabedatumFormatted = (new DateTime($abgabe->abgabedatum))->format('d.m.Y');
$kurzbzLine = !empty($abgabe->kurzbz) ? "<br/><small style='color: #777; font-style: italic;'>{$abgabe->kurzbz}</small>" : "";
$abgabenString .= "
<tr>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px; vertical-align: top;'>{$dateEmailFormatted}</td>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px;'>
<strong>{$abgabe->bezeichnung}</strong>{$kurzbzLine}
</td>
</tr>";
}
$abgabenString .= '</tbody></table>';
}
$abgabenString .= '</div>';
// done with building the change list, now send it
$assistenzRow = $tupelArr[0][1];
$anrede = $assistenzRow->anrede;
$anredeFillString = $assistenzRow->anrede == "Herr" ? "r" : "";
$fullFormattedNameString = $assistenzRow->first;
$path = $this->_ci->config->item('URL_ASSISTENZ');
$url = CIS_ROOT.$path;
$body_fields = array(
'anrede' => $anrede,
'anredeFillString' => $anredeFillString,
'fullFormattedNameString' => $fullFormattedNameString,
'abgabenString' => $abgabenString,
'linkAbgabetool' => $url
);
$email = $assistenzRow->uid."@".DOMAIN;
// send email with bundled info
sendSanchoMail(
'PAAChangesAssSM',
$body_fields,
$email,
$this->p->t('abgabetool', 'changedAbgabeterminev2')
);
$count++;
}
$this->_ci->logInfo($count . " Emails erfolgreich versandt");
$this->_ci->logInfo('End job FHC-Core->notifyAssistenzAboutChangedAbgaben');
}
public function notifyBetreuerAboutChangedAbgaben() {
$this->_ci->logInfo('Start job FHC-Core->notifyBetreuerAboutChangedAbgaben');
$interval = $this->_ci->config->item('PAABGABE_EMAIL_JOB_INTERVAL');
$relevantTypes = $this->_ci->config->item('RELEVANT_PAABGABETYPEN_SAMMELMAIL_BETREUER');
// get all new or changed termine in interval
$result = $this->_ci->PaabgabeModel->findAbgabenNewOrUpdatedSince($interval, $relevantTypes);
$retval = getData($result);
if(count($retval) == 0) {
$this->_ci->logInfo("Keine Emails an Betreuer über neue oder veränderte Termine versandt");
return;
}
// group changed/new abgaben for projektarbeiten
$projektarbeiten = [];
foreach($retval as $newOrChangedAbgabe) {
// Check if the current item has a 'projektarbeit_id' field.
// Replace 'projektarbeit_id' with the actual key name if it's different.
if (isset($newOrChangedAbgabe->projektarbeit_id)) {
$projektarbeitId = $newOrChangedAbgabe->projektarbeit_id;
// If the 'projektarbeit_id' is not yet a key in $projektarbeiten,
// initialize it as an empty array.
if (!isset($projektarbeiten[$projektarbeitId])) {
$projektarbeiten[$projektarbeitId] = [];
}
// Add the current row to the array associated with its 'projektarbeit_id'.
$projektarbeiten[$projektarbeitId][] = $newOrChangedAbgabe;
}
}
// for each projektarbeit fetch their betreuer and save them in their own dictionary to avoid too many mails
$betreuerMap = [];
forEach($projektarbeiten as $projektarbeit_id => $abgaben) {
$betreuerResult = $this->_ci->ProjektbetreuerModel->getAllBetreuerOfProjektarbeit($projektarbeit_id);
forEach($betreuerResult->retval as $betreuerRow) {
if (!isset($betreuerMap[$betreuerRow->person_id])) {
$betreuerMap[$betreuerRow->person_id] = [];
}
// Add the current betreuerRow to the betreuerMap as an array associated with its projektarbeit_id.
$betreuerMap[$betreuerRow->person_id][] = [$projektarbeit_id, $betreuerRow];
}
}
$count = 0;
// now iterate over the betreuerMap and build 1 email about all projektarbeiten and their new/changed termine
// $tupel = [$projektarbeit_id, $betreuerRow], each betreuer has 0..n [projektarbeit_id, changedAbgaben] tupel
forEach($betreuerMap as $betreuer_person_id => $tupelArr) {
// start the container
$abgabenString = '<div style="font-family: Arial, sans-serif; color: #333;">';
$result = $this->_ci->ProjektarbeitModel->getProjektbetreuerAnrede($betreuer_person_id);
$data = getData($result)[0];
$anrede = $data->anrede;
$anredeFillString = $data->anrede == "Herr" ? "r" : "";
$fullFormattedNameString = $data->first;
forEach($tupelArr as $tupel) {
$projektarbeit_id = $tupel[0];
$betreuerRow = $tupel[1];
$changedAbgaben = $projektarbeiten[$projektarbeit_id];
$relevantAbgaben = array_values(array_filter($changedAbgaben, function($abgabetermin) use ($betreuerRow) {
if($abgabetermin->updatevon == null && $abgabetermin->insertvon != $betreuerRow->uid) {
return $abgabetermin;
} else if($abgabetermin->updatevon != null && $abgabetermin->updatevon != $betreuerRow->uid) {
return $abgabetermin;
}
}));
if(count($relevantAbgaben) == 0) {
continue;
}
// format the Student Name
$s = $relevantAbgaben[0];
$nameParts = [];
if (!empty($s->titelpre)) $nameParts[] = $s->titelpre;
$nameParts[] = $s->vorname;
$nameParts[] = $s->nachname;
if (!empty($s->titelpost)) $nameParts[] = $s->titelpost;
$studentFullName = implode(' ', $nameParts);
$projektarbeit_titel = $s->titel ?? 'Kein Titel vergeben';
// project header section
$abgabenString .= "
<div style='margin-top: 25px; padding: 12px; background-color: #f8f9fa; border-left: 4px solid #007bff; border-bottom: 1px solid #eee;'>
<strong style='font-size: 16px; color: #0056b3;'>Projekt: {$projektarbeit_titel}</strong><br/>
<div style='margin-top: 5px; font-size: 14px;'>
<strong>Studierende/r:</strong> {$studentFullName}
</div>
<span style='color: #666; font-size: 12px;'>
ID: {$projektarbeit_id} | Rolle: {$betreuerRow->betreuerart_kurzbz} |
Stg: {$s->stgtyp}{$s->stgkz} ({$s->studiensemester_kurzbz})
</span>
</div>";
// start table
$abgabenString .= '
<table style="width: 100%; border-collapse: collapse; margin-bottom: 25px;">
<thead>
<tr style="background-color: #eee; text-align: left;">
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px; width: 20%;">Zieldatum</th>
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px;">Bezeichnung</th>
</tr>
</thead>
<tbody>';
foreach ($relevantAbgaben as $abgabe) {
$dateEmailFormatted = (new DateTime($abgabe->datum))->format('d.m.Y');
$abgabedatumFormatted = (new DateTime($abgabe->abgabedatum))->format('d.m.Y');
$kurzbzLine = !empty($abgabe->kurzbz) ? "<br/><small style='color: #777; font-style: italic;'>{$abgabe->kurzbz}</small>" : "";
$abgabenString .= "
<tr>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px; vertical-align: top;'>{$dateEmailFormatted}</td>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px;'>
<strong>{$abgabe->bezeichnung}</strong>{$kurzbzLine}
</td>
</tr>";
}
$abgabenString .= '</tbody></table>';
}
// close container
$abgabenString .= '</div>';
// done with building the change list, now send it
$betreuerRow = $tupelArr[0][1];
$path = $this->_ci->config->item('URL_MITARBEITER');
$url = CIS_ROOT.$path;
$body_fields = array(
'anrede' => $anrede,
'anredeFillString' => $anredeFillString,
'fullFormattedNameString' => $fullFormattedNameString,
'abgabenString' => $abgabenString,
'linkAbgabetool' => $url
);
$email = $betreuerRow->uid ? $betreuerRow->uid."@".DOMAIN : $betreuerRow->private_email;
// send email with bundled info
sendSanchoMail(
'PAAChangesBetSM',
$body_fields,
$email,
$this->p->t('abgabetool', 'changedAbgabeterminev2')
);
$count++;
}
$this->_ci->logInfo($count . " Emails erfolgreich versandt");
$this->_ci->logInfo('End job FHC-Core->notifyBetreuerAboutChangedAbgaben');
}
public function notifyBetreuerMail() {
// send all new projektarbeit abgabe UPLOADS since the last job run to the related betreuer
// this job gathers all new or changed file uploads via field 'abgabedatum', enduploads still
// send an email directly after happening since they are kind of important
$this->_ci->logInfo('Start job FHC-Core->notifyBetreuerMail');
// dont filter for relevant types since this mail should gather all UPLOAD info
$interval = $this->_ci->config->item('PAABGABE_EMAIL_JOB_INTERVAL');
$result = $this->_ci->PaabgabeModel->findAbgabenNewOrUpdatedSinceByAbgabedatum($interval);
$retval = getData($result);
// retval are paabgaben joined with projektarbeit and betreuer
if(count($retval) == 0) {
$this->logInfo("Keine Emails über neue Paabgaben an Betreuer versandt");
return;
}
// group contents per betreuer person_id
$betreuer_uids = [];
forEach($retval as $paabgabe) {
if(!isset($betreuer_uids[$paabgabe->person_id])) {
$betreuer_uids[$paabgabe->person_id] = [];
}
$betreuer_uids[$paabgabe->person_id][] = $paabgabe;
}
$count = 0;
forEach ($betreuer_uids as $person_id => $abgaben) {
// $person_id is from betreuer
$result = $this->_ci->ProjektarbeitModel->getProjektbetreuerAnrede($person_id);
$data = getData($result)[0];
$anrede = $data->anrede;
$anredeFillString = $data->anrede == "Herr" ? "r" : "";
$fullFormattedNameString = $data->first;
// sorting $abgaben array by datum
usort($abgaben, function ($a, $b) {
return strtotime($a->datum) <=> strtotime($b->datum);
});
$projektarbeit_titel = $abgaben[0]->titel;
// initialize the table and headers
$abgabenString = '
<table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; color: #333; margin-top: 15px; margin-bottom: 15px;">
<thead>
<tr style="background-color: #f2f2f2; text-align: left;">
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px; width: 15%;">Zieldatum</th>
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px; width: 25%;">Studierende/r</th>
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px;">Bezeichnung</th>
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px; width: 15%;">Abgabedatum</th>
</tr>
</thead>
<tbody>';
foreach ($abgaben as $abgabe) {
// format the student name
$nameParts = [];
if (!empty($abgabe->titelpre)) $nameParts[] = $abgabe->titelpre;
$nameParts[] = $abgabe->vorname;
$nameParts[] = $abgabe->nachname;
if (!empty($abgabe->titelpost)) $nameParts[] = $abgabe->titelpost;
$studentFullName = implode(' ', $nameParts);
// format dates inline
$dateEmailFormatted = (new DateTime($abgabe->datum))->format('d.m.Y');
$abgabedatumFormatted = (new DateTime($abgabe->abgabedatum))->format('d.m.Y');
// handle the optional Kurzbezeichnung
$kurzbzLine = !empty($abgabe->kurzbz) ? "<br/><small style='color: #666; font-style: italic;'>{$abgabe->kurzbz}</small>" : "";
$abgabenString .= "
<tr>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px; vertical-align: top;'>{$dateEmailFormatted}</td>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px; vertical-align: top;'>{$studentFullName}</td>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px;'>
<strong>{$abgabe->bezeichnung}</strong>{$kurzbzLine}
</td>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px; vertical-align: top;'>{$abgabedatumFormatted}</td>
</tr>";
}
$abgabenString .= '</tbody></table>';
$path = $this->_ci->config->item('URL_MITARBEITER');
$url = CIS_ROOT.$path;
$body_fields = array(
'anrede' => $anrede,
'anredeFillString' => $anredeFillString,
'fullFormattedNameString' => $fullFormattedNameString,
'paTitel' => $projektarbeit_titel,
'abgabenString' => $abgabenString,
'linkAbgabetool' => $url
);
$result = $this->_ci->ProjektbetreuerModel->getBetreuerOfProjektarbeit($abgaben[0]->projektarbeit_id, $abgaben[0]->betreuerart_kurzbz);
$data = getData($result)[0];
$email = $data->uid ? $data->uid."@".DOMAIN : $data->private_email;
// send email with bundled info
sendSanchoMail(
'PaabgabeUpdatesBetSM',
$body_fields,
$email,
$this->p->t('abgabetool', 'changedAbgabeterminev2')
);
$count++;
}
$this->_ci->logInfo($count . " Emails erfolgreich versandt");
$this->_ci->logInfo('End job FHC-Core->notifyBetreuerMail');
}
public function notifyStudentMail()
{
// send all new projektarbeit abgabe since the last job run to the related student
$this->_ci->logInfo('Start job FHC-Core->notifyStudentMail');
$interval = $this->_ci->config->item('PAABGABE_EMAIL_JOB_INTERVAL');
$relevantTypes = $this->_ci->config->item('RELEVANT_PAABGABETYPEN_SAMMELMAIL_STUDENT');
$result = $this->_ci->PaabgabeModel->findAbgabenNewOrUpdatedSince($interval, $relevantTypes);
$retval = getData($result);
if(count($retval) == 0) {
$this->_ci->logInfo("Keine Emails an Studenten versandt");
return;
}
// group results per projektarbeit/student_uid
$student_uids = [];
forEach($retval as $paabgabe) {
if(!isset($student_uids[$paabgabe->student_uid])) {
$student_uids[$paabgabe->student_uid] = [];
}
$student_uids[$paabgabe->student_uid][] = $paabgabe;
}
$count = 0;
foreach ($student_uids as $uid => $abgaben) {
// $uid is the student's UID
$result = $this->_ci->StudentModel->getEmailAnredeForStudentUID($uid);
$data = getData($result)[0];
// $abgabe is the array of paabgabe objects
$anredeFillString = $data->anrede=="Herr"?"r":"";
$fullFormattedNameString = trim($data->titelpre." ".$data->vorname." ".$data->vornamen." ".$data->nachname." ".$data->titelpost);
// https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.spaceship-op
// php has spaceships 🚀🚀🚀🚀🚀
usort($abgaben, function($a, $b) {
return strtotime($a->datum) <=> strtotime($b->datum);
});
$projektarbeit_titel = $abgaben[0]->titel;
// initialize the table and headers
$abgabenString = '
<table style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; color: #333; margin-top: 15px; margin-bottom: 15px;">
<thead>
<tr style="background-color: #f2f2f2; text-align: left;">
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px; width: 25%;">Zieldatum</th>
<th style="padding: 10px; border: 1px solid #ddd; font-size: 13px;">Bezeichnung / Hinweis</th>
</tr>
</thead>
<tbody>';
foreach ($abgaben as $abgabe) {
$dateEmailFormatted = (new DateTime($abgabe->datum))->format('d.m.Y');
// handle the optional Kurzbezeichnung
$kurzbzLine = !empty($abgabe->kurzbz) ? "<br/><small style='color: #666; font-style: italic;'>{$abgabe->kurzbz}</small>" : "";
$abgabenString .= "
<tr>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px; vertical-align: top;'>
{$dateEmailFormatted}
</td>
<td style='padding: 10px; border: 1px solid #ddd; font-size: 13px;'>
<strong>{$abgabe->bezeichnung}</strong>{$kurzbzLine}
</td>
</tr>";
}
$abgabenString .= '</tbody></table>';
$route = $this->_ci->config->item('URL_STUDENTS');
$url = CIS_ROOT.$route;
$body_fields = array(
'anrede' => $data->anrede,
'anredeFillString' => $anredeFillString,
'fullFormattedNameString' => $fullFormattedNameString,
'paTitel' => $projektarbeit_titel,
'abgabenString' => $abgabenString,
'linkAbgabetool' => $url
);
// send email with bundled info
sendSanchoMail(
'PaabgabeUpdatesSammelmail',
$body_fields,
$uid.'@'.DOMAIN,
$this->p->t('abgabetool', 'changedAbgabeterminev2')
);
$count++;
}
$this->_ci->logInfo($count . " Emails erfolgreich versandt");
$this->_ci->logInfo('End job FHC-Core->notifyStudentMail');
}
}
+28 -1
View File
@@ -266,7 +266,7 @@ class FHCAPI_Controller extends Auth_Controller
}
// ---------------------------------------------------------------
// Security
// Security Begin
// ---------------------------------------------------------------
/**
@@ -287,4 +287,31 @@ class FHCAPI_Controller extends Auth_Controller
'required_permissions' => $this->_rpsToString($requiredPermissions, $this->router->method)
], self::ERROR_TYPE_AUTH);
}
// ---------------------------------------------------------------
// Security End
// ---------------------------------------------------------------
/**
* Checks the client's total request size (Content-Length) against the minimum
* effective PHP limit (min of upload_max_filesize, post_max_size, memory_limit).
* This preempts failures that result in vague "missing parameters" errors on large files.
*
* @return void
*/
protected function checkUploadSize() {
// this number represents bytes
$content_length_bytes = (int)$this->input->server('CONTENT_LENGTH');
$content_length = $content_length_bytes / 1000000;
//get max serverside size upload -> this comes in megabytes
$max_upload = (int)(ini_get('upload_max_filesize'));
$max_post = (int)(ini_get('post_max_size'));
$memory_limit = (int)(ini_get('memory_limit'));
$max_upload_mb = min($max_upload, $max_post, $memory_limit); // smallest of 3 config values
if($content_length >= $max_upload_mb) {
$this->terminateWithError($this->p->t('global', 'filesizeExceeded'), 'general');
}
}
}
+19 -3
View File
@@ -185,7 +185,15 @@ function generateJSModulesInclude($JSModules)
for ($tmpJSsCounter = 0; $tmpJSsCounter < count($tmpJSs); $tmpJSsCounter++)
{
$toPrint = sprintf($jsInclude, base_url($tmpJSs[$tmpJSsCounter].$cachetoken)).PHP_EOL;
if($ci->config->item('use_fhcomplete_build_version_in_path'))
{
$relurl = preg_replace('#public/#', 'public/' . $ci->config->item('fhcomplete_build_version') . '/', $tmpJSs[$tmpJSsCounter]);
$toPrint = sprintf($jsInclude, base_url($relurl)).PHP_EOL;
}
else
{
$toPrint = sprintf($jsInclude, base_url($tmpJSs[$tmpJSsCounter].$cachetoken)).PHP_EOL;
}
if ($tmpJSsCounter > 0) $toPrint = "\t\t".$toPrint;
@@ -250,6 +258,14 @@ function generateSkipLink($skipID)
function absoluteJsImportUrl($relurl)
{
$ci =& get_instance();
$url = base_url($relurl) . '?'. $ci->config->item('fhcomplete_build_version');
$ci->load->config('javascript');
if($ci->config->item('use_fhcomplete_build_version_in_path'))
{
$url = base_url(preg_replace('#^public/#', 'public/' . $ci->config->item('fhcomplete_build_version') . '/', $relurl));
}
else
{
$url = base_url($relurl) . '?'. $ci->config->item('fhcomplete_build_version');
}
return $url;
}
}
+1 -1
View File
@@ -723,7 +723,7 @@ class PrestudentLib
// Generate Alias
$alias = '';
$alias = null;
if (!defined('GENERATE_ALIAS_STUDENT')
|| GENERATE_ALIAS_STUDENT === true
) {
+8
View File
@@ -279,4 +279,12 @@ class Student_model extends DB_Model
{
return $student_uid . '@' . DOMAIN;
}
public function getEmailAnredeForStudentUID($student_uid) {
$qry = "SELECT anrede, titelpre, vorname, vornamen, nachname, titelpost
FROM campus.vw_student
WHERE uid = ?";
return $this->execReadOnlyQuery($qry, array($student_uid));
}
}
@@ -11,4 +11,12 @@ class Note_model extends DB_Model
$this->dbTable = 'lehre.tbl_note';
$this->pk = 'note';
}
public function getAllActive() {
$qry ="SELECT *
FROM lehre.tbl_note
WHERE aktiv = true";
return $this->execReadOnlyQuery($qry);
}
}
@@ -60,5 +60,58 @@ class Paabgabe_model extends DB_Model
return $this->execReadOnlyQuery($qry, array($person_id));
}
public function findAbgabenNewOrUpdatedSince($interval, $relevantTypes)
{
$query = "SELECT projektarbeit_id, paabgabe_id, paabgabetyp_kurzbz, fixtermin, datum, campus.tbl_paabgabe.kurzbz, campus.tbl_paabgabetyp.bezeichnung, campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon, campus.tbl_paabgabe.insertamum, campus.tbl_paabgabe.updatevon, campus.tbl_paabgabe.updateamum,
campus.tbl_paabgabe.note, upload_allowed, beurteilungsnotiz, student_uid, tbl_projektarbeit.note, lehre.tbl_projektarbeit.titel,
UPPER(tbl_studiengang.typ) as stgtyp, UPPER(tbl_studiengang.kurzbz) as stgkz, public.tbl_studiengang.studiengang_kz,
public.tbl_studiengang.oe_kurzbz as stg_oe_kurzbz, tbl_lehreinheit.studiensemester_kurzbz,
public.tbl_person.anrede, public.tbl_person.titelpre, public.tbl_person.vorname, public.tbl_person.nachname, public.tbl_person.titelpost
FROM campus.tbl_paabgabe
JOIN campus.tbl_paabgabetyp USING (paabgabetyp_kurzbz)
JOIN lehre.tbl_projektarbeit USING (projektarbeit_id)
JOIN lehre.tbl_lehreinheit using(lehreinheit_id)
JOIN lehre.tbl_lehrveranstaltung using(lehrveranstaltung_id)
JOIN public.tbl_studiengang on(lehre.tbl_lehrveranstaltung.studiengang_kz = public.tbl_studiengang.studiengang_kz)
JOIN public.tbl_benutzer ON (public.tbl_benutzer.uid = student_uid)
JOIN public.tbl_person USING (person_id)
WHERE (campus.tbl_paabgabe.insertamum >= NOW() - INTERVAL ?
OR campus.tbl_paabgabe.updateamum >= NOW() - INTERVAL ?)
AND campus.tbl_paabgabe.paabgabetyp_kurzbz IN ?";
return $this->execQuery($query, [$interval, $interval, $relevantTypes]);
}
public function findAbgabenNewOrUpdatedSinceByAbgabedatum($interval) {
$query = "SELECT projektarbeit_id, paabgabe_id, paabgabetyp_kurzbz, fixtermin, datum, kurzbz, campus.tbl_paabgabetyp.bezeichnung, campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon, campus.tbl_paabgabe.insertamum, campus.tbl_paabgabe.updatevon, campus.tbl_paabgabe.updateamum,
campus.tbl_paabgabe.note, upload_allowed, beurteilungsnotiz, student_uid, tbl_projektarbeit.note, lehre.tbl_projektarbeit.titel,
lehre.tbl_projektbetreuer.betreuerart_kurzbz, lehre.tbl_projektbetreuer.person_id,
public.tbl_person.anrede, public.tbl_person.titelpre, public.tbl_person.vorname, public.tbl_person.nachname, public.tbl_person.titelpost
FROM campus.tbl_paabgabe
JOIN campus.tbl_paabgabetyp USING (paabgabetyp_kurzbz)
JOIN lehre.tbl_projektarbeit USING (projektarbeit_id)
JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
JOIN public.tbl_benutzer ON (public.tbl_benutzer.uid = student_uid)
JOIN public.tbl_person ON (public.tbl_benutzer.person_id = public.tbl_person.person_id)
WHERE campus.tbl_paabgabe.abgabedatum IS NOT NULL
AND campus.tbl_paabgabe.abgabedatum >= NOW() - INTERVAL ?
ORDER BY abgabedatum DESC
";
return $this->execQuery($query, [$interval]);
}
public function loadByIDs($paabgabe_ids) {
$qry = "SELECT * FROM campus.tbl_paabgabe WHERE paabgabe_id IN ?";
return $this->execReadOnlyQuery($qry, [$paabgabe_ids]);
}
}
@@ -11,4 +11,10 @@ class Paabgabetyp_model extends DB_Model
$this->dbTable = 'campus.tbl_paabgabetyp';
$this->pk = 'paabgabetyp_kurzbz';
}
public function getAll() {
$qry = "SELECT * FROM campus.tbl_paabgabetyp ORDER BY bezeichnung";
return $this->execReadOnlyQuery($qry);
}
}
@@ -109,36 +109,36 @@ class Projektarbeit_model extends DB_Model
*/
public function getStudentProjektarbeitenWithBetreuer($studentUID)
{
$betreuerQuery = "
SELECT
$betreuerQuery = "SELECT * FROM (SELECT DISTINCT ON(projektarbeit_id)
vorname as bvorname,
nachname as bnachname,
titelpre as btitelpre,
titelpost AS btitelpost,
titelpost AS btitelpost,
tbl_betreuerart.beschreibung AS betreuerart_beschreibung,
(SELECT person_id
FROM lehre.tbl_projektbetreuer
(SELECT person_id
FROM lehre.tbl_projektbetreuer
WHERE projektarbeit_id=tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter') LIMIT 1) AS zweitbetreuer_person_id,
(SELECT betreuerart_kurzbz
FROM lehre.tbl_projektbetreuer
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter') LIMIT 1) AS zweitbetreuer_person_id,
(SELECT betreuerart_kurzbz
FROM lehre.tbl_projektbetreuer
WHERE projektarbeit_id=tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter') LIMIT 1) AS zweitbetreuer_betreuerart_kurzbz,
(SELECT tbl_betreuerart.beschreibung
FROM lehre.tbl_projektbetreuer JOIN lehre.tbl_betreuerart USING(betreuerart_kurzbz)
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter') LIMIT 1) AS zweitbetreuer_betreuerart_kurzbz,
(SELECT tbl_betreuerart.beschreibung
FROM lehre.tbl_projektbetreuer JOIN lehre.tbl_betreuerart USING(betreuerart_kurzbz)
WHERE projektarbeit_id=tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied') LIMIT 1) AS zweitbetreuer_betreuerart_beschreibung,
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied') LIMIT 1) AS zweitbetreuer_betreuerart_beschreibung,
tbl_betreuerart.betreuerart_kurzbz,
person_id as bperson_id,
projektarbeit_id,
lehre.tbl_projekttyp.bezeichnung as projekttypbezeichnung,
lehre.tbl_projekttyp.projekttyp_kurzbz as projekttypkurzbz,
lehre.tbl_lehreinheit.studiensemester_kurzbz,
lehre.tbl_lehrveranstaltung.studiengang_kz,
public.tbl_studiengang.kurzbzlang,
lehre.tbl_projektbetreuer.note as note,
lehre.tbl_projektarbeit.note as note,
lehre.tbl_note.bezeichnung as note_bezeichnung,
public.tbl_mitarbeiter.mitarbeiter_uid,
lehre.tbl_projektarbeit.titel as titel,
lehre.tbl_projektarbeit.sprache as sprache,
@@ -147,9 +147,8 @@ class Projektarbeit_model extends DB_Model
lehre.tbl_projektarbeit.schlagwoerter as schlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter_en as schlagwoerter_en,
lehre.tbl_projektarbeit.abstract as abstract,
lehre.tbl_projektarbeit.abstract_en as abstract_en,
(SELECT abgeschicktvon FROM extension.tbl_projektarbeitsbeurteilung WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id AND betreuer_person_id = tbl_projektbetreuer.person_id) AS babgeschickt,
(SELECT abgeschicktvon FROM extension.tbl_projektarbeitsbeurteilung WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter') LIMIT 1) AS zweitbetreuer_abgeschickt,
lehre.tbl_projektarbeit.abstract_en as abstract_en,
lehre.tbl_projektarbeit.insertamum as insertamum,
(SELECT datum FROM campus.tbl_paabgabe WHERE paabgabetyp_kurzbz = 'end' AND abgabedatum IS NOT NULL AND projektarbeit_id = tbl_projektarbeit.projektarbeit_id LIMIT 1) AS abgegeben
FROM lehre.tbl_projektarbeit
@@ -158,15 +157,17 @@ class Projektarbeit_model extends DB_Model
LEFT JOIN public.tbl_benutzer USING(person_id)
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_betreuerart USING(betreuerart_kurzbz)
LEFT JOIN lehre.tbl_lehreinheit USING(lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id)
LEFT JOIN public.tbl_mitarbeiter ON(public.tbl_mitarbeiter.mitarbeiter_uid = public.tbl_benutzer.uid)
LEFT JOIN public.tbl_studiengang USING(studiengang_kz)
WHERE
tbl_projektarbeit.student_uid = ? AND
LEFT JOIN lehre.tbl_lehreinheit USING(lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING(lehrveranstaltung_id)
LEFT JOIN public.tbl_mitarbeiter ON(public.tbl_mitarbeiter.mitarbeiter_uid = public.tbl_benutzer.uid)
LEFT JOIN public.tbl_studiengang USING(studiengang_kz)
LEFT JOIN lehre.tbl_note ON(lehre.tbl_projektarbeit.note = lehre.tbl_note.note)
WHERE
tbl_projektarbeit.student_uid = ? AND mitarbeiter_uid IS NOT NULL AND
(projekttyp_kurzbz='Bachelor' OR projekttyp_kurzbz='Diplom')
AND betreuerart_kurzbz IN ('Betreuer', 'Begutachter', 'Erstbegutachter', 'Senatsvorsitz')";
AND betreuerart_kurzbz IN ('Betreuer', 'Begutachter', 'Erstbegutachter', 'Senatsvorsitz')) as base
ORDER BY insertamum DESC";
return $this->execReadOnlyQuery($betreuerQuery, array($studentUID));
}
@@ -179,8 +180,12 @@ class Projektarbeit_model extends DB_Model
campus.tbl_paabgabe.fixtermin,
campus.tbl_paabgabe.kurzbz,
campus.tbl_paabgabe.datum,
campus.tbl_paabgabe.note,
campus.tbl_paabgabe.upload_allowed,
campus.tbl_paabgabe.beurteilungsnotiz,
campus.tbl_paabgabetyp.paabgabetyp_kurzbz,
campus.tbl_paabgabetyp.bezeichnung,
campus.tbl_paabgabetyp.benotbar,
campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon
FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz)
@@ -190,16 +195,76 @@ class Projektarbeit_model extends DB_Model
return $this->execReadOnlyQuery($qry, array($projektarbeit_id));
}
public function getProjektarbeitenAbgabetermine($projektarbeiten_ids) {
$qry ="SELECT campus.tbl_paabgabe.paabgabe_id,
campus.tbl_paabgabe.projektarbeit_id,
campus.tbl_paabgabe.fixtermin,
campus.tbl_paabgabe.kurzbz,
campus.tbl_paabgabe.datum,
campus.tbl_paabgabe.note,
campus.tbl_paabgabe.upload_allowed,
campus.tbl_paabgabe.beurteilungsnotiz,
campus.tbl_paabgabetyp.paabgabetyp_kurzbz,
campus.tbl_paabgabetyp.bezeichnung,
campus.tbl_paabgabe.abgabedatum,
campus.tbl_paabgabe.insertvon
FROM campus.tbl_paabgabe JOIN campus.tbl_paabgabetyp USING(paabgabetyp_kurzbz)
WHERE campus.tbl_paabgabe.projektarbeit_id IN ?
ORDER BY campus.tbl_paabgabe.datum";
return $this->execReadOnlyQuery($qry, array($projektarbeiten_ids));
}
public function getProjektbetreuerAnrede($bperson_id) {
$qry_betr="SELECT distinct trim(COALESCE(titelpre,'')||' '||COALESCE(vorname,'')||' '||COALESCE(nachname,'')||' '||COALESCE(titelpost,'')) as first,
public.tbl_mitarbeiter.mitarbeiter_uid, anrede
FROM public.tbl_person JOIN lehre.tbl_projektbetreuer ON(lehre.tbl_projektbetreuer.person_id=public.tbl_person.person_id)
JOIN public.tbl_benutzer ON(public.tbl_benutzer.person_id=public.tbl_person.person_id)
JOIN public.tbl_mitarbeiter ON(public.tbl_benutzer.uid=public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE public.tbl_person.person_id= ?";
$qry_betr="SELECT DISTINCT ON(public.tbl_person.person_id) trim(COALESCE(titelpre,'')||' '||COALESCE(vorname,'')||' '||COALESCE(nachname,'')||' '||COALESCE(titelpost,'')) as first, anrede
FROM public.tbl_person JOIN lehre.tbl_projektbetreuer ON(lehre.tbl_projektbetreuer.person_id=public.tbl_person.person_id)
WHERE public.tbl_person.person_id= ?";
return $this->execReadOnlyQuery($qry_betr, [$bperson_id]);
}
public function getProjektbetreuerEmail($projektarbeit_id) {
$qry = "SELECT (
SELECT kontakt
FROM public.tbl_kontakt
WHERE kontakttyp = 'email'
AND person_id = pers.person_id
ORDER BY
CASE WHEN zustellung THEN 0 ELSE 1 END,
insertamum DESC NULLS LAST
LIMIT 1
) AS private_email, mitarbeiter_uid as uid
FROM lehre.tbl_projektarbeit pa
JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
JOIN public.tbl_person pers USING (person_id)
LEFT JOIN public.tbl_benutzer ben USING (person_id)
LEFT JOIN public.tbl_mitarbeiter ma ON ben.uid = ma.mitarbeiter_uid
WHERE (ben.aktiv OR ben.aktiv IS NULL)
AND projektarbeit_id = ?";
return $this->execReadOnlyQuery($qry, [$projektarbeit_id]);
}
public function getProjektbetreuerEmailByPersonID($person_id) {
$qry = "SELECT (
SELECT kontakt
FROM public.tbl_kontakt
WHERE kontakttyp = 'email'
AND person_id = pers.person_id
ORDER BY
CASE WHEN zustellung THEN 0 ELSE 1 END,
insertamum DESC NULLS LAST
LIMIT 1
) AS private_email, mitarbeiter_uid as uid
FROM lehre.tbl_projektarbeit pa
JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
JOIN public.tbl_person pers USING (person_id)
LEFT JOIN public.tbl_benutzer ben USING (person_id)
LEFT JOIN public.tbl_mitarbeiter ma ON ben.uid = ma.mitarbeiter_uid
WHERE (ben.aktiv OR ben.aktiv IS NULL)
AND person_id = ?";
return $this->execReadOnlyQuery($qry, [$person_id]);
}
public function getProjektarbeitBenutzer($uid) {
@@ -234,9 +299,9 @@ class Projektarbeit_model extends DB_Model
*
FROM
(SELECT tbl_person.vorname, tbl_person.nachname, tbl_studiengang.typ, tbl_studiengang.kurzbz,
tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung, tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id,
tbl_projektbetreuer.betreuerart_kurzbz, tbl_betreuerart.beschreibung AS betreuerart_beschreibung,
tbl_benutzer.uid, tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz
tbl_projektarbeit.projekttyp_kurzbz, tbl_projekttyp.bezeichnung, tbl_projektarbeit.titel, tbl_projektarbeit.projektarbeit_id, tbl_projektarbeit.note,
tbl_projektbetreuer.person_id as betreuer_person_id, tbl_projektbetreuer.betreuerart_kurzbz, tbl_betreuerart.beschreibung AS betreuerart_beschreibung,
tbl_benutzer.uid, tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz, public.tbl_student.student_uid
FROM lehre.tbl_projektarbeit
LEFT JOIN lehre.tbl_projektbetreuer using(projektarbeit_id)
LEFT JOIN lehre.tbl_betreuerart using(betreuerart_kurzbz)
@@ -273,7 +338,134 @@ class Projektarbeit_model extends DB_Model
return $this->execReadOnlyQuery($qry, array($projektarbeit_id));
}
public function getProjektarbeitenForStudiengang($studiengang_kz, $benotet) {
$new_qry = "SELECT DISTINCT ON(tmp.projektarbeit_id) *, campus.get_betreuer_details(tmp.zweitbetreuer_person_id) as zweitbetreuer_full_name, campus.get_betreuer_details(tmp.betreuer_person_id) as erstbetreuer_full_name
FROM(
SELECT
DISTINCT ON(tbl_projektarbeit.projektarbeit_id)
tbl_projektarbeit.projekttyp_kurzbz,
tbl_projektarbeit.titel,
tbl_projektarbeit.projektarbeit_id,
tbl_studiengang.typ, tbl_studiengang.kurzbz,
student_benutzer.uid as student_uid,
student_person.vorname as student_vorname,
student_person.nachname as student_nachname,
tbl_student.matrikelnr, tbl_lehreinheit.studiensemester_kurzbz,
betreuer_benutzer.uid as betreuer_benutzer_uid,
betreuer_person.vorname as betreuer_vorname,
betreuer_person.nachname as betreuer_nachname,
lehre.tbl_projektbetreuer.betreuerart_kurzbz as betreuerart,
lehre.tbl_projektbetreuer.person_id as betreuer_person_id,
lehre.tbl_projektarbeit.sprache as sprache,
lehre.tbl_projektarbeit.seitenanzahl as seitenanzahl,
lehre.tbl_projektarbeit.kontrollschlagwoerter as kontrollschlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter as schlagwoerter,
lehre.tbl_projektarbeit.schlagwoerter_en as schlagwoerter_en,
lehre.tbl_projektarbeit.abstract as abstract,
lehre.tbl_projektarbeit.abstract_en as abstract_en,
lehre.tbl_projektarbeit.insertamum as insertamum,
lehre.tbl_projektarbeit.note as note,
(
SELECT orgform_kurzbz
FROM tbl_prestudentstatus
WHERE prestudent_id = (SELECT prestudent_id
FROM tbl_student
WHERE student_uid = student_benutzer.uid
LIMIT 1)
ORDER BY datum DESC, insertamum DESC, ext_id DESC
LIMIT 1
)
as organisationsform,
(
SELECT person_id
FROM lehre.tbl_projektbetreuer
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
AS zweitbetreuer_person_id,
(
SELECT betreuerart_kurzbz
FROM lehre.tbl_projektbetreuer
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
AS zweitbetreuer_betreuerart_kurzbz,
(
SELECT tbl_betreuerart.beschreibung
FROM lehre.tbl_projektbetreuer
JOIN lehre.tbl_betreuerart USING (betreuerart_kurzbz)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
AS zweitbetreuer_betreuerart_beschreibung,
(
SELECT trim(COALESCE(titelpre, '') || ' ' || COALESCE(vorname, '') || ' ' || COALESCE(nachname, '') || ' ' ||
COALESCE(titelpost, ''))
FROM public.tbl_person
JOIN lehre.tbl_projektbetreuer ON (lehre.tbl_projektbetreuer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_benutzer ON (public.tbl_benutzer.person_id = public.tbl_person.person_id)
LEFT JOIN public.tbl_mitarbeiter ON (public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid)
WHERE projektarbeit_id = tbl_projektarbeit.projektarbeit_id
AND betreuerart_kurzbz IN ('Zweitbetreuer', 'Zweitbegutachter', 'Senatsmitglied')
LIMIT 1
)
as zweitbetreuer_full_name,
(
SELECT
COALESCE(tbl_studienplan.orgform_kurzbz,
tbl_prestudentstatus.orgform_kurzbz, tbl_studiengang.orgform_kurzbz) as
orgform
FROM
public.tbl_prestudent
JOIN public.tbl_prestudentstatus USING(prestudent_id)
JOIN public.tbl_studiensemester USING(studiensemester_kurzbz)
JOIN public.tbl_studiengang USING(studiengang_kz)
LEFT JOIN lehre.tbl_studienplan USING(studienplan_id)
WHERE
prestudent_id=tbl_student.prestudent_id
ORDER BY tbl_prestudentstatus.datum DESC LIMIT 1
) as orgform,
(SELECT status_kurzbz FROM public.tbl_prestudentstatus
WHERE prestudent_id=tbl_student.prestudent_id
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1) as studienstatus
FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer student_benutzer ON (student_benutzer.uid = lehre.tbl_projektarbeit.student_uid)
LEFT JOIN public.tbl_person student_person ON (student_benutzer.person_id = student_person.person_id)
LEFT JOIN public.tbl_student on(student_benutzer.uid = public.tbl_student.student_uid)
LEFT JOIN lehre.tbl_lehreinheit USING (lehreinheit_id)
LEFT JOIN lehre.tbl_lehrveranstaltung USING (lehrveranstaltung_id)
LEFT JOIN public.tbl_studiengang ON (public.tbl_student.studiengang_kz = public.tbl_studiengang.studiengang_kz)
LEFT JOIN lehre.tbl_projekttyp USING (projekttyp_kurzbz)
LEFT JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
LEFT JOIN public.tbl_person betreuer_person ON (betreuer_person.person_id = lehre.tbl_projektbetreuer.person_id)
LEFT JOIN public.tbl_benutzer betreuer_benutzer ON (betreuer_person.person_id = betreuer_benutzer.person_id)
WHERE (projekttyp_kurzbz = 'Bachelor' OR projekttyp_kurzbz = 'Diplom')
AND student_benutzer.aktiv AND (
lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbegutachter'
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Begutachter'
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Betreuer'
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Erstbetreuer'
OR lehre.tbl_projektbetreuer.betreuerart_kurzbz = 'Senatsvorsitz'
)
AND public.tbl_studiengang.studiengang_kz = ?";
if($benotet == 0) {
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NULL ";
} else if ($benotet == 1) {
$new_qry .= " AND lehre.tbl_projektarbeit.note IS NOT NULL ";
}
$new_qry .= " ORDER BY tbl_projektarbeit.projektarbeit_id DESC, student_person.nachname ASC
) as tmp";
return $this->execReadOnlyQuery($new_qry, array($studiengang_kz));
}
/**
*
* @param
@@ -299,4 +491,17 @@ class Projektarbeit_model extends DB_Model
return false;
}
public function getProjektarbeitByPaabgabeID($paabgabe_id) {
$qry = "SELECT
projektarbeit_id
FROM
campus.tbl_paabgabe
JOIN lehre.tbl_projektarbeit USING(projektarbeit_id)
WHERE
campus.tbl_paabgabe.paabgabe_id = ?;
";
return $this->execReadOnlyQuery($qry, [$paabgabe_id]);
}
}
@@ -232,4 +232,41 @@ class Projektbetreuer_model extends DB_Model
return $this->execQuery($qry, array($projektarbeit_id, $betreuer_person_id));
}
/**
* Gets all Betreuer of a Projektarbeit.
* Returns one row for each person.
* @param int $projektarbeit_id
* @return array success with number of Betreuer or error
*/
public function getAllBetreuerOfProjektarbeit($projektarbeit_id)
{
$qry = "SELECT DISTINCT ON (pers.person_id) pers.person_id, betreuerart_kurzbz, vorname, nachname,
trim(COALESCE(titelpre,'')||' '||COALESCE(vorname,'')||' '||COALESCE(nachname,'')||' '||COALESCE(titelpost,'')) as voller_name,
anrede, titelpre, titelpost, gebdatum, geschlecht, pa.projekttyp_kurzbz,
ben.uid, ben.alias, ma.personalnummer, mitarbeiter_uid, student_uid,
(
SELECT kontakt
FROM public.tbl_kontakt
WHERE kontakttyp = 'email'
AND person_id = pers.person_id
ORDER BY
CASE WHEN zustellung THEN 0 ELSE 1 END,
insertamum DESC NULLS LAST
LIMIT 1
) AS private_email
FROM lehre.tbl_projektarbeit pa
JOIN lehre.tbl_projektbetreuer USING (projektarbeit_id)
JOIN public.tbl_person pers USING (person_id)
LEFT JOIN public.tbl_benutzer ben USING (person_id)
LEFT JOIN public.tbl_mitarbeiter ma ON ben.uid = ma.mitarbeiter_uid
WHERE (ben.aktiv OR ben.aktiv IS NULL)
AND projektarbeit_id = ?
ORDER BY pers.person_id, CASE WHEN ma.mitarbeiter_uid IS NULL THEN 1 ELSE 0 END, /*Mitarbeiter account first*/
CASE WHEN ben.uid IS NULL THEN 1 ELSE 0 END, /*user with account first*/
ben.insertamum";
return $this->execQuery($qry, array($projektarbeit_id));
}
}
@@ -243,4 +243,20 @@ class Organisationseinheit_model extends DB_Model
return $this->execReadOnlyQuery($qry, array($oe_kurzbz));
}
public function getAssistenzForOE($oe_kurzbz) {
$qry = "
SELECT person_id, uid, benutzerfunktion_id, funktion_kurzbz, oe_kurzbz, alias,
anrede, trim(COALESCE(titelpre,'')||' '||COALESCE(vorname,'')||' '||COALESCE(nachname,'')||' '||COALESCE(titelpost,'')) as first
FROM tbl_benutzerfunktion
JOIN public.tbl_benutzer USING(uid)
JOIN public.tbl_person USING(person_id)
WHERE funktion_kurzbz = 'ass'
AND oe_kurzbz = ?
AND (datum_bis IS NULL OR NOW() <= datum_bis)
AND public.tbl_benutzer.aktiv = true
";
return $this->execReadOnlyQuery($qry, array($oe_kurzbz));
}
}
@@ -657,37 +657,7 @@ class Studiengang_model extends DB_Model
$this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
$this->load->model('person/Person_model', 'PersonModel');
$this->load->model('crm/Student_model', 'StudentModel');
$addEmailProperty= function(&$benutzerfunktionen){
if(count($benutzerfunktionen) && defined('DOMAIN'))
{
$benutzerfunktionen = array_map(function($benutzer)
{
$benutzer->email = $benutzer->alias."@".DOMAIN;
return $benutzer;
},$benutzerfunktionen) ;
}
};
$addFotoProperty= function(&$collection){
$collection = array_map(function($item){
$person_id = $this->PersonModel->getByUid($item->uid);
if(isError($person_id))
return error($person_id);
$person_id = current(getData($person_id))->person_id;
$this->PersonModel->addSelect('foto');
$foto = $this->PersonModel->loadWhere(array('person_id'=>$person_id));
if(isError($foto))
return error($foto);
$foto = current(getData($foto))->foto;
$item->foto = $foto;
return $item;
},$collection);
};
$this->load->model('crm/Student_model', 'StudentModel');
$student = $this->StudentModel->loadWhere(['student_uid' => getAuthUID()]);
if (isError($student))
return error($student);
@@ -712,7 +682,7 @@ class Studiengang_model extends DB_Model
$stg_ltg = array_values(array_filter($stg_ltg, function($stg_leitung){
return $stg_leitung->aktiv;
}));
$addFotoProperty($stg_ltg);
$this->addFotoProperty($stg_ltg);
$gf_ltg = $this->BenutzerfunktionModel->getBenutzerFunktionenDetailed('gLtg', $stg_obj->oe_kurzbz);
if (isError($gf_ltg))
@@ -721,8 +691,8 @@ class Studiengang_model extends DB_Model
$gf_ltg = array_values(array_filter($gf_ltg, function($gf_leitung){
return $gf_leitung->aktiv;
}));
$addEmailProperty($gf_ltg);
$addFotoProperty($gf_ltg);
$this->addEmailProperty($gf_ltg);
$this->addFotoProperty($gf_ltg);
$stv_ltg = $this->BenutzerfunktionModel->getBenutzerFunktionenDetailed('stvLtg', $stg_obj->oe_kurzbz);
if (isError($stv_ltg))
@@ -731,8 +701,8 @@ class Studiengang_model extends DB_Model
$stv_ltg = array_values(array_filter($stv_ltg, function($stv_leitung){
return $stv_leitung->aktiv;
}));
$addEmailProperty($stv_ltg);
$addFotoProperty($stv_ltg);
$this->addEmailProperty($stv_ltg);
$this->addFotoProperty($stv_ltg);
$ass = $this->BenutzerfunktionModel->getBenutzerFunktionenDetailed('ass', $stg_obj->oe_kurzbz);
if (isError($ass))
@@ -741,8 +711,8 @@ class Studiengang_model extends DB_Model
$ass = array_values(array_filter($ass, function($assistenz){
return $assistenz->aktiv;
}));
$addEmailProperty($ass);
$addFotoProperty($ass);
$this->addEmailProperty($ass);
$this->addFotoProperty($ass);
$hochschulvertr = $this->BenutzerfunktionModel->getBenutzerFunktionenDetailed('hsv');
if (isError($hochschulvertr))
@@ -751,7 +721,7 @@ class Studiengang_model extends DB_Model
$hochschulvertr = array_values(array_filter($hochschulvertr, function($hochschul_vertreter){
return $hochschul_vertreter->aktiv;
}));
$addEmailProperty($hochschulvertr);
$this->addEmailProperty($hochschulvertr);
$stdv = $this->BenutzerfunktionModel->getBenutzerFunktionenDetailed('stdv', $stg_obj->oe_kurzbz);
@@ -761,7 +731,7 @@ class Studiengang_model extends DB_Model
$stdv = array_values(array_filter($stdv, function($std_vertreter){
return $std_vertreter->aktiv;
}));
$addEmailProperty($stdv);
$this->addEmailProperty($stdv);
$jahrgangsvertr = $this->BenutzerfunktionModel->getBenutzerFunktionenDetailed('jgv', $stg_obj->oe_kurzbz, $semester);
@@ -771,7 +741,7 @@ class Studiengang_model extends DB_Model
$jahrgangsvertr = array_values(array_filter($jahrgangsvertr, function($jahrgang_vertreter){
return $jahrgang_vertreter->aktiv;
}));
$addEmailProperty($jahrgangsvertr);
$this->addEmailProperty($jahrgangsvertr);
$result_object = new stdClass();
@@ -870,4 +840,72 @@ class Studiengang_model extends DB_Model
return $this->execQuery($qry, [$studiengang_kz, $studiensemester_kurzbz]);
}
public function getStudiengaengeFiltered($allowed_stg) {
$query ="SELECT DISTINCT
public.tbl_studiengang.studiengang_kz,
public.tbl_studiengang.bezeichnung,
public.tbl_studiengang.kurzbzlang,
public.tbl_studiengang.orgform_kurzbz
FROM public.tbl_studiengang JOIN lehre.tbl_studienordnung USING(studiengang_kz)
JOIN lehre.tbl_studienplan USING(studienordnung_id)
JOIN lehre.tbl_studienplan_semester USING(studienplan_id)
WHERE public.tbl_studiengang.aktiv = true
AND public.tbl_studiengang.studiengang_kz IN ?
ORDER BY public.tbl_studiengang.kurzbzlang";
return $this->execReadOnlyQuery($query, [$allowed_stg]);
}
public function getAssistenzForStudiengangKZ($stg_kz) {
$this->load->model('person/Benutzerfunktion_model', 'BenutzerfunktionModel');
$stg_obj = $this->load($stg_kz);
if(isError($stg_obj))
return error($stg_obj);
if(getData($stg_obj))
{
$stg_obj = current(getData($stg_obj));
}
$ass = $this->BenutzerfunktionModel->getBenutzerFunktionenDetailed('ass', $stg_obj->oe_kurzbz);
if (isError($ass))
return $ass;
$ass = getData($ass) ?: [];
$ass = array_values(array_filter($ass, function($assistenz){
return $assistenz->aktiv;
}));
$this->addEmailProperty($ass);
return success($ass);
}
private function addEmailProperty(&$benutzerfunktionen) {
if(count($benutzerfunktionen) && defined('DOMAIN'))
{
$benutzerfunktionen = array_map(function($benutzer)
{
$benutzer->email = $benutzer->alias."@".DOMAIN;
return $benutzer;
},$benutzerfunktionen) ;
}
}
private function addFotoProperty (&$collection) {
$collection = array_map(function($item){
$person_id = $this->PersonModel->getByUid($item->uid);
if(isError($person_id))
return error($person_id);
$person_id = current(getData($person_id))->person_id;
$this->PersonModel->addSelect('foto');
$foto = $this->PersonModel->loadWhere(array('person_id'=>$person_id));
if(isError($foto))
return error($foto);
$foto = current(getData($foto))->foto;
$item->foto = $foto;
return $item;
},$collection);
}
}
@@ -420,4 +420,17 @@ class Person_model extends DB_Model
return success($result);
}
}
public function loadAllStudentUIDSForPersonID($person_id) {
$qry = "SELECT
CONCAT(tp.vorname, ' ', tp.nachname) AS name,
ARRAY_AGG(DISTINCT b.uid ORDER BY b.uid) AS uids
FROM public.tbl_student s
JOIN public.tbl_benutzer b ON s.student_uid = b.uid
JOIN public.tbl_person tp ON b.person_id = tp.person_id
GROUP BY tp.vorname, tp.nachname, b.aktiv, b.person_id
HAVING b.person_id = ? AND b.aktiv IS TRUE;";
return $this->execReadOnlyQuery($qry, [$person_id]);
}
}
@@ -470,12 +470,12 @@ class Stundenplan_model extends DB_Model
}
foreach($studentlehrverbaende[$sem_date] as $key=>$lehrverband)
{
$query .= "((sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND sp.gruppe = ".$this->escape($lehrverband->gruppe)." AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")";
$query .= "(((sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND sp.gruppe = ".$this->escape($lehrverband->gruppe)." AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")";
// Eintraege fuer den ganzen Verband
$query .= "OR (sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND sp.verband = ".$this->escape($lehrverband->verband)." AND (sp.gruppe is null OR sp.gruppe='') AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)." AND ".$this->escape($sem_date_range->ende).")";
// Eintraege fuer das ganze Semester
$query .= "OR (sp.studiengang_kz = ".$this->escape($lehrverband->studiengang_kz)." AND sp.semester = ".$this->escape($lehrverband->semester)." AND (sp.verband is null OR sp.verband='') AND sp.datum BETWEEN ".$this->escape($sem_date_range->start)
." AND ".$this->escape($sem_date_range->ende).")". $stringGroupLv. ")";
." AND ".$this->escape($sem_date_range->ende).")) AND gruppe_kurzbz is null)";
$query .="OR";
}
+69 -54
View File
@@ -242,74 +242,89 @@ class Message_model extends DB_Model
*/
public function getMessagesForTable($person_id, $offset, $limit)
{
$sql_base = "
SELECT
$sql = <<<EOSQL
with filtered_messages as (
select
m.message_id, m.person_id as sender_id, mr.person_id as recipient_id
from
public.tbl_msg_message m
join
public.tbl_msg_recipient mr on mr.message_id = m.message_id
where
m.person_id = ?
group by
m.message_id, m.person_id, mr.person_id
union all
select
m.message_id, m.person_id as sender_id, mr.person_id as recipient_id
from
public.tbl_msg_message m
join
public.tbl_msg_recipient mr on mr.message_id = m.message_id
where
mr.person_id = ?
group by
m.message_id, m.person_id, mr.person_id
), lastmsgstatus as (
select
ms.*
from (
select
s.message_id, s.person_id, MAX(s.insertamum) as lastinserted
from
public.tbl_msg_status s
group by
s.message_id, s.person_id
) ls
join
public.tbl_msg_status ms on ms.message_id = ls.message_id and ms.person_id = ls.person_id and ms.insertamum = ls.lastinserted
)
select
(select count(*) from filtered_messages) as total_msgs,
m.message_id AS message_id,
m.subject AS subject,
m.body AS body,
m.insertamum AS insertamum,
m.relationmessage_id AS relationmessage_id,
(SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = m.person_id) as sender,
(SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = r.person_id) as recipient,
m.person_id as sender_id,
r.person_id as recipient_id,
MAX(ss.status) as status,
MAX(ss.insertamum) as statusdatum
FROM public.tbl_msg_message m
JOIN public.tbl_msg_recipient r USING(message_id)
JOIN public.tbl_msg_status ss ON(r.message_id = ss.message_id AND ss.person_id = r.person_id)
WHERE m.person_id = ?
GROUP BY m.message_id, m.subject, m.body, m.insertamum, m.relationmessage_id, sender, recipient, sender_id, recipient_id
UNION ALL
SELECT
m.message_id AS message_id,
m.subject AS subject,
m.body AS body,
m.insertamum AS insertamum,
m.relationmessage_id AS relationmessage_id,
(SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = m.person_id) as sender,
(SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = r.person_id) as recipient,
m.person_id as sender_id,
r.person_id as recipient_id,
MAX(ss.status) as status,
MAX(ss.insertamum) as statusdatum
FROM public.tbl_msg_recipient r
JOIN public.tbl_msg_status ss USING(message_id, person_id)
JOIN public.tbl_msg_message m USING(message_id)
WHERE r.person_id = ?
GROUP BY m.message_id, m.subject, m.body, m.insertamum, m.relationmessage_id, sender, recipient, sender_id, recipient_id
";
$sql = "
SELECT COUNT(*) AS count FROM (
" . $sql_base . "
) a
";
$parametersArray = array($person_id, $person_id);
$count = $this->execQuery($sql, $parametersArray);
if (isError($count))
return $count;
$count = ceil(current(getData($count))->count/$limit);
$sql = "
SELECT * FROM (
" . $sql_base . "
) a
ORDER BY insertamum DESC
LIMIT ?
OFFSET ?
";
(COALESCE(ps.titelpre,'') || ' ' || COALESCE(ps.vorname,'') || ' ' || COALESCE(ps.nachname,'') || ' ' || COALESCE(ps.titelpost,'')) as sender,
(COALESCE(pr.titelpre,'') || ' ' || COALESCE(pr.vorname,'') || ' ' || COALESCE(pr.nachname,'') || ' ' || COALESCE(pr.titelpost,'')) as recipient,
fm.sender_id,
fm.recipient_id,
ms.status,
ms.insertamum as statusdatum
from
filtered_messages fm
join
public.tbl_msg_message m on fm.message_id = m.message_id
join
lastmsgstatus ms on fm.message_id = ms.message_id and fm.recipient_id = ms.person_id
left join
public.tbl_person ps on ps.person_id = fm.sender_id
left join
public.tbl_person pr on pr.person_id = fm.recipient_id
order by
m.insertamum DESC
limit ?
offset ?;
EOSQL;
$parametersArray = array($person_id, $person_id, $limit, $offset);
$count = 0;
$data = $this->execQuery($sql, $parametersArray);
if (isError($data))
return $data;
$data = getData($data);
if($data)
{
$count = ceil($data[0]->total_msgs / $limit);
}
return success(['data' => $data, 'count' => $count]);
}
@@ -286,7 +286,13 @@ EOSQL;
foreach( $rows as $row ) {
$tmpgb = new Gehaltsbestandteil();
$tmpgb->hydrateByStdClass($row, true);
if ($row->betrag_valorisiert != null && $row->valorisierungsdatum != null
&& $row->valorisierungsdatum == $row->von) {
// neuer Gehaltsbestandteil mit Valorisierungsdatum aber auch valorisiert
$tmpgb->setGrundbetrag($row->betrag_valorisiert);
}
// prevent duplication (caused by the join with historic values)
if (!isset($lastRecords[(string)$row->gehaltsbestandteil_id])) {
$gehaltsbestandteile[] = $tmpgb;
+47
View File
@@ -0,0 +1,47 @@
<?php
$includesArray = array(
'title' => 'Cis4',
'axios027' => true,
'bootstrap5' => true,
'fontawesome6' => true,
'tabulator5' => true, // TODO: upgrade to 6 when available
'vue3' => true,
'primevue3' => true,
'skipID' => '#fhccontent',
'vuedatepicker11' => true,
'customCSSs' => array(
'public/css/components/verticalsplit.css',
'public/css/components/FilterComponent.css',
'public/css/components/FormUnderline.css',
'public/css/theme/default.css',
'public/css/components/abgabetool/abgabe.css'
),
'customJSs' => array(
'vendor/npm-asset/primevue/accordion/accordion.min.js',
'vendor/npm-asset/primevue/accordiontab/accordiontab.min.js',
'vendor/npm-asset/primevue/checkbox/checkbox.min.js',
'vendor/npm-asset/primevue/inputnumber/inputnumber.min.js',
'vendor/npm-asset/primevue/speeddial/speeddial.min.js',
'vendor/npm-asset/primevue/textarea/textarea.min.js',
'vendor/npm-asset/primevue/timeline/timeline.min.js',
'vendor/npm-asset/primevue/inplace/inplace.min.js',
'vendor/npm-asset/primevue/message/message.min.js',
'vendor/npm-asset/primevue/tieredmenu/tieredmenu.js',
'vendor/moment/luxonjs/luxon.min.js'
),
'customJSModules' => array(
'public/js/apps/Abgabetool/Abgabetool.js',
),
);
$this->load->view('templates/FHC-Header', $includesArray);
?>
<div id="abgabetoolroot" class="h-100" style="max-width: 95%;" route=<?php echo json_encode($route) ?>
uid=<?php echo $uid ?>
student_uid_prop="<?php echo $student_uid_prop ?? '' ?>"
stg_kz_prop="<?php echo $stg_kz_prop ?? '' ?>"
>
</div>
<?php $this->load->view('templates/FHC-Footer', $includesArray); ?>
@@ -21,19 +21,25 @@ $includesArray = array(
'public/css/components/FilterComponent.css',
'public/css/components/Profil.css',
'public/css/components/FormUnderline.css',
'public/css/components/abgabetool/abgabe.css',
'public/css/Cis4/Cms.css',
'public/css/Cis4/Studium.css',
),
'customJSs' => array(
'vendor/npm-asset/primevue/accordion/accordion.min.js',
'vendor/npm-asset/primevue/accordiontab/accordiontab.min.js',
'vendor/npm-asset/primevue/inputnumber/inputnumber.min.js',
'vendor/npm-asset/primevue/textarea/textarea.min.js',
'vendor/npm-asset/primevue/checkbox/checkbox.min.js',
'vendor/npm-asset/primevue/inputnumber/inputnumber.min.js',
'vendor/npm-asset/primevue/speeddial/speeddial.min.js',
'vendor/npm-asset/primevue/textarea/textarea.min.js',
'vendor/npm-asset/primevue/timeline/timeline.min.js',
'vendor/npm-asset/primevue/inplace/inplace.min.js',
'vendor/npm-asset/primevue/message/message.min.js',
'vendor/npm-asset/primevue/tieredmenu/tieredmenu.js',
'vendor/moment/luxonjs/luxon.min.js'
),
'customJSModules' => array(
'public/js/apps/Dashboard/Fhc.js'
'public/js/apps/Dashboard/Fhc.js',
),
);
@@ -36,4 +36,4 @@ $this->load->view('templates/FHC-Header', $includesArray);
></cis-menu>
</header>
<main id="cis-main" class="flex-grow-1 p-4 pt-2">
<main id="cis-main" class="flex-grow-1 p-4 pt-2" style="min-width: 0;">
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

+75 -54
View File
@@ -86,67 +86,88 @@ echo '<?xml version="1.0" encoding="ISO-8859-1" ?>';
<h3>Formel / Formula</h3>
<div style="font-size: large; padding-top: 15px">
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mfrac>
<mn> 5 </mn>
<mn> 3 </mn>
</mfrac>
<mo> + </mo>
<mfrac>
<mn> 7 </mn>
<mn> 6 </mn>
</mfrac>
<mo> = </mo>
<mfrac>
<mn> 10 </mn>
<mn> 6 </mn>
</mfrac>
<mo> + </mo>
<mfrac>
<mn> 7 </mn>
<mn> 6 </mn>
</mfrac>
<mo> = </mo>
<mfrac>
<mn> 17 </mn>
<mn> 6 </mn>
</mfrac>
<mrow>
<mfrac>
<mn>5</mn>
<mn>3</mn>
</mfrac>
<mo>+</mo>
<mfrac>
<mn>7</mn>
<mn>6</mn>
</mfrac>
<mo>=</mo>
<mfrac>
<mn>10</mn>
<mn>6</mn>
</mfrac>
<mo>+</mo>
<mfrac>
<mn>7</mn>
<mn>6</mn>
</mfrac>
<mo>=</mo>
<mfrac>
<mn>17</mn>
<mn>6</mn>
</mfrac>
</mrow>
</math><br/><br/>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mrow>
<munderover>
<mo movablelimits="false">&sum;</mo>
<mn><mi>k</mi>=1</mn>
<mn>5</mn>
</munderover>
<mrow>
<msup>
<mo>(-1)</mo>
<mn><mi>k</mi>+1</mn>
</msup>
</mrow>
<mfrac>
<mrow>
<msup>
<mi>x</mi>
<mn>2<mi>k</mi> + 1</mn>
</msup>
</mrow>
<mrow>
<mo>(2<mi>k</mi>+1)!</mo>
</mrow>
</mfrac>
</mrow>
<mrow>
<munderover>
<mo>&sum;</mo>
<mrow>
<mi>k</mi>
<mo>=</mo>
<mn>1</mn>
</mrow>
<mn>5</mn>
</munderover>
<mrow>
<msup>
<mrow>
<mo>(</mo>
<mo>-</mo>
<mn>1</mn>
<mo>)</mo>
</mrow>
<mrow>
<mi>k</mi>
<mo>+</mo>
<mn>1</mn>
</mrow>
</msup>
<mfrac>
<mrow>
<msup>
<mi>x</mi>
<mrow>
<mn>2</mn>
<mi>k</mi>
<mo>+</mo>
<mn>1</mn>
</mrow>
</msup>
</mrow>
<mrow>
<mo>(</mo>
<mn>2</mn>
<mi>k</mi>
<mo>+</mo>
<mn>1</mn>
<mo>)</mo>
<mo>!</mo>
</mrow>
</mfrac>
</mrow>
</mrow>
</math>
</div>
</div>
<div class="col-sm-6">
<h3>Bild / Picture</h3>
<img alt="Beispielbild" src="MathML_Beispiel.jpg" border="1" height="154" width="233"></img>
<img alt="Beispielbild" src="MathML_Beispiel.png" height="140" style="border: 1px solid black">
</div>
</div>
</div>
+4 -4
View File
@@ -1172,8 +1172,8 @@ if ($frage_id != '')
echo "</td></tr>";
//Vorschau fuer das Text-Feld
echo "<tr><td style='width: 50%'>Vorschau:<br />
<div id='vorschau_frage' style='border: 1px solid black' align='center'>$frage->text</div></td>
<td style='width: 50%'>Derzeit:<br /><div id='aktuell' style='border: 1px solid black' align='center'>$frage->text</div>
<div id='vorschau_frage' style='border: 1px solid black; padding: 5px' align='center'>$frage->text</div></td>
<td style='width: 50%'>Derzeit:<br /><div id='aktuell' style='border: 1px solid black; padding: 5px' align='center'>$frage->text</div>
</td></tr>";
echo "</table>";
echo '</td><td style="border-left: 1px solid black" valign="top">';
@@ -1280,8 +1280,8 @@ if ($frage_id != '')
echo "/></td></tr>";
echo "<tr><td colspan='2' align='right'><input type='submit' name='submitvorschlag' value='Speichern' />".($vorschlag_id != ''?"<input type='button' value='Abbrechen' onclick=\"document.location.href='$PHP_SELF?gebiet_id=$gebiet_id&amp;stg_kz=$stg_kz&amp;nummer=$nummer&amp;frage_id=$frage->frage_id'\" />":'')."</td></tr>";
//Vorschau fuer das Text-Feld
echo "<tr><td colspan='2'>Vorschau:<br /><div id='vorschau_vorschlag' style='border: 1px solid black' align='center'>$vorschlag->text</div>
Derzeit:<br /><div id='aktuellvorschlag' style='border: 1px solid black' align='center'>$vorschlag->text</div></td></tr>";
echo "<tr><td colspan='2'>Vorschau:<br /><div id='vorschau_vorschlag' style='border: 1px solid black; padding: 5px' align='center'>$vorschlag->text</div>
Derzeit:<br /><div id='aktuellvorschlag' style='border: 1px solid black; padding: 5px' align='center'>$vorschlag->text</div></td></tr>";
echo "</table>";
echo "</form>";
echo '</td></tr></table>';
+2 -2
View File
@@ -581,14 +581,14 @@ if($frage->frage_id!='')
else
$value=$p->t('testtool/blaettern').' &gt;&gt;';
echo " <a href='$PHP_SELF?gebiet_id=$gebiet_id&amp;frage_id=$nextfrage' class='Item'>$value</a>";
echo "<a href='$PHP_SELF?gebiet_id=$gebiet_id&amp;frage_id=$nextfrage' class='Item' style='padding-left: 5px'>$value</a>";
}
else
{
if(!$demo)
{
//Wenns der letzte Eintrag ist, wieder zum ersten springen
echo " <a href='$PHP_SELF?gebiet_id=$gebiet_id' class='Item'>".$p->t('testtool/blaettern')." &gt;&gt;</a>";
echo "<a href='$PHP_SELF?gebiet_id=$gebiet_id' class='Item' style='padding-left: 5px'>".$p->t('testtool/blaettern')." &gt;&gt;</a>";
}
}
}
+22 -8
View File
@@ -340,13 +340,26 @@ else
}
}
if ((isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) &&
!isset($_SESSION['confirmation_needed']) && !isset($_SESSION['confirmed_code'])) ||
(isset($_SESSION['confirmation_needed']) && $_SESSION['confirmation_needed'] === true &&
isset($_SESSION['confirmed_code']) && $_SESSION['confirmed_code'] === true &&
isset($_SESSION['externe_ueberwachung']) && $_SESSION['externe_ueberwachung'] === true &&
isset($_SESSION['externe_ueberwachung_verified']) && $_SESSION['externe_ueberwachung_verified'] === true &&
isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id'])))
if (
(
isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id']) &&
!isset($_SESSION['confirmation_needed']) && !isset($_SESSION['confirmed_code']) &&
!isset($_SESSION['externe_ueberwachung']) && !isset($_SESSION['externe_ueberwachung_verified'])
)
||
(
isset($_SESSION['confirmation_needed']) && $_SESSION['confirmation_needed'] === true &&
isset($_SESSION['confirmed_code']) && $_SESSION['confirmed_code'] === true &&
isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id'])
)
||
(
isset($_SESSION['externe_ueberwachung']) && $_SESSION['externe_ueberwachung'] === true &&
isset($_SESSION['externe_ueberwachung_verified']) && $_SESSION['externe_ueberwachung_verified'] === true &&
isset($_SESSION['prestudent_id']) && !isset($_SESSION['pruefling_id'])
)
)
{
$pruefling = new pruefling();
@@ -665,10 +678,11 @@ elseif (isset($prestudent_id))
else
{
// Letzten Status für des Prestudenten einholen
$ps_master = new Prestudent();
$ps_master = new Prestudent($prestudent_id);
$ps_master->getLastStatus($prestudent_id);
$sto = new Studienordnung();
$sto->getStudienordnungFromStudienplan($ps_master->studienplan_id);
$stg = new Studiengang($ps_master->studiengang_kz);
// Name des Studiengangs aus Studienordnung laden, ansonsten Fallback auf Studiengang
$stg_name = $sto->studiengangbezeichnung;
$stg_name_eng = $sto->studiengangbezeichnung_englisch;
+1
View File
@@ -311,6 +311,7 @@ define('EXTERNE_UEBERWACHUNG_TRIAL_TEST', false);
define('EXTERNE_UEBERWACHUNG_EXAM_PARAMS', []);
define('EXTERNE_UEBERWACHUNG_EXAM_RULES', []);
define('EXTERNE_UEBERWACHUNG_EXAM_SCORE', []);
define('EXTERNE_UEBERWACHUNG_EXAM_WARNINGS', []);
?>
@@ -41,6 +41,12 @@ else
$user = get_uid();
loadVariables($user);
if (isset($_GET['studiensemester_kurzbz']))
{
if (check_stsem($_GET['studiensemester_kurzbz']))
$semester_aktuell = $_GET['studiensemester_kurzbz'];
}
//Studiengang laden
$studiengang = new studiengang($studiengang_kz);
+6
View File
@@ -50,6 +50,12 @@ else
$user = get_uid();
loadVariables($user);
if (isset($_GET['studiensemester_kurzbz']))
{
if (check_stsem($_GET['studiensemester_kurzbz']))
$semester_aktuell = $_GET['studiensemester_kurzbz'];
}
$rechte = new benutzerberechtigung();
$rechte->getBerechtigungen($user);
+170 -72
View File
@@ -479,91 +479,182 @@ class anwesenheit extends basis_db
*/
public function loadAnwesenheitStudiensemester($studiensemester_kurzbz, $student_uid=null, $lehrveranstaltung_id=null)
{
$qry = "SELECT
lehrveranstaltung_id, vorname, nachname, wahlname, student_uid as uid, bezeichnung,
gesamt as gesamtstunden, anwesend, nichtanwesend, trunc(100-(nichtanwesend/gesamt)*100,2) as prozent
FROM
(
SELECT
vorname, nachname, wahlname, lehrveranstaltung_id, bezeichnung, gruppe, student_uid,
count(stundenplan_id) as gesamt,
case when anwesend.summe is null then 0 else anwesend.summe end as anwesend,
case when nichtanwesend.summe is null then 0 else nichtanwesend.summe end as nichtanwesend
$qry = "SELECT
lehrveranstaltung_id,
vorname,
nachname,
wahlname,
student_uid AS uid,
bezeichnung,
gesamt AS gesamtstunden,
anwesend,
nichtanwesend,
CASE WHEN gesamt = 0 THEN 100.00 ELSE trunc(100-(nichtanwesend/(gesamt))*100,2) END AS prozent
FROM(
SELECT
vorname,
nachname,
wahlname,
lehrveranstaltung_id,
bezeichnung,
student_uid,
--COUNT(stundenplan_id) AS gesamts,
COALESCE(anwesend.summe, 0) + COALESCE(nichtanwesend.summe, 0) AS gesamt,
COALESCE(anwesend.summe, 0) AS anwesend,
COALESCE(nichtanwesend.summe, 0) AS nichtanwesend
FROM
(
SELECT
sum(stundenplan_id) as stundenplan_id, datum, stunde, lehrveranstaltung_id,
bezeichnung, studiensemester_kurzbz, studiengang_kz,
TRIM(
CASE WHEN stp.gruppe_kurzbz is not null then stp.gruppe_kurzbz
else stp.semester||(case when verband is null then '' else stp.verband end)||(case when stp.gruppe is null then '' else stp.gruppe end) end) as gruppe
SELECT
SUM(stundenplan_id) AS stundenplan_id,
datum,
stunde,
lehrveranstaltung_id,
bezeichnung,
studiensemester_kurzbz,
studiengang_kz
FROM
lehre.tbl_lehrveranstaltung lv
JOIN lehre.tbl_lehreinheit le using (lehrveranstaltung_id)
JOIN lehre.tbl_stundenplan stp using (lehreinheit_id,studiengang_kz)
JOIN
lehre.tbl_lehreinheit le USING (lehrveranstaltung_id)
JOIN
lehre.tbl_stundenplan stp USING (lehreinheit_id,studiengang_kz)
WHERE
studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
AND (titel not like '%Nebenprüfung%' OR titel is null)
group by datum, stunde, lehrveranstaltung_id, bezeichnung, studiensemester_kurzbz, studiengang_kz, stp.gruppe_kurzbz, stp.semester, stp.verband, stp.gruppe
AND (titel NOT LIKE '%Nebenprüfung%' OR titel IS NULL)
GROUP BY
datum,
stunde,
lehrveranstaltung_id,
bezeichnung,
studiensemester_kurzbz,
studiengang_kz,
stp.gruppe_kurzbz,
stp.semester,
stp.verband,
stp.gruppe
)x
JOIN (
SELECT semester::text as gruppe, public.tbl_studentlehrverband.studiensemester_kurzbz, student_uid, studiengang_kz
FROM
public.tbl_studentlehrverband
WHERE studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
UNION
SELECT semester||verband as gruppe, public.tbl_studentlehrverband.studiensemester_kurzbz, student_uid, studiengang_kz
FROM
public.tbl_studentlehrverband
WHERE
studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
UNION
SELECT semester||verband||gruppe as gruppe, public.tbl_studentlehrverband.studiensemester_kurzbz, student_uid, studiengang_kz
FROM
public.tbl_studentlehrverband
WHERE
studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
UNION
SELECT gruppe_kurzbz as gruppe, public.tbl_benutzergruppe.studiensemester_kurzbz, uid as student_uid, studiengang_kz
FROM
public.tbl_benutzergruppe
JOIN
public.tbl_gruppe using (gruppe_kurzbz)
WHERE studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
)a using (gruppe, studiensemester_kurzbz, studiengang_kz)
JOIN public.tbl_benutzer b on b.uid = student_uid
JOIN public.tbl_person p using(person_id)
LEFT JOIN(
SELECT
lehrveranstaltung_id, studiensemester_kurzbz, uid as student_uid, sum(einheiten) as summe
FROM
campus.tbl_anwesenheit a
JOIN lehre.tbl_lehreinheit le using (lehreinheit_id)
JOIN lehre.tbl_lehrveranstaltung lv using (lehrveranstaltung_id)
WHERE
anwesend = true AND studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
le.lehrveranstaltung_id,
slv.student_uid,
le.studiensemester_kurzbz,
st.prestudent_id
FROM
public.tbl_studentlehrverband slv
JOIN
lehre.tbl_lehreinheitgruppe leg
ON slv.studiengang_kz = leg.studiengang_kz
AND slv.semester = leg.semester
AND (
NULLIF(btrim(leg.verband::text), '') IS NULL
OR NULLIF(btrim(slv.verband::text), '') = NULLIF(btrim(leg.verband::text), '')
)
AND (
NULLIF(btrim(leg.gruppe::text), '') IS NULL
OR NULLIF(btrim(slv.gruppe::text), '') = NULLIF(btrim(leg.gruppe::text), '')
)
JOIN
lehre.tbl_lehreinheit le USING (lehreinheit_id, studiensemester_kurzbz)
JOIN
public.tbl_student st USING(student_uid)
WHERE
leg.gruppe_kurzbz IS NULL
AND le.studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
GROUP BY
lehrveranstaltung_id, bezeichnung, uid, studiensemester_kurzbz
)anwesend using(lehrveranstaltung_id, student_uid, studiensemester_kurzbz)
le.lehrveranstaltung_id,
slv.student_uid,
le.studiensemester_kurzbz,
st.prestudent_id
UNION
SELECT
le.lehrveranstaltung_id,
bg.uid AS student_uid,
bg.studiensemester_kurzbz,
st.prestudent_id
FROM
public.tbl_benutzergruppe bg
JOIN
lehre.tbl_lehreinheitgruppe leg USING (gruppe_kurzbz)
JOIN
lehre.tbl_lehreinheit le USING(lehreinheit_id, studiensemester_kurzbz)
JOIN
public.tbl_student st ON bg.uid = st.student_uid
WHERE
bg.studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
GROUP BY
le.lehrveranstaltung_id,
bg.uid,
bg.studiensemester_kurzbz,
st.prestudent_id
)a USING (lehrveranstaltung_id, studiensemester_kurzbz)
JOIN
public.tbl_benutzer b on b.uid = student_uid
JOIN
public.tbl_person p using(person_id)
LEFT JOIN(
SELECT lehrveranstaltung_id, studiensemester_kurzbz, uid as student_uid, sum(einheiten) as summe
SELECT
lehrveranstaltung_id,
studiensemester_kurzbz,
uid AS student_uid,
SUM(einheiten) AS summe
FROM
campus.tbl_anwesenheit a
JOIN lehre.tbl_lehreinheit le using (lehreinheit_id)
JOIN lehre.tbl_lehrveranstaltung lv using (lehrveranstaltung_id)
JOIN
lehre.tbl_lehreinheit le USING(lehreinheit_id)
JOIN
lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id)
WHERE
anwesend = false AND studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
anwesend = true
AND studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
GROUP BY
lehrveranstaltung_id, bezeichnung, uid, studiensemester_kurzbz
)nichtanwesend using(lehrveranstaltung_id, student_uid, studiensemester_kurzbz)
lehrveranstaltung_id,
bezeichnung,
uid,
studiensemester_kurzbz
)anwesend USING(lehrveranstaltung_id, student_uid, studiensemester_kurzbz)
LEFT JOIN(
SELECT
lehrveranstaltung_id,
studiensemester_kurzbz,
uid AS student_uid,
SUM(einheiten) AS summe
FROM
campus.tbl_anwesenheit a
JOIN
lehre.tbl_lehreinheit le USING (lehreinheit_id)
JOIN
lehre.tbl_lehrveranstaltung lv USING (lehrveranstaltung_id)
WHERE
anwesend = false
AND studiensemester_kurzbz = ".$this->db_add_param($studiensemester_kurzbz)."
GROUP BY
lehrveranstaltung_id,
bezeichnung,
uid,
studiensemester_kurzbz
)nichtanwesend USING(lehrveranstaltung_id, student_uid, studiensemester_kurzbz)
WHERE
lehrveranstaltung_id > 0
";
@@ -573,8 +664,15 @@ class anwesenheit extends basis_db
if(!is_null($lehrveranstaltung_id))
$qry.=" AND lehrveranstaltung_id=".$this->db_add_param($lehrveranstaltung_id);
$qry.="group by
vorname, nachname, wahlname, lehrveranstaltung_id, bezeichnung, gruppe, student_uid, anwesend.summe, nichtanwesend.summe
$qry.=" GROUP BY
vorname,
nachname,
wahlname,
lehrveranstaltung_id,
bezeichnung,
student_uid,
anwesend.summe,
nichtanwesend.summe
)m";
if($lehrveranstaltung_id != '')
+5
View File
@@ -168,6 +168,11 @@ class externeUeberwachung extends basis_db
$payload = array_merge($payload, EXTERNE_UEBERWACHUNG_EXAM_PARAMS);
}
if (defined('EXTERNE_UEBERWACHUNG_EXAM_WARNINGS'))
{
$payload['visibleWarnings'] = EXTERNE_UEBERWACHUNG_EXAM_WARNINGS;
}
if (defined('EXTERNE_UEBERWACHUNG_EXAM_RULES'))
{
$payload['rules'] = EXTERNE_UEBERWACHUNG_EXAM_RULES;
+48 -1
View File
@@ -345,6 +345,7 @@ class gebiet extends basis_db
}
//Pruefen ob jede Fragen mindestens 2 Vorschlaege hat
//Angepasst am 28.01.2026 auf ein Warning.
$qry = "SELECT frage_id, nummer FROM testtool.tbl_frage
WHERE (SELECT count(*) as anzahl FROM testtool.tbl_vorschlag WHERE frage_id=tbl_frage.frage_id)<2
AND gebiet_id=".$this->db_add_param($gebiet_id, FHC_INTEGER)." AND NOT demo;";
@@ -352,7 +353,7 @@ class gebiet extends basis_db
{
while($row = $this->db_fetch_object())
{
$this->errormsg .= "Frage Nummer $row->nummer (ID: $row->frage_id) hat weniger als 2 Vorschlaege.\n";
$this->warningmsg .= "Frage Nummer $row->nummer (ID: $row->frage_id) hat weniger als 2 Vorschlaege.\n";
}
}
@@ -448,6 +449,52 @@ class gebiet extends basis_db
}
}
//Pruefen ob es leere Fragen (ohne Text, Bild oder Audio) gibt
$qry = "SELECT
fr.frage_id,
fr.nummer,
fs.sprache
FROM
testtool.tbl_frage fr
JOIN testtool.tbl_frage_sprache fs
USING (frage_id)
WHERE
(fs.text IS NULL
OR fs.text = '')
AND fs.bild IS NULL
AND fs.audio IS NULL
AND demo = false
AND gebiet_id=".$this->db_add_param($gebiet_id, FHC_INTEGER)."
AND EXISTS (
SELECT
1
FROM
testtool.tbl_frage fr2
JOIN testtool.tbl_frage_sprache fs2
USING (frage_id)
WHERE
fs2.sprache = fs.sprache
AND fr2.gebiet_id = fr.gebiet_id
AND fr2.frage_id != fr.frage_id
AND (
(fs2.text IS NOT NULL
AND fs2.text != '')
OR fs2.bild IS NOT NULL
OR fs2.audio IS NOT NULL
)
AND demo = false
)
ORDER BY
fs.sprache,
fr.nummer;";
if($this->db_query($qry))
{
while($row = $this->db_fetch_object())
{
$this->warningmsg .= "Frage Nummer $row->nummer (ID: $row->frage_id), Sprache $row->sprache hat keinen Text, Bild oder Audio.\n";
}
}
if($this->errormsg=='')
return true;
else
+8 -1
View File
@@ -513,6 +513,7 @@ class statistik extends basis_db
$this->json=array();
$this->countRows=0;
set_time_limit(600);
$parseHtml = (strpos($this->preferences, 'parseHTML: true') !== false);
// In case a decryption function is used then perform password substitution
$this->sql = $this->replaceSQLDecryptionPassword($this->sql);
@@ -565,7 +566,13 @@ class statistik extends basis_db
for($spalte=0;$spalte<$anzahl_spalten;$spalte++)
{
$name = $this->db_field_name($this->data,$spalte);
$this->html.= '<td>'.$this->convert_html_chars($row->$name).'</td>';
if ($parseHtml) {
// HTML direkt rendern
$this->html .= '<td>'.($row->$name).'</td>';
} else {
// wie bisher escapen
$this->html .= '<td>'.$this->convert_html_chars($row->$name).'</td>';
}
// Umwandeln von Punkt in Komma bei Float-Werten
if (is_numeric($row->$name))
{
+4 -2
View File
@@ -53,8 +53,10 @@ $this->phrasen['testtool/fuerFolgendeStgAngemeldet']='Für folgende Studiengäng
$this->phrasen['testtool/invalideGebiete']='Ein oder mehrere Fragengebiet/e inkorrekt!<br>Bitte melden Sie dies der Betreuungsperson.';
$this->phrasen['testtool/confirmationText']='<b>Ich bestätige, den Online-Reihungstest persönlich, selbständig, ohne Hilfe einer zusätzlichen Person und ohne Hilfsmittel zu absolvieren.<br>
I confirm that I will complete the online placement test personally, independently, without the help of an additional person and without any aids.</b>';
$this->phrasen['testtool/dsgvoConfirmText']='<b>Ich habe die <a href ="'. APP_ROOT. 'cms/dms.php?id=373121" target="_blank">Datenschutzerklärung</a> gelesen.</b>';
$this->phrasen['testtool/procotoringConfirmText']='<b>Ich stimme der digitalen Beaufsichtigung beim Online-Reihungstest (Proctoring) zu.</b>';
$this->phrasen['testtool/dsgvoConfirmText']='<b>Ich habe die <a href ="'. APP_ROOT. 'cms/dms.php?id=373121" target="_blank">Datenschutzerklärung</a> gelesen. <br />
I have read the <a href ="'. APP_ROOT. 'cms/dms.php?id=374814" target="_blank">privacy policy</a>.</b>';
$this->phrasen['testtool/procotoringConfirmText']='<b>Ich stimme der digitalen Beaufsichtigung beim Online-Reihungstest (Proctoring) zu. <br />
I agree to the digital supervision of the online placement test (proctoring).</b>';
$this->phrasen['testtool/loginNoetig']='Bitte beachten Sie, dass der Reihungstest erst <b>unmittelbar</b> vor Ihrem <b>Reihungstesttermin</b> von uns aktiviert wird.<br>
Please note that the test will be activated by us <b>immediately</b> before your <b>placement test date</b>.';
$this->phrasen['testtool/start']='Reihungstest jetzt starten';
+9
View File
@@ -0,0 +1,9 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^[0-9]{10}/(.*)$ $1 [L]
RewriteCond %{REQUEST_URI} ^(.*?)/public/index.ci.php/
RewriteRule ^index.ci.php/(.*)$ %1/index.ci.php/$1 [R=303]
</IfModule>
+6 -1
View File
@@ -407,7 +407,6 @@ html {
background-color: var(--fhc-background);
border-color: var(--fhc-border);
padding: var(--fhc-cis-main-py) var(--fhc-cis-main-px);
min-width: 0; /* fix flex-grow with tabulator exceeding width */
}
#cis-main .fa-arrow-up-right-from-square {
@@ -856,3 +855,9 @@ html {
background-color: var(--fhc-secondary);
}
.bordered-modal {
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.5rem;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
+308
View File
@@ -0,0 +1,308 @@
/* Base Header */
.verpasst-header {
background-color: var(--fhc-red-70);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.verpasst-header:hover {
background-color: var(--fhc-red-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .verpasst-header {
background-color: var(--fhc-red-50);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .verpasst-header:hover {
background-color: var(--fhc-red-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Base Header */
.abzugeben-header {
background-color: var(--fhc-yellow-70);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.abzugeben-header:hover {
background-color: var(--fhc-yellow-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .abzugeben-header {
background-color: var(--fhc-yellow-50);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .abzugeben-header:hover {
background-color: var(--fhc-yellow-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Base Header */
.beurteilungerforderlich-header {
background-color: var(--fhc-orange-70);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.beurteilungerforderlich-header:hover {
background-color: var(--fhc-orange-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .beurteilungerforderlich-header {
background-color: var(--fhc-orange-50);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .beurteilungerforderlich-header:hover {
background-color: var(--fhc-orange-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Base Header */
.verspaetet-header {
background-color: var(--fhc-pink-40);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.verspaetet-header:hover {
background-color: var(--fhc-pink-20);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .verspaetet-header {
background-color: var(--fhc-pink-30);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .verspaetet-header:hover {
background-color: var(--fhc-pink-20);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Base Header */
.abgegeben-header {
background-color: var(--fhc-green-70);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.abgegeben-header:hover {
background-color: var(--fhc-green-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .abgegeben-header {
background-color: var(--fhc-green-50);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .abgegeben-header:hover {
background-color: var(--fhc-green-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Base Header */
.bestanden-header {
background-color: var(--fhc-green-70);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.bestanden-header:hover {
background-color: var(--fhc-green-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .bestanden-header {
background-color: var(--fhc-green-50);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .bestanden-header:hover {
background-color: var(--fhc-green-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Base Header */
.nichtbestanden-header {
background-color: var(--fhc-red-70);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.nichtbestanden-header:hover {
background-color: var(--fhc-red-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .nichtbestanden-header {
background-color: var(--fhc-red-50);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .nichtbestanden-header:hover {
background-color: var(--fhc-red-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Base Header */
.erledigt-header {
background-color: var(--fhc-red-70);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.erledigt-header:hover {
background-color: var(--fhc-red-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .erledigt-header {
background-color: var(--fhc-red-50);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .erledigt-header:hover {
background-color: var(--fhc-red-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Base Header */
.standard-header {
background-color: var(--fhc-white-70);
font-weight: 600;
border-radius: 6px;
padding: 0px 0px 0px 34px;
transition: background-color 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Hover State */
.standard-header:hover {
background-color: var(--fhc-white-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Active / Expanded State */
.p-accordion-tab-active > .standard-header {
background-color: var(--fhc-white-50);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
/* Hover State Active*/
.p-accordion-tab-active > .standard-header:hover {
background-color: var(--fhc-white-60);
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
#abgabetoolroot .modal-header{
background-color: var(--fhc-blue-primary);
color: var(--fhc-white-50);
}
#abgabetoolroot .modal-header .btn-close{
filter: invert(1);
}
#abgabetoolroot .modal-footer {
background-color: var(--fhc-white-20);
}
.bordered-modal {
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.5rem;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
.bordered-modal .modal-body {
overflow-y: visible;
}
.p-accordion .p-accordion-header .p-accordion-header-link {
padding: 12px!important;
}
/* 1. Stick the Header */
#abgabetable .tabulator-header .tabulator-col.sticky-col {
position: sticky;
left: 0;
z-index: 10; /* Must be higher than other headers */
background-color: #fff; /* Opaque background is required */
border-right: 2px solid #ddd; /* Optional: Separator line */
}
/* 2. Stick the Data Cells */
#abgabetable .tabulator-tableholder .tabulator-row .tabulator-cell.sticky-col {
position: sticky;
left: 0;
z-index: 10; /* Ensure it floats above other cells */
background-color: #fff; /* Match your row background color */
border-right: 2px solid #ddd; /* Optional: Separator line */
}
/* 3. Fix for Hover Effects (Optional) */
/* If you use hover rows, you need to ensure the sticky cell matches the hover color */
#abgabetable .tabulator-row:hover .tabulator-cell.sticky-col {
background-color: #ccc; /* Match your existing hover color */
}
+8
View File
@@ -51,6 +51,14 @@
background-color: #6d4c41;
}
.tag_dark_grey {
background-color: #595959;
}
.tag_light_grey {
background-color: #9a9a9a;
}
.tag_blau {
background-color: #508498;
}
+92 -3
View File
@@ -38,15 +38,104 @@
--fhc-blue-primary: #006095;
--fhc-blue-primary-highlight: #0086CB;
--fhc-red-10: #842029;
--fhc-red-20: #800000;
/* --- Green --- */
--fhc-green-5: rgb(240, 250, 240);
--fhc-green-10: rgb(220, 245, 220);
--fhc-green-20: rgb(190, 235, 190);
--fhc-green-30: rgb(150, 220, 150);
--fhc-green-40: rgb(110, 200, 110);
--fhc-green-50: rgb(70, 170, 70);
--fhc-green-60: rgb(50, 145, 50);
--fhc-green-70: rgb(35, 120, 35);
--fhc-green-80: rgb(25, 95, 25);
--fhc-green-90: rgb(15, 70, 15);
--fhc-green-10: #008000;
/* --- Red --- */
--fhc-red-5: rgb(255, 245, 246);
--fhc-red-10: rgb(255, 225, 228);
--fhc-red-20: rgb(250, 190, 195);
--fhc-red-30: rgb(240, 150, 160);
--fhc-red-40: rgb(225, 110, 120);
--fhc-red-50: rgb(200, 70, 85);
--fhc-red-60: rgb(170, 50, 65);
--fhc-red-70: rgb(140, 35, 50);
--fhc-red-80: rgb(110, 20, 35);
--fhc-red-90: rgb(85, 10, 25);
/* --- Yellow --- */
--fhc-yellow-5: rgb(255, 255, 240);
--fhc-yellow-10: rgb(255, 250, 210);
--fhc-yellow-20: rgb(255, 240, 160);
--fhc-yellow-30: rgb(255, 225, 100);
--fhc-yellow-40: rgb(250, 210, 50);
--fhc-yellow-50: rgb(240, 190, 0);
--fhc-yellow-60: rgb(220, 165, 0);
--fhc-yellow-70: rgb(190, 135, 0);
--fhc-yellow-80: rgb(160, 105, 0);
--fhc-yellow-90: rgb(120, 75, 0);
/* --- Pink --- */
--fhc-pink-5: rgb(255, 245, 250);
--fhc-pink-10: rgb(255, 225, 235);
--fhc-pink-20: rgb(250, 195, 215);
--fhc-pink-30: rgb(245, 160, 190);
--fhc-pink-40: rgb(235, 120, 160);
--fhc-pink-50: rgb(220, 80, 130);
--fhc-pink-60: rgb(190, 60, 110);
--fhc-pink-70: rgb(160, 40, 90);
--fhc-pink-80: rgb(130, 25, 70);
--fhc-pink-90: rgb(100, 15, 50);
/* --- Orange --- */
--fhc-orange-5: rgb(255, 250, 240);
--fhc-orange-10: rgb(255, 235, 200);
--fhc-orange-20: rgb(255, 210, 140);
--fhc-orange-30: rgb(255, 185, 80);
--fhc-orange-40: rgb(255, 155, 40);
--fhc-orange-50: rgb(255, 128, 0);
--fhc-orange-60: rgb(230, 110, 0);
--fhc-orange-70: rgb(200, 90, 0);
--fhc-orange-80: rgb(170, 70, 0);
--fhc-orange-90: rgb(130, 50, 0);
--fhc-beige-10: rgba(245, 233, 215, 0.5);
--fhc-beige-20: rgba(172, 153, 125, 0.5);
/* --- Purple --- */
--fhc-purple-5: rgb(250, 245, 255);
--fhc-purple-10: rgb(240, 230, 255);
--fhc-purple-20: rgb(220, 200, 250);
--fhc-purple-30: rgb(190, 160, 245);
--fhc-purple-40: rgb(160, 120, 235);
--fhc-purple-50: rgb(130, 80, 220);
--fhc-purple-60: rgb(110, 60, 190);
--fhc-purple-70: rgb(90, 40, 160);
--fhc-purple-80: rgb(70, 25, 130);
--fhc-purple-90: rgb(50, 15, 100);
/* --- Teal --- */
--fhc-teal-5: rgb(240, 252, 252);
--fhc-teal-10: rgb(220, 245, 245);
--fhc-teal-20: rgb(180, 235, 235);
--fhc-teal-30: rgb(130, 220, 220);
--fhc-teal-40: rgb(80, 200, 200);
--fhc-teal-50: rgb(30, 170, 170);
--fhc-teal-60: rgb(20, 140, 140);
--fhc-teal-70: rgb(15, 115, 115);
--fhc-teal-80: rgb(10, 90, 90);
--fhc-teal-90: rgb(5, 65, 65);
/* --- Indigo --- */
--fhc-indigo-5: rgb(245, 247, 255);
--fhc-indigo-10: rgb(230, 235, 255);
--fhc-indigo-20: rgb(200, 210, 250);
--fhc-indigo-30: rgb(160, 175, 245);
--fhc-indigo-40: rgb(120, 140, 235);
--fhc-indigo-50: rgb(80, 100, 220);
--fhc-indigo-60: rgb(60, 80, 190);
--fhc-indigo-70: rgb(45, 60, 160);
--fhc-indigo-80: rgb(30, 40, 130);
--fhc-indigo-90: rgb(20, 25, 100);
}
+144
View File
@@ -0,0 +1,144 @@
export default {
getConfig() {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getConfig'
};
},
getConfigStudent() {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getConfigStudent'
};
},
getStudentProjektarbeiten(uid) {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getStudentProjektarbeiten',
params: { uid }
};
},
getStudentProjektabgaben(detail) {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getStudentProjektabgaben',
params: { projektarbeit_id: detail.projektarbeit_id, student_uid: detail.student_uid }
};
},
postStudentProjektarbeitEndupload(formData) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/postStudentProjektarbeitEndupload',
params: formData,
config: {Headers: { "Content-Type": "multipart/form-data" }}
};
},
postStudentProjektarbeitZwischenabgabe(formData) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/postStudentProjektarbeitZwischenabgabe',
params: formData,
config: {Headers: { "Content-Type": "multipart/form-data" }}
};
},
getMitarbeiterProjektarbeiten(all) {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getMitarbeiterProjektarbeiten',
params: { showall: all }
};
},
postProjektarbeitAbgabe(termin) {
let dateString = termin.datum
if(termin.datum instanceof Date) {
const year = termin.datum.getFullYear();
const month = String(termin.datum.getMonth() + 1).padStart(2, '0');
const day = String(termin.datum.getDate()).padStart(2, '0');
dateString = `${year}-${month}-${day}`
}
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/postProjektarbeitAbgabe',
params: {
paabgabe_id: termin.paabgabe_id,
paabgabetyp_kurzbz: termin.bezeichnung.paabgabetyp_kurzbz,
datum: dateString,
note: termin.note_pk,
upload_allowed: !!termin.upload_allowed,
beurteilungsnotiz: termin.beurteilungsnotiz ?? '',
fixtermin: termin.fixtermin,
insertvon: termin.insertvon,
kurzbz: termin.kurzbz,
projektarbeit_id: termin.projektarbeit_id,
betreuer_person_id: termin.betreuer_person_id
}
};
},
deleteProjektarbeitAbgabe(paabgabe_id) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/deleteProjektarbeitAbgabe',
params: { paabgabe_id }
};
},
postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, upload_allowed, projektarbeit_ids, fixtermin) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/postSerientermin',
params: { datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, upload_allowed, projektarbeit_ids, fixtermin }
};
},
fetchDeadlines(person_id) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/fetchDeadlines',
params: { person_id }
};
},
getPaAbgabetypen() {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getPaAbgabetypen'
};
},
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
getNoten(){
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getNoten'
};
},
getProjektarbeitenForStudiengang(studiengang_kz, benotet = 0) {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getProjektarbeitenForStudiengang',
params: { studiengang_kz, benotet }
};
},
// TODO: this could also very well be generic info api
getStudiengaenge() {
return {
method: 'get',
url: '/api/frontend/v1/Abgabe/getStudiengaenge'
};
},
postStudentProjektarbeitZusatzdaten(formData) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/postStudentProjektarbeitZusatzdaten',
params: formData,
config: {Headers: { "Content-Type": "multipart/form-data" }}
};
},
getSignaturStatusForProjektarbeitAbgaben(paabgabe_ids, student_uid) {
return {
method: 'post',
url: '/api/frontend/v1/Abgabe/getSignaturStatusForProjektarbeitAbgaben',
params: {paabgabe_ids, student_uid},
};
}
};
+24 -2
View File
@@ -1,5 +1,27 @@
export default {
/**
* Copyright (C) 2025 fhcomplete.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
getAllStudiensemesterAndAktOrNext() {
return {
method: 'get',
url: '/api/frontend/v1/organisation/Studiensemester/getAllStudiensemesterAndAktOrNext',
};
},
getAll(order = null, start = null)
{
return {
@@ -8,4 +30,4 @@ export default {
params: { order, start }
};
}
}
};
-75
View File
@@ -18,80 +18,5 @@ export default {
`/api/frontend/v1/Lehre/Pruefungen/${lehrveranstaltung_id}`
, {}
);
},
getStudentProjektarbeiten(uid) {
return this.$fhcApi.get(
`/api/frontend/v1/Lehre/getStudentProjektarbeiten/${uid}`
, {}
);
},
getStudentProjektabgaben(detail) {
return this.$fhcApi.get(
`/api/frontend/v1/Lehre/getStudentProjektabgaben`
, {
projektarbeit_id: detail.projektarbeit_id,
student_uid: detail.student_uid
}
);
},
postStudentProjektarbeitEndupload(formData) {
const url = '/api/frontend/v1/Lehre/postStudentProjektarbeitEndupload';
const headers = {Headers: { "Content-Type": "multipart/form-data" }}
return this.$fhcApi.post(url, formData, headers)
},
postStudentProjektarbeitZwischenabgabe(formData) {
const url = '/api/frontend/v1/Lehre/postStudentProjektarbeitZwischenabgabe';
const headers = {Headers: { "Content-Type": "multipart/form-data" }}
return this.$fhcApi.post(url, formData, headers)
},
getStudentProjektarbeitAbgabeFile(paabgabe_id, student_uid) {
const url = `/Cis/Abgabetool/getStudentProjektarbeitAbgabeFile?paabgabe_id=${paabgabe_id}&student_uid=${student_uid}`;
window.location = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url
},
getMitarbeiterProjektarbeiten(uid, all) {
return this.$fhcApi.get(
`/api/frontend/v1/Lehre/getMitarbeiterProjektarbeiten?showall=${all}`
, {}
);
},
postProjektarbeitAbgabe(termin) {
const payload = {
paabgabe_id: termin.paabgabe_id,
paabgabetyp_kurzbz: termin.bezeichnung.paabgabetyp_kurzbz,
datum: termin.datum,
fixtermin: termin.fixtermin,
insertvon: termin.insertvon,
kurzbz: termin.kurzbz,
projektarbeit_id: termin.projektarbeit_id
}
const url = '/api/frontend/v1/Lehre/postProjektarbeitAbgabe';
return this.$fhcApi.post(url, payload, null)
},
deleteProjektarbeitAbgabe(paabgabe_id) {
const payload = {
paabgabe_id
}
const url = '/api/frontend/v1/Lehre/deleteProjektarbeitAbgabe';
return this.$fhcApi.post(url, payload, null)
},
postSerientermin(datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids) {
const payload = {
datum, paabgabetyp_kurzbz, bezeichnung, kurzbz, projektarbeit_ids
}
const url = '/api/frontend/v1/Lehre/postSerientermin';
return this.$fhcApi.post(url, payload, null)
},
fetchDeadlines(person_id) {
const payload = {
person_id
}
const url = '/api/frontend/v1/Lehre/fetchDeadlines';
return this.$fhcApi.post(url, payload, null)
}
}
+74
View File
@@ -0,0 +1,74 @@
import PluginsPhrasen from '../../plugins/Phrasen.js';
import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js";
import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js";
import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js";
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
import {capitalize} from "../../helpers/StringHelpers.js";
const app = Vue.createApp({
name: 'AbgabetoolApp',
components: {
AbgabetoolStudent,
AbgabetoolMitarbeiter,
AbgabetoolAssistenz,
DeadlineOverview
},
data: function() {
return {
comp: null,
uid: null,
student_uid: null,
stg_kz: null
};
},
methods: {
},
computed: {
viewData() {
return { uid: this.uid}
},
student_uid_computed() {
return this.student_uid ?? this.uid
},
stg_kz_computed() {
return this.stg_kz ?? null
}
},
created() {
},
mounted() {
const root = document.getElementById('abgabetoolroot')
const route = root.getAttribute("route");
this.comp = route
const uid = root.getAttribute("uid");
this.uid = uid
const stg_kz = root.getAttribute("stg_kz_prop");
this.stg_kz = stg_kz
const student_uid = root.getAttribute("student_uid_prop");
this.student_uid = student_uid
},
template: `
<template v-if="comp && uid">
<AbgabetoolStudent v-if="comp == 'AbgabetoolStudent'" :viewData="viewData" :student_uid_prop="student_uid_computed"></AbgabetoolStudent>
<AbgabetoolMitarbeiter v-if="comp == 'AbgabetoolMitarbeiter'" :viewData="viewData"></AbgabetoolMitarbeiter>
<AbgabetoolAssistenz v-if="comp == 'AbgabetoolAssistenz'" :viewData="viewData" :stg_kz_prop="stg_kz_computed"></AbgabetoolAssistenz>
<DeadlineOverview v-if="comp == 'DeadlinesOverview'" :viewData="viewData"></DeadlineOverview>
</template>
`
});
app.config.globalProperties.$capitalize = capitalize;
app.use(primevue.config.default, {
zIndex: {
overlay: 9000,
tooltip: 8000
}
})
app.directive('tooltip', primevue.tooltip);
app.use(PluginsPhrasen);
app.mount('#abgabetoolroot');
+13 -1
View File
@@ -14,6 +14,7 @@ import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js";
import RoomInformation, {DEFAULT_MODE_RAUMINFO} from "../../components/Cis/Mylv/RoomInformation.js";
import AbgabetoolStudent from "../../components/Cis/Abgabetool/AbgabetoolStudent.js";
import AbgabetoolMitarbeiter from "../../components/Cis/Abgabetool/AbgabetoolMitarbeiter.js";
import AbgabetoolAssistenz from "../../components/Cis/Abgabetool/AbgabetoolAssistenz.js";
import DeadlineOverview from "../../components/Cis/Abgabetool/DeadlineOverview.js";
import Studium from "../../components/Cis/Studium/Studium.js";
@@ -56,6 +57,12 @@ const router = VueRouter.createRouter({
component: AbgabetoolMitarbeiter,
props: true
},
{
path: `/Cis/Abgabetool/Assistenz/:stg_kz_prop?`,
name: 'AbgabetoolAssistenz',
component: AbgabetoolAssistenz,
props: true
},
{
path: `/Cis/Abgabetool/Deadlines/:person_uid_prop?`,
name: 'DeadlineOverview',
@@ -228,13 +235,16 @@ const app = Vue.createApp({
components: {},
computed: {
isMobile() {
return /Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const smallScreen = window.matchMedia("(max-width: 767px)").matches;
const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
return smallScreen;// && touchCapable;
}
},
provide() {
return { // provide injectable & watchable language property
language: Vue.computed(() => this.$p.user_language),
renderers: Vue.computed(() => this.renderers),
isMobile: this.isMobile
}
},
methods: {
@@ -273,6 +283,7 @@ const app = Vue.createApp({
}
},
async created(){
await this.$api
.call(ApiRenderers.loadRenderers())
.then(res => res.data)
@@ -313,6 +324,7 @@ const app = Vue.createApp({
},
mounted() {
document.addEventListener('click', this.handleClick);
},
beforeUnmount() {
document.removeEventListener('click', this.handleClick);
+13 -5
View File
@@ -46,7 +46,8 @@ export default {
"hiddenBsModal",
"hidePreventedBsModal",
"showBsModal",
"shownBsModal"
"shownBsModal",
"toggleFullscreen"
],
methods: {
dispose() {
@@ -66,6 +67,7 @@ export default {
},
toggleFullscreen() {
this.fullscreen = !this.fullscreen
this.$emit('toggleFullscreen')
}
},
mounted() {
@@ -135,10 +137,16 @@ export default {
<div class="modal-content">
<div v-if="$slots.title" class="modal-header" :class="headerClass">
<h5 class="modal-title"><slot name="title"/></h5>
<div class="d-flex align-items-center ms-auto">
<button type="button" class="btn ms-auto" style="filter: invert(1)" v-if="allowFullscreenExpand" @click="toggleFullscreen">
<i v-if="!fullscreen" class="fa-solid fa-expand"></i>
<i v-else class="fa-solid fa-compress"></i>
<div class="d-flex align-items-center ms-auto gap-2">
<button
type="button"
class="btn mb-1"
v-if="allowFullscreenExpand"
@click="toggleFullscreen"
:aria-label="fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'"
>
<i v-if="!fullscreen" class="fa-solid fa-expand"></i>
<i v-else class="fa-solid fa-compress"></i>
</button>
<button v-if="!noCloseBtn" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
+151
View File
@@ -0,0 +1,151 @@
export default {
name: 'BootstrapOffcanvas',
data: () => ({
offcanvas: null
}),
props: {
backdrop: {
type: [Boolean, String],
default: true,
validator(value) {
return ['static', true, false].includes(value);
}
},
keyboard: {
type: Boolean,
default: true
},
scroll: {
type: Boolean,
default: false
},
placement: {
type: String,
default: 'start', // start | end | top | bottom
validator(value) {
return ['start', 'end', 'top', 'bottom'].includes(value);
}
},
noCloseBtn: Boolean,
headerClass: {
type: [String, Array, Object],
default: ''
},
bodyClass: {
type: [String, Array, Object],
default: 'p-4'
},
footerClass: {
type: [String, Array, Object],
default: ''
},
dialogClass: [String, Array, Object]
},
emits: [
"hideBsOffcanvas",
"hiddenBsOffcanvas",
"hidePreventedBsOffcanvas",
"showBsOffcanvas",
"shownBsOffcanvas"
],
methods: {
dispose() {
return this.offcanvas?.dispose();
},
hide() {
return this.offcanvas?.hide();
},
show(relatedTarget) {
return this.offcanvas?.show(relatedTarget);
},
toggle() {
return this.offcanvas?.toggle();
},
popup(body, options, title, footer) {
const BsOffcanvas = this,
slots = {};
if (body !== undefined)
slots.default = () => body;
if (title !== undefined)
slots.title = () => title;
if (footer !== undefined)
slots.footer = () => footer;
let includedPrimevue = false;
if (typeof primevue !== 'undefined')
includedPrimevue = true;
return new Promise((resolve, reject) => {
const instance = Vue.createApp({
name: 'OffcanvasTmpApp',
setup() {
return () =>
Vue.h(BsOffcanvas, {
class: 'offcanvas-wrapper',
ref: 'offcanvas',
...options
}, slots);
},
mounted() {
this.$refs.offcanvas.show();
},
beforeUnmount() {
if (this.$refs.offcanvas)
this.$refs.offcanvas.result !== false ? resolve(this.$refs.offcanvas.result) : reject();
},
unmounted() {
wrapper.parentElement.removeChild(wrapper);
}
});
const wrapper = document.createElement('div');
if (includedPrimevue) {
instance.use(primevue.config.default, { zIndex: { overlay: 9999 } });
}
import('../../plugins/Phrasen.js').then((Phrasen) => {
instance.use(Phrasen.default);
instance.mount(wrapper);
document.body.appendChild(wrapper);
});
});
}
},
mounted() {
if (this.$refs.offcanvas) {
this.offcanvas = new bootstrap.Offcanvas(this.$refs.offcanvas, {
backdrop: this.backdrop,
keyboard: this.keyboard,
scroll: this.scroll
});
}
},
template: `
<div ref="offcanvas"
class="bootstrap-offcanvas offcanvas"
:class="['offcanvas-' + placement, dialogClass]"
tabindex="-1"
@[\`hide.bs.offcanvas\`]="$emit('hideBsOffcanvas')"
@[\`hidden.bs.offcanvas\`]="$emit('hiddenBsOffcanvas')"
@[\`hidePrevented.bs.offcanvas\`]="$emit('hidePreventedBsOffcanvas')"
@[\`show.bs.offcanvas\`]="$emit('showBsOffcanvas')"
@[\`shown.bs.offcanvas\`]="$emit('shownBsOffcanvas')"
>
<div class="offcanvas-header" :class="headerClass" v-if="$slots.title">
<h5 class="offcanvas-title">
<slot name="title"></slot>
</h5>
<button v-if="!noCloseBtn" type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" :class="bodyClass">
<slot></slot>
</div>
<div v-if="$slots.footer" class="offcanvas-footer" :class="footerClass">
<slot name="footer"></slot>
</div>
</div>
`
}
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,9 @@
import Upload from '../../../components/Form/Upload/Dms.js';
import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
import FhcOverlay from "../../Overlay/FhcOverlay.js";
const today = new Date()
export const AbgabeStudentDetail = {
name: "AbgabeStudentDetail",
components: {
@@ -12,8 +13,14 @@ export const AbgabeStudentDetail = {
Checkbox: primevue.checkbox,
Dropdown: primevue.dropdown,
Textarea: primevue.textarea,
VueDatePicker
Accordion: primevue.accordion,
AccordionTab: primevue.accordiontab,
Message: primevue.message,
Inplace: primevue.inplace,
VueDatePicker,
FhcOverlay
},
inject: ['notenOptions', 'isMobile', 'isViewMode', 'moodle_link'],
props: {
projektarbeit: {
type: Object,
@@ -26,6 +33,7 @@ export const AbgabeStudentDetail = {
},
data() {
return {
loading: false,
eidAkzeptiert: false,
enduploadTermin: null,
allActiveLanguages: FHC_JS_DATA_STORAGE_OBJECT.server_languages,
@@ -41,16 +49,40 @@ export const AbgabeStudentDetail = {
}
},
methods: {
validate: function(termin) {
getNoteBezeichnung(termin){
const noteOpt = this.notenOptions.find(opt => opt.note == termin.note)
if(noteOpt?.bezeichnung) {
return noteOpt?.positiv ? this.$capitalize(this.$p.t('abgabetool/c4positivBenotet')) + ' ✅' : this.$capitalize(this.$p.t('abgabetool/c4negativBenotet')) + ' ❌'
} else if(noteOpt?.benotbar === true && !termin.note) {
return this.$capitalize(this.$p.t('abgabetool/c4notYetGraded'));
} else {
return ''
}
},
async validate(termin, endupload = false) {
if(!termin.file.length) {
this.$fhcAlert.alertWarning(this.$p.t('global/warningChooseFile'));
this.$fhcAlert.alertWarning(this.$capitalize(this.$p.t('global/warningChooseFile')));
return false
}
if(endupload) {
if(await this.$fhcAlert.confirm({
message: this.$p.t('abgabetool/confirmEnduploadSpeichern'),
acceptLabel: this.$capitalize(this.$p.t('abgabetool/c4AcceptAndProceed')),
acceptClass: 'p-button-primary',
rejectLabel: this.$capitalize(this.$p.t('abgabetool/c4Cancel')),
rejectClass: 'p-button-secondary'
}) === false) {
return false
}
}
return true;
},
triggerEndupload() {
if (!this.validate(this.enduploadTermin))
async triggerEndupload() {
if (!await this.validate(this.enduploadTermin, true))
{
return false;
}
@@ -63,7 +95,6 @@ export const AbgabeStudentDetail = {
formData.append('student_uid', this.projektarbeit.student_uid)
formData.append('bperson_id', this.projektarbeit.bperson_id)
// TODO: validate/check for null etc.
formData.append('sprache', this.form['sprache'].sprache)
formData.append('abstract', this.form['abstract'])
formData.append('abstract_en', this.form['abstract_en'])
@@ -74,15 +105,21 @@ export const AbgabeStudentDetail = {
for (let i = 0; i < this.enduploadTermin.file.length; i++) {
formData.append('file', this.enduploadTermin.file[i]);
}
this.$fhcApi.factory.lehre.postStudentProjektarbeitEndupload(formData)
this.loading = true
this.$api.call(ApiAbgabe.postStudentProjektarbeitEndupload(formData))
.then(res => {
this.handleUploadRes(res)
})
this.handleUploadRes(res, this.enduploadTermin)
}).finally(()=> {
this.loading = false
})
this.$refs.modalContainerEnduploadZusatzdaten.hide()
},
downloadAbgabe(termin) {
this.$fhcApi.factory.lehre.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid)
const url = `/api/frontend/v1/Abgabe/getStudentProjektarbeitAbgabeFile?paabgabe_id=${termin.paabgabe_id}&student_uid=${this.projektarbeit.student_uid}&projektarbeit_id=${this.projektarbeit.projektarbeit_id}`;
window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url)
// this.$api.call(ApiAbgabe.getStudentProjektarbeitAbgabeFile(termin.paabgabe_id, this.projektarbeit.student_uid))
},
formatDate(dateParam) {
const date = new Date(dateParam)
@@ -93,16 +130,17 @@ export const AbgabeStudentDetail = {
const day = padZero(date.getDate());
const year = date.getFullYear();
return `${day}.${month}.${year}`;
return `${day}.${month}.${year}`
},
upload(termin) {
async upload(termin) {
if (!this.validate(termin))
// only do this on endupload
if (! await this.validate(termin))
{
return false;
}
if(termin.bezeichnung === 'Endupload') {
if(termin.bezeichnung?.paabgabetyp_kurzbz === 'end') {
// open endupload form modal for further inputs
this.enduploadTermin = termin
this.$refs.modalContainerEnduploadZusatzdaten.show()
@@ -117,166 +155,401 @@ export const AbgabeStudentDetail = {
for (let i = 0; i < termin.file.length; i++) {
formData.append('file', termin.file[i]);
}
this.$fhcApi.factory.lehre.postStudentProjektarbeitZwischenabgabe(formData)
this.loading = true
this.$api.call(ApiAbgabe.postStudentProjektarbeitZwischenabgabe(formData))
.then(res => {
this.handleUploadRes(res)
})
this.handleUploadRes(res, termin)
}).finally(()=> {
this.loading = false
})
}
},
handleUploadRes(res) {
handleUploadRes(res, termin) {
if(res.meta.status == "success") {
this.$fhcAlert.alertSuccess('File erfolgreich hochgeladen')
this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4fileUploadSuccessv3')))
// update 'abgabedatum' for successful upload -> shows the pdf icon and date once set
termin.abgabedatum = new Date().toISOString().split('T')[0];
if(res?.data?.signatur !== undefined) {
termin.signatur = res.data.signatur
}
} else {
this.$fhcAlert.alertError('File upload error')
this.$fhcAlert.alertError(this.$capitalize(this.$p.t('abgabetool/c4fileUploadErrorv3')))
}
if(res.meta.signaturInfo) {
this.$fhcAlert.alertInfo(res.meta.signaturInfo)
}
},
dateDiffInDays(datum, today){
const oneDayMs = 1000 * 60 * 60 * 24
return Math.round((new Date(datum) - new Date(today)) / oneDayMs)
},
getDateStyle(termin, mode) {
const datum = new Date(termin.datum)
const abgabedatum = new Date(termin.abgabedatum)
// todo: rework styling but keep the color pattern logic
// https://wiki.fhcomplete.info/doku.php?id=cis:abgabetool_fuer_studierende
let color = 'white'
let fontColor = 'black'
let icon = '';
if (termin.abgabedatum === null) {
if(datum < today) {
color = 'red'
fontColor = 'white'
icon = 'fa-triangle-exclamation'
} else if (datum > today && this.dateDiffInDays(datum, today) <= 12) {
color = 'yellow'
icon = 'fa-circle-exclamation'
}
} else if(abgabedatum > datum) {
color = 'pink' // aka "hellrot"
fontColor = 'white'
icon = 'fa-circle-question'
} else {
color = 'green'
icon = 'fa-square-check'
}
//return `font-color: ${fontColor} ; background-color: ${color}; border-radius: 50%;`
if( typeof mode !== 'undefined' || mode === 'icon') {
return icon;
} else {
return 'abgabe-zieldatum-border-' + color;
}
},
openBeurteilungLink(link) {
window.open(link, '_blank')
},
getOptionLabel(option) {
return option.sprache
}
},
getTerminNoteBezeichnung(termin) {
const noteOpt = this.notenOptions.find(opt => opt.note == termin.note)
return noteOpt ? noteOpt.bezeichnung : ''
},
},
watch: {
projektarbeit(newVal) {
// default select german if projektarbeit sprache was null
this.form.sprache = newVal.sprache ? this.allActiveLanguages.find(lang => lang.sprache == newVal.sprache) : this.allActiveLanguages.find(lang => lang.sprache == 'German')
this.form.abstract = newVal.abstract
this.form.abstract_en = newVal.abstract_en
this.form.schlagwoerter = newVal.schlagwoerter
this.form.schlagwoerter_en = newVal.schlagwoerter_en
this.form.kontrollschlagwoerter = newVal.kontrollschlagwoerter
this.form.seitenanzahl = newVal.seitenanzahl
this.form.abstract = newVal.abstract ?? ''
this.form.abstract_en = newVal.abstract_en ?? ''
this.form.schlagwoerter = newVal.schlagwoerter ?? ''
this.form.schlagwoerter_en = newVal.schlagwoerter_en ?? ''
this.form.kontrollschlagwoerter = newVal.kontrollschlagwoerter ?? ''
this.form.seitenanzahl = newVal.seitenanzahl ?? 1
}
},
computed: {
getEid() {
return this.$p.t('abgabetool/c4eidesstattlicheErklaerung')
getMoodleLink() {
return this.moodle_link + this.projektarbeit.studiengang_kz
},
getMessagePtStyle() {
// adjust outer spacing and internal padding to appear similar to doenload button in size
return {
root: {
style: {
margin: '0px'
}
},
wrapper: {
style: {
padding: '6px'
}
}
}
},
getEid() {
return this.$capitalize(this.$p.t('abgabetool/c4eidesstattlicheErklaerung'))
},
allowedToSaveZusatzdaten() {
return this.form.schlagwoerter.length > 0 && this.form.schlagwoerter_en.length > 0 && this.form.abstract.length > 0 && this.form.abstract_en.length > 0 && this.form.seitenanzahl > 0
},
getAllowedToSendEndupload() {
return this.eidAkzeptiert && this.allowedToSaveZusatzdaten
},
qualityGateTerminAvailable() {
let qgatefound = false
this.projektarbeit?.abgabetermine.forEach(abgabe => {
if(abgabe.paabgabetyp_kurzbz == 'qualgate1'
|| abgabe.paabgabetyp_kurzbz == 'qualgate2') {
qgatefound = true
}
})
return qgatefound
},
getTooltipVerspaetet() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerspaetet')),
class: "custom-tooltip"
}
},
getTooltipVerpasst() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerpasst')),
class: "custom-tooltip"
}
},
getTooltipAbzugeben() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbzugeben')),
class: "custom-tooltip"
}
},
getTooltipStandard() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipStandardv2')),
class: "custom-tooltip"
}
},
getTooltipAbgegeben() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgegeben')),
class: "custom-tooltip"
}
},
getTooltipFixtermin() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipFixtermin')),
class: "custom-tooltip"
}
},
getTooltipAbgabeDetected() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipAbgabeDetected')),
class: "custom-tooltip"
}
},
getTooltipNotAllowedToUpload() {
if(this.isViewMode) {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedInViewMode')),
class: "custom-tooltip"
}
} else {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4studentAbgabeNotAllowedRegular')),
class: "custom-tooltip"
}
}
},
getTooltipBeurteilungerforderlich() {
return {
value: this.$capitalize(this.$p.t('abgabetool/c4tooltipBeurteilungerforderlich')),
class: "custom-tooltip"
}
},
getTooltipBestanden() {
return {
value: this.$p.t('abgabetool/c4tooltipBestanden'),
class: "custom-tooltip"
}
},
getTooltipNichtBestanden() {
return {
value: this.$p.t('abgabetool/c4tooltipNichtBestanden'),
class: "custom-tooltip"
}
},
getEnduploadErlaubt() {
return !this.eidAkzeptiert
}
},
created() {
},
mounted() {
},
template: `
<FhcOverlay :active="loading"></FhcOverlay>
<div v-if="projektarbeit">
<h5>{{$p.t('abgabetool/c4abgabeStudentenbereich')}}</h5>
<h5>{{$capitalize( $p.t('abgabetool/c4abgabeStudentenbereich') )}}</h5>
<div class="row">
<p> {{projektarbeit?.betreuer}}</p>
<p> {{projektarbeit?.titel}}</p>
</div>
<div id="uploadWrapper">
<div class="row" style="margin-bottom: 12px;">
<div class="col-1 fw-bold text-center">{{$p.t('abgabetool/c4fixtermin')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4zieldatum')}}</div>
<div class="col-2 fw-bold">{{$p.t('abgabetool/c4abgabetyp')}}</div>
<div class="col-3 fw-bold">{{$p.t('abgabetool/c4abgabekurzbz')}}</div>
<div class="col-1 fw-bold text-center">{{$p.t('abgabetool/c4abgabedatum')}}</div>
<div class="col-3 fw-bold">
{{$p.t('abgabetool/c4fileupload')}}
</div>
<div class="col-8">
<p> {{$capitalize( $p.t('person/student') ) }}: {{projektarbeit?.student}}</p>
<p> {{$capitalize( $p.t('abgabetool/c4titel') ) }}: {{projektarbeit?.titel}}</p>
<p> {{$capitalize( $p.t('abgabetool/c4betreuer') ) }}: {{projektarbeit ? $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) + ' ' + projektarbeit.betreuer : ''}}</p>
</div>
<div class="row" v-for="termin in projektarbeit.abgabetermine">
<div class="col-1 d-flex justify-content-center align-items-start">
<i v-if="termin.fixtermin" class="fa-solid fa-2x fa-circle-check fhc-bullet-red"></i>
<i v-else="" class="fa-solid fa-2x fa-circle-xmark fhc-bullet-green"></i>
<!--
<p class="fhc-bullet" :class="{ 'fhc-bullet-red': termin.fixtermin, 'fhc-bullet-green': !termin.fixtermin }"></p>
-->
</div>
<div class="col-2 d-flex justify-content-start align-items-start">
<div class="position-relative" :class="getDateStyle(termin)">
<VueDatePicker
v-model="termin.datum"
:clearable="false"
:disabled="true"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
<i class="position-absolute abgabe-zieldatum-overlay fa-solid fa-2x" :class="getDateStyle(termin, 'icon')"></i>
</div>
</div>
<div class="col-2 d-flex justify-content-start align-items-start">{{ termin.bezeichnung }}</div>
<div class="col-3 d-flex justify-content-start align-items-start">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="3" cols="45" :disabled="true"></Textarea>
</div>
<div class="col-1 d-flex flex-column justify-content-start align-items-center">
{{ termin.abgabedatum?.split("-").reverse().join(".") }}
<a v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" style="margin-left:4px; cursor: pointer;">
<i class="fa-solid fa-3x fa-file-pdf"></i>
</a>
</div>
<div class="col-3" v-if="!viewMode">
<div class="row">
<div class="col-8">
<Upload v-if="termin && termin.allowedToUpload" accept=".pdf" v-model="termin.file"></Upload>
</div>
<div class="col-4">
<button class="btn btn-primary border-0" @click="upload(termin)" :disabled="!termin.allowedToUpload">
Upload
<i style="margin-left: 8px" class="fa-solid fa-upload"></i>
</button>
</div>
</div>
</div>
<div class="col-4">
<p>{{ $p.t('abgabetool/c4checkoutStgMoodleInfos') }}
<a :href="getMoodleLink" target="_blank">Moodle</a>
</p>
</div>
</div>
<Accordion :multiple="true">
<template v-for="termin in this.projektarbeit?.abgabetermine">
<AccordionTab :headerClass="termin.dateStyle + '-header'">
<template #header>
<div class="d-flex flex-nowrap align-items-center w-100">
<div class="flex-shrink-0 d-flex align-items-center justify-content-center" style="width: 36px; height: 36px; margin-left: -66px;">
<i v-if="termin.dateStyle == 'verspaetet'" v-tooltip.right="getTooltipVerspaetet" class="fa-solid fa-triangle-exclamation"></i>
<i v-else-if="termin.dateStyle == 'verpasst'" v-tooltip.right="getTooltipVerpasst" class="fa-solid fa-calendar-xmark"></i>
<i v-else-if="termin.dateStyle == 'abzugeben'" v-tooltip.right="getTooltipAbzugeben" class="fa-solid fa-hourglass-half"></i>
<i v-else-if="termin.dateStyle == 'standard'" v-tooltip.right="getTooltipStandard" class="fa-solid fa-clock"></i>
<i v-else-if="termin.dateStyle == 'abgegeben'" v-tooltip.right="getTooltipAbgegeben" class="fa-solid fa-paperclip"></i>
<i v-else-if="termin.dateStyle == 'beurteilungerforderlich'" v-tooltip.right="getTooltipBeurteilungerforderlich" class="fa-solid fa-list-check"></i>
<i v-else-if="termin.dateStyle == 'bestanden'" v-tooltip.right="getTooltipBestanden" class="fa-solid fa-check"></i>
<i v-else-if="termin.dateStyle == 'nichtbestanden'" v-tooltip.right="getTooltipNichtBestanden" class="fa-solid fa-circle-exclamation"></i>
</div>
<div class="text-start px-2" style="min-width: 150px; max-width: 300px; margin-left: 40px">
<span>{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}</span>
</div>
<div class="text-start px-2" style="min-width: 100px;">
<span>{{ formatDate(termin.datum) }}</span>
</div>
<div class="px-1">
<i v-if="termin?.fixtermin" v-tooltip.right="getTooltipFixtermin" class="fa-solid fa-lock"></i>
<i v-if="termin?.abgabedatum && isMobile" v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
</div>
<div v-if="termin?.abgabedatum && !isMobile" class="px-1">
<i v-tooltip.right="getTooltipAbgabeDetected" class="fa-solid fa-file"></i>
</div>
<div class="flex-grow-1 text-end pe-2">
<span class="fw-bold">{{getNoteBezeichnung(termin)}}</span>
</div>
</div>
</template>
<div v-if="isMobile" class="row mt-2 align-items-center">
<Inplace
closable
:closeButtonProps="{
style: {
position: 'relative',
bottom: '100px',
left: '80%',
zIndex: 1
}
}"
>
<template #display>{{ $capitalize($p.t('abgabetool/c4tapForTooltipInfo'))}}</template>
<template #content>
<div class="col-auto">
<div class="row">
<div class="col-12 col-md-3 fw-bold align-content-center">{{ $capitalize($p.t('abgabetool/c4abgabeZeitstatus')) }}</div>
<div class="col-12 col-md-9">{{$p.t('abgabetool/c4tooltip' + $capitalize(termin?.dateStyle) )}}</div>
</div>
<div class="row">
<div class="col-12 col-md-3 fw-bold align-content-center">{{ $capitalize($p.t('abgabetool/c4fixterminv4')) }}</div>
<div class="col-12 col-md-9">{{!termin?.fixtermin}}</div>
</div>
<div class="row">
<div class="col-12 col-md-3 fw-bold align-content-center">{{ $capitalize($p.t('abgabetool/c4fileUploaded')) }}</div>
<div class="col-12 col-md-9">{{termin?.abgabedatum !== null}}</div>
</div>
</div>
</template>
</Inplace>
</div>
<div class="row mt-2">
<div class="col-12 col-md-3 align-content-center">
<div class="row fw-bold" style="margin-left: 2px">{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
<div class="row fw-light" style="margin-left: 2px">{{$capitalize( $p.t('abgabetool/c4abgabeuntil2359') )}}</div>
</div>
<div class="col-12 col-md-9">
<VueDatePicker
v-model="termin.datum"
:clearable="false"
:disabled="true"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
</div>
</div>
<div class="row mt-2">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4abgabetyp') )}}</div>
<div class="col-12 col-md-9">
{{ termin ? $p.t('abgabetool/c4paatyp' + termin.paabgabetyp_kurzbz) : '' }}
</div>
</div>
<div class="row mt-2" v-if="termin.note">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4note') )}}</div>
<div class="col-12 col-md-9">
<div class="col-auto d-flex justify-content-start align-items-start">
{{ getTerminNoteBezeichnung(termin) }}
</div>
</div>
</div>
<div class="row mt-2" v-if="termin.paabgabetyp_kurzbz === 'qualgate1' || termin.paabgabetyp_kurzbz === 'qualgate2'">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4notizQualGatev2') )}}</div>
<div class="col-12 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.beurteilungsnotiz" rows="1" class="w-100" disabled></Textarea>
</div>
</div>
<div v-if="termin.kurzbz && termin.kurzbz.length > 0" class="row mt-2">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}</div>
<div class="col-12 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="termin.kurzbz" rows="1" class="w-100" :disabled="true"></Textarea>
</div>
</div>
<div class="row mt-2" v-if="termin.upload_allowed">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4abgabedatum') )}}</div>
<div class="col-12 col-md-9">
<template v-if="termin?.abgabedatum">
<div class="row">
<div style="width:100px; align-content: center;">
<h6>{{ termin.abgabedatum?.split("-").reverse().join(".") }}</h6>
</div>
<div class="col-auto">
<button v-if="termin?.abgabedatum" @click="downloadAbgabe(termin)" class="btn btn-primary">
<a> {{$capitalize($p.t('abgabetool/c4downloadAbgabe') )}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
</button>
</div>
<template v-if="termin.paabgabetyp_kurzbz == 'end'">
<div v-if="termin?.signatur !== undefined && termin?.signatur !== null" class="col-auto">
<Message v-if="termin?.signatur == true" severity="success" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4signaturGefunden') }} </Message>
<Message v-else-if="termin?.signatur == false" severity="error" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4keineSignatur') }} </Message>
<Message v-else-if="termin?.signatur == 'error'" severity="warn" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4signaturServerError') }} </Message>
</div>
<!-- <div v-else class="col-auto">-->
<!-- <Message severity="info" :closable="false" :pt="getMessagePtStyle"> {{ $p.t('abgabetool/c4noFileFound') }} </Message>-->
<!-- </div>-->
</template>
</div>
</template>
<template v-else>
{{ $capitalize( $p.t('abgabetool/c4nochNichtsAbgegeben') )}}
</template>
</div>
</div>
<div class="row mt-2" v-if="termin.upload_allowed">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4fileupload') )}}</div>
<div class="col-12 col-md-9">
<div class="row" v-if="termin?.allowedToUpload">
<div class="col-12 col-sm-6 mb-2">
<Upload
accept=".pdf"
v-model="termin.file"
></Upload>
</div>
<div class="col-12 col-sm-6">
<button
class="btn btn-primary border-0 w-100"
@click="upload(termin)"
>
{{$capitalize( $p.t('abgabetool/c4upload') )}}
<i class="fa-solid fa-upload"></i>
</button>
</div>
</div>
<div class="row" v-else-if="!termin?.allowedToUpload || isViewMode" v-tooltip.right="getTooltipNotAllowedToUpload">
<div class="col-12 col-sm-6 mb-2">
<Upload
disabled
accept=".pdf"
v-model="termin.file"
></Upload>
</div>
<div class="col-12 col-sm-6">
<button
class="btn btn-primary border-0 w-100"
@click="upload(termin)"
disabled
>
{{$capitalize( $p.t('abgabetool/c4upload') )}}
<i class="fa-solid fa-upload"></i>
</button>
</div>
</div>
</div>
</div>
</AccordionTab>
</template>
</Accordion>
<div v-if="projektarbeit?.abgabetermine.length == 0" style="display:flex; justify-content: center; align-content: center;">
<h5>{{ $capitalize( $p.t('abgabetool/c4keineAbgabetermineGefunden') )}}</h5>
</div>
</div>
<bs-modal ref="modalContainerEnduploadZusatzdaten" class="bootstrap-prompt" dialogClass="modal-lg">
<bs-modal
ref="modalContainerEnduploadZusatzdaten"
class="bootstrap-prompt"
dialogClass="bordered-modal modal-lg">
<template v-slot:title>
<div>
{{$p.t('abgabetool/c4enduploadZusatzdaten')}}
{{$capitalize( $p.t('abgabetool/c4enduploadZusatzdaten') )}}
</div>
<div class="row mb-3 align-items-start">
@@ -285,13 +558,13 @@ export const AbgabeStudentDetail = {
</div>
<div class="row mb-3 align-items-start">
<p class="ml-4 mr-4">Titel: {{ projektarbeit?.titel }}</p>
<p class="ml-4 mr-4">{{$capitalize( $p.t('abgabetool/c4titel') )}}: {{ projektarbeit?.titel }}</p>
</div>
</template>
<template v-slot:default>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4Sprache')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4Sprache') )}}</div>
<div class="row">
<Dropdown
:style="{'width': '100%'}"
@@ -312,35 +585,37 @@ export const AbgabeStudentDetail = {
<!-- -->
<!-- </div>-->
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4schlagwoerterGer')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4schlagwoerterGer') )}}</div>
<div class="row">
<Textarea v-model="form.schlagwoerter"></Textarea>
<Textarea v-model="form.schlagwoerter" class="w-100"></Textarea>
</div>
</div>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4schlagwoerterEng')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4schlagwoerterEng') )}}</div>
<div class="row">
<Textarea v-model="form.schlagwoerter_en"></Textarea>
<Textarea v-model="form.schlagwoerter_en" class="w-100"></Textarea>
</div>
</div>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4abstractGer')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4abstractGer') )}}</div>
<div class="row">
<Textarea v-model="form.abstract" rows="10"></Textarea>
<Textarea v-model="form.abstract" rows="10" maxlength="5000" class="w-100"></Textarea>
<p>{{ form.abstract?.length ? form.abstract.length : 0 }} / 5000 characters</p>
</div>
</div>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4abstractEng')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4abstractEng') )}}</div>
<div class="row">
<Textarea v-model="form.abstract_en" rows="10"></Textarea>
<Textarea v-model="form.abstract_en" rows="10" maxlength="5000" class="w-100"></Textarea>
<p>{{ form.abstract_en?.length ? form.abstract_en.length : 0 }} / 5000 characters</p>
</div>
</div>
<div class="row mb-3 align-items-start">
<div class="row">{{$p.t('abgabetool/c4seitenanzahl')}}</div>
<div class="row">{{$capitalize( $p.t('abgabetool/c4seitenanzahl') )}}</div>
<div class="row">
<InputNumber
v-model="form.seitenanzahl"
@@ -353,7 +628,7 @@ export const AbgabeStudentDetail = {
<div v-html="getEid"></div>
<div class="row">
<div class="col-9"></div>
<div class="col-2"><p>{{ $p.t('abgabetool/c4gelesenUndAkzeptiert') }}</p></div>
<div class="col-2"><p>{{$capitalize( $p.t('abgabetool/c4gelesenUndAkzeptiert') )}}</p></div>
<div class="col-1">
<Checkbox
@@ -368,7 +643,8 @@ export const AbgabeStudentDetail = {
</template>
<template v-slot:footer>
<button class="btn btn-primary" :disabled="getEnduploadErlaubt" @click="triggerEndupload">{{$p.t('ui/hochladen')}}</button>
<div v-show="!allowedToSaveZusatzdaten">{{ $p.t('abgabetool/c4zusatzdatenausfuellen') }}</div>
<button class="btn btn-primary" :disabled="!getAllowedToSendEndupload" @click="triggerEndupload">{{$capitalize( $p.t('ui/hochladen') )}}</button>
</template>
</bs-modal>
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,9 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import AbgabeDetail from "./AbgabeMitarbeiterDetail.js";
import VerticalSplit from "../../verticalsplit/verticalsplit.js"
import BsModal from '../../Bootstrap/Modal.js';
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiAbgabe from '../../../api/factory/abgabe.js'
import FhcOverlay from "../../Overlay/FhcOverlay.js";
export const AbgabetoolMitarbeiter = {
name: "AbgabetoolMitarbeiter",
@@ -10,10 +11,21 @@ export const AbgabetoolMitarbeiter = {
BsModal,
CoreFilterCmpt,
AbgabeDetail,
VerticalSplit,
Checkbox: primevue.checkbox,
Dropdown: primevue.dropdown,
Textarea: primevue.textarea,
VueDatePicker
VueDatePicker,
FhcOverlay
},
provide() {
return {
abgabeTypeOptions: Vue.computed(() => this.abgabeTypeOptions),
abgabetypenBetreuer: Vue.computed(() => this.abgabetypenBetreuer),
allowedNotenOptions: Vue.computed(() => this.allowedNotenOptions),
notenOptionsNonFinal: Vue.computed(() => this.notenOptionsNonFinal),
turnitin_link: Vue.computed(() => this.turnitin_link),
old_abgabe_beurteilung_link: Vue.computed(() => this.old_abgabe_beurteilung_link)
}
},
props: {
viewData: {
@@ -21,44 +33,33 @@ export const AbgabetoolMitarbeiter = {
required: true,
default: () => ({name: '', uid: ''}),
validator(value) {
return value && value.name && value.uid
return value && value.uid // && value.name -> extensive viewData use only for cis4 onwards
}
}
},
data() {
return {
tableData: null,
abgabetypenBetreuer: null,
detailIsFullscreen: false,
phrasenPromise: null,
phrasenResolved: false,
turnitin_link: null,
old_abgabe_beurteilung_link: null,
saving: false,
loading: false,
// TODO: fetch types
allAbgabeTypes: [
{
paabgabetyp_kurzbz: 'abstract',
bezeichnung: 'Entwurf'
},
{
paabgabetyp_kurzbz: 'zwischen',
bezeichnung: 'Zwischenabgabe'
},
{
paabgabetyp_kurzbz: 'note',
bezeichnung: 'Benotung'
},
{
paabgabetyp_kurzbz: 'end',
bezeichnung: 'Endupload'
},
{
paabgabetyp_kurzbz: 'enda',
bezeichnung: 'Endabgabe im Sekretariat'
}
],
abgabeTypeOptions: null,
notenOptions: null,
allowedNotenOptions: null,
notenOptionsNonFinal: null,
serienTermin: Vue.reactive({
datum: new Date(),
bezeichnung: {
paabgabetyp_kurzbz: 'zwischen',
bezeichnung: 'Zwischenabgabe'
},
kurzbz: ''
kurzbz: '',
upload_allowed: false
}),
showAll: false,
tabulatorUuid: Vue.ref(0),
@@ -72,34 +73,78 @@ export const AbgabetoolMitarbeiter = {
tableBuiltResolve: null,
tableBuiltPromise: null,
abgabeTableOptions: {
height: 700,
minHeight: 250,
index: 'projektarbeit_id',
layout: 'fitDataStretch',
placeholder: this.$p.t('global/noDataAvailable'),
placeholder: Vue.computed(() => this.$p.t('global/noDataAvailable')),
selectable: true,
selectableCheck: this.selectionCheck,
rowHeight: 80,
columns: [
{
formatter: 'rowSelection',
titleFormatter: 'rowSelection',
titleFormatterParams: {
rowRange: "active" // Only toggle the values of the active filtered rows
formatter: function (cell, formatterParams, onRendered) {
// create the built-in checkbox
if(!cell.getRow().getData().selectable) return
let checkbox = document.createElement("input");
checkbox.type = "checkbox";
// Handle select manually
checkbox.addEventListener("click", (e) => {
e.stopPropagation();
// call our function
if (formatterParams && formatterParams.handleClick) {
formatterParams.handleClick(e, cell);
}
});
cell.getRow().getData().checkbox = checkbox
let wrapper = document.createElement("div");
wrapper.style.cssText = "display: flex; justify-content: center; align-items: center; height: 100%; width: 100%;";
wrapper.appendChild(checkbox);
return wrapper;
},
hozAlign:"center",
titleFormatter: function (cell, formatterParams, onRendered) {
// create the built-in checkbox
let checkbox = document.createElement("input");
checkbox.type = "checkbox";
// Handle "select all" manually
checkbox.addEventListener("click", (e) => {
e.stopPropagation();
// call our function
if (formatterParams && formatterParams.handleClick) {
formatterParams.handleClick(e, cell);
}
});
return checkbox;
},
hozAlign: "center",
headerSort: false,
frozen: true,
width: 70
formatterParams: {
handleClick: this.selectHandler
},
titleFormatterParams: {
handleClick: this.selectAllHandler
},
width: 50,
cssClass: 'sticky-col'
},
{title: Vue.computed(() => this.$p.t('abgabetool/c4details')), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4personenkennzeichen')), field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4kontakt')), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4vorname')), field: 'vorname', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4nachname')), field: 'nachname', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4projekttyp')), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4stg')), field: 'stg', formatter: this.centeredTextFormatter, widthGrow: 2},
{title: Vue.computed(() => this.$p.t('abgabetool/c4sem')), field: 'studiensemester_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4titel')), field: 'titel', formatter: this.centeredTextFormatter, maxWidth: 500, widthGrow: 8},
{title: Vue.computed(() => this.$p.t('abgabetool/c4betreuerart')), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 8}
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4details'))), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false, cssClass: 'sticky-col'},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4personenkennzeichen'))), headerFilter: true, field: 'pkz', formatter: this.pkzTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4kontakt'))), field: 'mail', formatter: this.mailFormatter, widthGrow: 1, tooltip: false, visible: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4vorname'))), field: 'vorname', headerFilter: true, formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4nachname'))), field: 'nachname', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4projekttyp'))), field: 'projekttyp_kurzbz', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'studiensemester_kurzbz', headerFilter: true, formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4titel'))), field: 'titel', headerFilter: true, formatter: this.centeredTextFormatter, maxWidth: 500, widthGrow: 8},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4betreuerart'))), field: 'betreuerart_beschreibung',formatter: this.centeredTextFormatter, widthGrow: 1}
],
persistence: false,
},
@@ -123,12 +168,54 @@ export const AbgabetoolMitarbeiter = {
{
event: "rowSelectionChanged",
handler: async(data) => {
this.selectedData.filter(sd => !data.includes(sd)).forEach(fsd => {
if(fsd.checkbox) fsd.checkbox.checked = false
})
data.forEach(d => {
if(d.checkbox) d.checkbox.checked = true
})
this.selectedData = data
}
}
]};
},
methods: {
selectHandler(e, cell) {
const row = cell.getRow();
if(row.isSelected()) {
row.deselect();
} else {
row.select();
}
// stop built-in handler
e.stopPropagation();
return false;
},
selectAllHandler(e, cell) {
const table = cell.getTable();
const rows = table.getRows();
// custom select all logic
const allowed = rows.filter(r => r.getData().selectable);
const selected = allowed.every(r => r.isSelected());
if(selected) {
allowed.forEach(r => r.deselect());
} else {
allowed.forEach(r => r.select());
}
// stop built-in handler
e.stopPropagation();
return false;
},
handleToggleFullscreenDetail() {
this.detailIsFullscreen = !this.detailIsFullscreen
},
getOptionLabelAbgabetyp(option){
return option.bezeichnung
},
@@ -176,17 +263,17 @@ export const AbgabetoolMitarbeiter = {
},
addSeries() {
this.saving = true
this.$fhcApi.factory.lehre.postSerientermin(
this.$api.call(ApiAbgabe.postSerientermin(
this.serienTermin.datum.toISOString(),
this.serienTermin.bezeichnung.paabgabetyp_kurzbz,
this.serienTermin.bezeichnung.bezeichnung,
this.serienTermin.kurzbz,
this.selectedData?.map(projekt => projekt.projektarbeit_id)
).then(res => {
this.serienTermin.upload_allowed,
this.selectedData?.map(projekt => projekt.projektarbeit_id),
false
)).then(res => {
if (res.meta.status === "success" && res.data) {
this.$fhcAlert.alertSuccess(this.$p.t('abgabetool/serienTerminGespeichert'))
// TODO: sticky lifetime erhöhen um sinnvoll lesen zu können?
this.$fhcAlert.alertInfo(this.$p.t('abgabetool/serienTerminEmailSentInfo', [this.createInfoString(res.data)]));
} else {
this.$fhcAlert.alertError(this.$p.t('abgabetool/errorSerienterminSpeichern'))
}
@@ -210,44 +297,45 @@ export const AbgabetoolMitarbeiter = {
return new Date(date) < new Date(Date.now())
},
setDetailComponent(details){
this.loading=true
this.loadAbgaben(details).then((res)=> {
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data[0].retval
pa.isCurrent = res.data[1]
pa.abgabetermine.push({ // new abgatermin row
'paabgabe_id': -1,
'projektarbeit_id': pa.projektarbeit_id,
'fixtermin': false,
'kurzbz': '',
'datum': new Date().toISOString().split('T')[0],
'paabgabetyp_kurzbz': '',
'bezeichnung': '',
'abgabedatum': null,
'insertvon': this.viewData?.uid ?? ''
})
let paIsBenotet = false
if(pa.note !== undefined && pa.note !== null) {
// check if the note is not defined as a non final projektarbeit note
const opt = this.notenOptionsNonFinal.find(opt => opt.note)
// if thats the case allow further work
if(opt) paIsBenotet = false
// else the PA is to be considered finished
paIsBenotet = true
}
pa.abgabetermine.forEach(termin => {
termin.note = this.allowedNotenOptions.find(opt => opt.note == termin.note)
termin.file = []
termin.allowedToSave = termin.insertvon == this.viewData?.uid && pa.betreuerart_kurzbz != 'Zweitbegutachter'
// update 08-01-2026: everybody is allowed to do everything in client, critical checks happen at backend level
// termin.allowedToSave = true
// update 21-01-2026: actually blocking operations on finished projektarbeiten seems like a decent idea
termin.allowedToSave = paIsBenotet ? false : true
// lektoren are not allowed to delete deadlines with existing submissions
termin.allowedToDelete = termin.allowedToSave && !termin.abgabedatum
termin.bezeichnung = {
bezeichnung: termin.bezeichnung,
paabgabetyp_kurzbz: termin.paabgabetyp_kurzbz
}
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
})
pa.betreuer = this.buildBetreuer(pa)
pa.student_uid = details.student_uid
pa.student = `${pa.vorname} ${pa.nachname}`
this.selectedProjektarbeit = pa
this.$refs.verticalsplit.showBoth()
this.$refs.modalContainerAbgabeDetail.show()
})
}).finally(()=>{this.loading = false})
},
centeredTextFormatter(cell) {
const val = cell.getValue()
@@ -276,7 +364,7 @@ export const AbgabetoolMitarbeiter = {
const val = cell.getValue()
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
'<p style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+val+'</p></div>'
'<a style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+val+'</a></div>'
},
tableResolve(resolve) {
this.tableBuiltResolve = resolve
@@ -290,16 +378,13 @@ export const AbgabetoolMitarbeiter = {
buildStg(projekt) {
return (projekt.typ + projekt.kurzbz)?.toUpperCase()
},
buildBetreuer(abgabe) {
// TODO: preload and insert own titled name of betreuer somehow
return abgabe.betreuerart_beschreibung + ': ' + (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
},
setupData(data){
this.projektarbeiten = data[0]
this.domain = data[1]
const d = data[0]?.retval?.map(projekt => {
let mode = 'detailTermine'
this.tableData = data[0]?.retval?.map(projekt => {
projekt.selectable = projekt.betreuerart_kurzbz !== 'Zweitbegutachter'
return {
...projekt,
@@ -316,12 +401,12 @@ export const AbgabetoolMitarbeiter = {
titel: projekt.titel
}
})
this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
this.$refs.abgabeTable.tabulator.setData(d);
this.$refs.abgabeTable.tabulator.setData(this.tableData);
},
loadProjektarbeiten(all = false, callback) {
this.$fhcApi.factory.lehre.getMitarbeiterProjektarbeiten(this.viewData?.uid ?? null, all)
this.$api.call(ApiAbgabe.getMitarbeiterProjektarbeiten(all))
.then(res => {
if(res?.data) this.setupData(res.data)
}).finally(() => {
@@ -332,7 +417,7 @@ export const AbgabetoolMitarbeiter = {
},
loadAbgaben(details) {
return new Promise((resolve) => {
this.$fhcApi.factory.lehre.getStudentProjektabgaben(details)
this.$api.call(ApiAbgabe.getStudentProjektabgaben(details))
.then(res => {
resolve(res)
})
@@ -347,7 +432,7 @@ export const AbgabetoolMitarbeiter = {
if(!tableDataSet) return
const rect = tableDataSet.getBoundingClientRect();
this.abgabeTableOptions.height = window.visualViewport.height - rect.top
this.abgabeTableOptions.height = window.visualViewport.height - rect.top - 80
this.$refs.abgabeTable.tabulator.setHeight(this.abgabeTableOptions.height)
},
async setupMounted() {
@@ -356,68 +441,125 @@ export const AbgabetoolMitarbeiter = {
this.loadProjektarbeiten()
this.$refs.verticalsplit.collapseBottom()
this.calcMaxTableHeight()
}
},
watch: {
'serienTermin.bezeichnung'(newVal) {
if(newVal?.paabgabetyp_kurzbz === 'qualgate1' || newVal?.paabgabetyp_kurzbz === 'qualgate2') {
this.serienTermin.kurzbz = newVal.bezeichnung
}
this.serienTermin.upload_allowed = newVal.upload_allowed_default
},
},
computed: {
getAllowedAbgabeTypeOptions() {
return this.abgabeTypeOptions.filter(opt => this.abgabetypenBetreuer.includes(opt.paabgabetyp_kurzbz))
}
},
created() {
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
// fetch config to avoid hard coded links
this.$api.call(ApiAbgabe.getConfig()).then(res => {
this.turnitin_link = res.data?.turnitin_link
this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link
this.abgabetypenBetreuer = res.data?.abgabetypenBetreuer
}).catch(e => {
this.loading = false
})
// fetch noten options
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
this.$api.call(ApiAbgabe.getNoten()).then(res => {
if(res.meta.status == 'success') {
this.notenOptions = res.data[0]
this.allowedNotenOptions = this.notenOptions.filter(
opt => res.data[1].includes(opt.note)
)
this.notenOptionsNonFinal = this.notenOptions.filter(
opt => res.data[2].includes(opt.note)
)
}
}).catch(e => {
this.loading = false
})
// fetch abgabetypen options
this.$api.call(ApiAbgabe.getPaAbgabetypen()).then(res => {
this.abgabeTypeOptions = res.data
}).catch(e => {
this.loading = false
})
},
mounted() {
this.setupMounted()
},
template: `
<template v-if="phrasenResolved">
<FhcOverlay :active="loading || saving"></FhcOverlay>
<bs-modal ref="modalContainerAddSeries" class="bootstrap-prompt"
dialogClass="modal-lg">
dialogClass="modal-lg">
<template v-slot:title>
<div>
{{ $p.t('abgabetool/neueTerminserie') }}
</div>
</template>
<template v-slot:default>
<div class="row">
<div class="col-3 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4zieldatum')}}
<div class="row mt-2">
<div class="col-12 col-md-3 align-content-center">
<div class="row fw-bold" style="margin-left: 2px">{{$capitalize( $p.t('abgabetool/c4zieldatum') )}}</div>
</div>
<div class="col-3 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4abgabetyp')}}
</div>
<div class="col-6 d-flex justify-content-center align-items-center">
{{$p.t('abgabetool/c4abgabekurzbz')}}
<div class="col-12 col-md-9">
<VueDatePicker
style="width: 95%;"
v-model="serienTermin.datum"
:clearable="false"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
</div>
</div>
<div class="row">
<div class="col-3 d-flex justify-content-center align-items-center">
<div>
<VueDatePicker
style="width: 95%;"
v-model="serienTermin.datum"
:clearable="false"
:enable-time-picker="false"
:format="formatDate"
:text-input="true"
auto-apply>
</VueDatePicker>
</div>
<div class="row mt-2">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4upload_allowed') )}}</div>
<div class="col-12 col-md-9">
<Checkbox
v-model="serienTermin.upload_allowed"
:binary="true"
:pt="{ root: { class: 'ml-auto' }}"
>
</Checkbox>
</div>
<div class="col-3 d-flex justify-content-center align-items-center">
<Dropdown
</div>
<div class="row mt-2">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4abgabetyp') )}}</div>
<div class="col-12 col-md-9"
v-if="abgabetypenBetreuer && abgabeTypeOptions"
>
<Dropdown
:style="{'width': '100%'}"
v-model="serienTermin.bezeichnung"
:options="allAbgabeTypes"
:options="getAllowedAbgabeTypeOptions"
:optionLabel="getOptionLabelAbgabetyp">
</Dropdown>
</div>
<div class="col-6 d-flex justify-content-center align-items-center">
<Textarea style="margin-bottom: 4px;" v-model="serienTermin.kurzbz" rows="3" cols="40"></Textarea>
</div>
<div class="row mt-2">
<div class="col-12 col-md-3 fw-bold align-content-center">{{$capitalize( $p.t('abgabetool/c4abgabekurzbz') )}}</div>
<div class="col-12 col-md-9">
<Textarea style="margin-bottom: 4px;" v-model="serienTermin.kurzbz" rows="1" class="w-100"></Textarea>
</div>
</div>
@@ -427,56 +569,56 @@ export const AbgabetoolMitarbeiter = {
</template>
</bs-modal>
<vertical-split ref="verticalsplit">
<template #top>
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
:title="''"
@uuidDefined="handleUuidDefined"
ref="abgabeTable"
:newBtnShow="true"
:newBtnLabel="$p.t('abgabetool/neueTerminserie')"
:newBtnDisabled="!selectedData.length"
@click:new=openAddSeriesModal
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
:useSelectionSpan="false"
>
<template #actions>
<button @click="toggleShowAll(!showAll)" role="button" class="btn btn-secondary ml-2">
<i v-show="!showAll" class="fa fa-eye"></i>
<i v-show="showAll" class="fa fa-eye-slash"></i>
{{ $p.t('abgabetool/showAll') }}
</button>
<button @click="showDeadlines" role="button" class="btn btn-secondary ml-2">
<i class="fa fa-hourglass-end"></i>
{{ $p.t('abgabetool/showDeadlines') }}
</button>
<div v-show="saving">
{{ $p.t('abgabetool/currentlySaving') }} <i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
<div v-show="loading">
{{ $p.t('abgabetool/currentlyLoading') }} <i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
</div>
</template>
</core-filter-cmpt>
</template>
<template #bottom>
<div v-show="selectedProjektarbeit" ref="selProj">
<AbgabeDetail :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
dialogClass="modal-xl" :allowFullscreenExpand="true"
@toggle-fullscreen="handleToggleFullscreenDetail">
<template v-slot:title>
<div>
{{$p.t('abgabetool/c4abgabeMitarbeiterDetailTitle')}}
</div>
</template>
</vertical-split>
<template v-slot:default>
<AbgabeDetail :projektarbeit="selectedProjektarbeit" :isFullscreen="detailIsFullscreen"></AbgabeDetail>
</template>
</bs-modal>
<!-- low max height on this vsplit wrapper to avoid padding scrolls, elements have their inherent height anyways -->
<div id="abgabetable" style="max-height:40vw;">
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
:title="''"
@uuidDefined="handleUuidDefined"
ref="abgabeTable"
:newBtnShow="true"
:newBtnLabel="$p.t('abgabetool/neueTerminserie')"
:newBtnDisabled="!selectedData.length"
@click:new=openAddSeriesModal
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
:useSelectionSpan="false"
>
<template #actions>
<button @click="toggleShowAll(!showAll)" role="button" class="btn btn-secondary ml-2">
<i v-show="!showAll" class="fa fa-eye"></i>
<i v-show="showAll" class="fa fa-eye-slash"></i>
{{ $p.t('abgabetool/showAll') }}
</button>
<button @click="showDeadlines" role="button" class="btn btn-secondary ml-2">
<i class="fa fa-hourglass-end"></i>
{{ $p.t('abgabetool/showDeadlines') }}
</button>
</template>
</core-filter-cmpt>
</div>
</template>
`,
};
@@ -1,13 +1,24 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import AbgabeDetail from "./AbgabeStudentDetail.js";
import VerticalSplit from "../../verticalsplit/verticalsplit.js";
import ApiAbgabe from '../../../api/factory/abgabe.js'
import BsModal from "../../Bootstrap/Modal.js";
import FhcOverlay from "../../Overlay/FhcOverlay.js";
const today = new Date()
export const AbgabetoolStudent = {
name: "AbgabetoolStudent",
components: {
CoreFilterCmpt,
Accordion: primevue.accordion,
AccordionTab: primevue.accordiontab,
BsModal,
AbgabeDetail,
VerticalSplit
FhcOverlay
},
provide() {
return {
notenOptions: Vue.computed(() => this.notenOptions),
isViewMode: Vue.computed(() => this.isViewMode),
moodle_link: Vue.computed(() => this.moodle_link)
}
},
props: {
student_uid_prop: {
@@ -24,147 +35,221 @@ export const AbgabetoolStudent = {
},
data() {
return {
tabulatorUuid: Vue.ref(0),
domain: '',
student_uid: null,
activeTabIndex: [0],
abgabeTypeOptions: null,
phrasenPromise: null,
phrasenResolved: false,
loading: false,
notenOptions: null,
detail: null,
projektarbeiten: null,
selectedProjektarbeit: null,
tableBuiltResolve: null,
tableBuiltPromise: null,
abgabeTableOptions: {
minHeight: 250,
index: 'projektarbeit_id',
layout: 'fitColumns',
placeholder: this.$p.t('global/noDataAvailable'),
columns: [
{title: Vue.computed(() => this.$p.t('abgabetool/c4details')), field: 'details', formatter: this.detailFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4beurteilung')), field: 'beurteilung', formatter: this.beurteilungFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4sem')), field: 'sem', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4stg')), field: 'stg', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4kontakt')), field: 'mail', formatter: this.mailFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4betreuer')), field: 'betreuer', formatter: this.centeredTextFormatter,widthGrow: 2},
{title: Vue.computed(() => this.$p.t('abgabetool/c4projekttyp')), field: 'typ', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4titel')), field: 'titel', formatter: this.centeredTextFormatter, widthGrow: 8}
],
persistence: false,
},
abgabeTableEventHandlers: [{
event: "tableBuilt",
handler: async () => {
this.tableBuiltResolve()
}
},
{
event: "cellClick",
handler: async (e, cell) => {
if(cell.getColumn().getField() === "details") {
const val = cell.getValue()
if(val.mode === 'detailTermine') {
this.setDetailComponent(cell.getValue())
} else if (val.mode === 'beurteilungDownload') {
const pdfExportLink = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'cis/private/pdfExport.php?xml=projektarbeitsbeurteilung.xml.php&xsl=Projektbeurteilung&betreuerart_kurzbz='+val.betreuerart_kurzbz+'&projektarbeit_id='+val.projektarbeit_id+'&person_id=' + val.betreuer_person_id
// const pdfExportLink2 = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'cis/private/lehre/projektbeurteilungDocumentExport.php?betreuerart_kurzbz='+val.betreuerart_kurzbz+'&projektarbeit_id='+val.projektarbeit_id+'&person_id=' + val.betreuer_person_id
window.open(pdfExportLink, '_blank')
}
} else if (cell.getColumn().getField() === "beurteilung") {
const val = cell.getValue()
if(val != '-') window.open(val, '_blank')
}
e.stopPropagation()
}
}
]};
moodle_link: null
};
},
methods: {
dateDiffInDays(datumParam) {
let datum = datumParam
if(datumParam instanceof Date && !isNaN(datum.getTime()))
{
const year = datumParam.getFullYear();
const month = datumParam.getMonth() + 1; // getMonth() is 0-indexed
const day = datumParam.getDate();
const pad = (num) => String(num).padStart(2, '0');
datum = `${year}-${pad(month)}-${pad(day)}`
}
const dateToday = luxon.DateTime.now().startOf('day');
const dateDatum = luxon.DateTime.fromISO(datum).startOf('day');
const duration = dateDatum.diff(dateToday, 'days');
return duration.values.days;
},
getDateStyleClass(termin) {
const datum = new Date(termin.datum)
const abgabedatum = new Date(termin.abgabedatum)
termin.diffindays = this.dateDiffInDays(termin.datum)
const isLate = termin.abgabedatum && abgabedatum > datum;
// GRADE STATUS
if (termin.note) {
if(Number.isInteger(termin.note)) {
const opt = this.notenOptions.find(opt => opt.note == termin.note)
if(opt.positiv) return 'bestanden'
}
if (termin.note.positiv) return 'bestanden';
return 'nichtbestanden';
}
// ACTION REQUIRED FOR GRADE
if (termin.bezeichnung?.benotbar && datum < today) {
return 'beurteilungerforderlich';
}
// SUBMISSION STATUS
if (termin.upload_allowed) {
if (termin.abgabedatum) {
return isLate ? 'verspaetet' : 'abgegeben';
}
// no submission yet
if (datum < today) return 'verpasst';
if (termin.diffindays <= 12) return 'abzugeben';
return 'standard';
}
// GENERIC STATUS
return datum < today ? 'verpasst' : 'standard';
},
checkQualityGatesStrict(termine) {
let qgate1Passed = false
let qgate2Passed = false
termine.forEach(t => {
const noteOption = this.notenOptions?.find(opt => opt.note == t.note)
if(noteOption && noteOption.positiv) {
if(t.paabgabetyp_kurzbz == 'qualgate1') {
qgate1Passed = true
} else if(t.paabgabetyp_kurzbz == 'qualgate2') {
qgate2Passed = true
}
}
})
return qgate1Passed && qgate2Passed
},
checkQualityGatesOptional(termine) {
const qgate1found = termine.find(t => t.paabgabetyp_kurzbz == 'qualgate1')
const qgate2found = termine.find(t => t.paabgabetyp_kurzbz == 'qualgate2')
let qgate1positiv = true
if(qgate1found) {
qgate1positiv = false
termine.forEach(t => {
const noteOption = this.notenOptions?.find(opt => opt.note == t.note)
if(noteOption && noteOption.positiv) {
if (t.paabgabetyp_kurzbz == 'qualgate1') {
qgate1positiv = true
}
}
})
}
let qgate2positiv = true
if(qgate2found) {
qgate2positiv = false
termine.forEach(t => {
const noteOption = this.notenOptions?.find(opt => opt.note == t.note)
if(noteOption && noteOption.positiv) {
if (t.paabgabetyp_kurzbz == 'qualgate2') {
qgate2positiv = true
}
}
})
}
return qgate1positiv && qgate2positiv
},
isPastDate(date) {
return new Date(date) < new Date(Date.now())
},
setDetailComponent(details){
this.loading = true
this.loadAbgaben(details).then((res)=> {
const pa = this.projektarbeiten?.retval?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
const pa = this.projektarbeiten?.find(projekarbeit => projekarbeit.projektarbeit_id == details.projektarbeit_id)
pa.abgabetermine = res.data[0].retval
const paIsBenotet = pa.note !== null
pa.abgabetermine.forEach(termin => {
termin.file = []
termin.allowedToUpload = true
termin.allowedToUpload = false
// TODO: fixtermin logic?
if(termin.bezeichnung == 'Endupload' && this.isPastDate(termin.datum)) {
if(termin.paabgabetyp_kurzbz == 'end') {
// old assumed production logic when qgates are required
// termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGatesStrict(pa.abgabetermine)
// termin.allowedToUpload = false
} else {
// new larifari we want qgates but they are optional fhtw mode
termin.allowedToUpload = !this.isPastDate(termin.datum) && this.checkQualityGatesOptional(pa.abgabetermine)
// development purposes
// termin.allowedToUpload = this.checkQualityGatesStrict(pa.abgabetermine)
// termin.allowedToUpload = true
} else if(termin.fixtermin) {
termin.allowedToUpload = !this.isPastDate(termin.datum)
} else {
// this could confuse people since we should dont show people this flag
termin.allowedToUpload = termin.upload_allowed
}
// blocks client upload button if projektarbeitet is already beurteilt und thus further abgaben on any termin should be blocked
if(paIsBenotet) termin.allowedToUpload = false
termin.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === termin.paabgabetyp_kurzbz)
termin.dateStyle = this.getDateStyleClass(termin)
})
pa.betreuer = this.buildBetreuer(pa)
pa.student_uid = this.student_uid
this.selectedProjektarbeit = pa
this.$refs.modalContainerAbgabeDetail.show()
this.$refs.verticalsplit.showBoth()
})
}).finally(()=>{this.loading=false})
},
centeredTextFormatter(cell) {
const val = cell.getValue()
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%;">' +
'<p style="max-width: 100%; word-wrap: break-word; white-space: normal;">'+val+'</p></div>'
},
detailFormatter(cell) {
const val = cell.getValue()
if(val.mode === 'detailTermine') {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%;">' +
'<a><i class="fa fa-folder-open" style="color:#00649C"></i></a></div>'
} else if (val.mode === 'beurteilungDownload') {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%;">' +
'<a><i class="fa fa-file-pdf" style="color:#00649C"></i></a></div>'
}
},
mailFormatter(cell) {
const val = cell.getValue()
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%;">' +
'<a href='+val+'><i class="fa fa-envelope" style="color:#00649C"></i></a></div>'
},
beurteilungFormatter(cell) {
const val = cell.getValue()
if(val) {
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%">' +
return '<div style="display: flex; justify-content: center; align-items: center; height: 100%;">' +
'<a><i class="fa fa-file-pdf" style="color:#00649C"></i></a></div>'
} else return '-'
},
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
buildMailToLink(abgabe) {
return 'mailto:' + abgabe.mitarbeiter_uid +'@'+ this.domain
buildMailToLink(projekt) {
// should always be "projekt.mitarbeiter_uid +'@'+ this.domain", built in backend
return 'mailto:' + projekt.email
},
buildBetreuer(abgabe) {
return abgabe.betreuerart_beschreibung + ': ' + (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
return (abgabe.btitelpre ? abgabe.btitelpre + ' ' : '') + abgabe.bvorname + ' ' + abgabe.bnachname + (abgabe.btitelpost ? ' ' + abgabe.btitelpost : '')
},
setupData(data){
this.projektarbeiten = data[0]
this.domain = data[1]
this.student_uid = data[2]
const d = data[0]?.retval?.map(projekt => {
async setupData(data){
// this.projektarbeiten = data[0]
const projektarbeiten = data[0] ?? null
if(!projektarbeiten) return
this.projektarbeiten = projektarbeiten.map(projekt => {
let mode = 'detailTermine'
if (projekt.babgeschickt || projekt.zweitbetreuer_abgeschickt) {
// mode = 'beurteilungDownload' // build dl link for both betreuer documents
projekt.beurteilungLink = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'cis/private/pdfExport.php?xml=projektarbeitsbeurteilung.xml.php&xsl=Projektbeurteilung&betreuerart_kurzbz='+projekt.betreuerart_kurzbz+'&projektarbeit_id='+projekt.projektarbeit_id+'&person_id=' + projekt.bperson_id
}
return {
...projekt,
details: {
student_uid: this.student_uid,
projektarbeit_id: projekt.projektarbeit_id,
@@ -172,7 +257,8 @@ export const AbgabetoolStudent = {
betreuerart_kurzbz: projekt.betreuerart_kurzbz,
mode
},
beurteilung: projekt.beurteilungLink ?? null,
beurteilung1: projekt.downloadLink1 ?? null,
beurteilung2: projekt.downloadLink2 ?? null,
sem: projekt.studiensemester_kurzbz,
stg: projekt.kurzbzlang,
mail: this.buildMailToLink(projekt),
@@ -182,43 +268,49 @@ export const AbgabetoolStudent = {
}
})
this.$refs.abgabeTable.tabulator.setColumns(this.abgabeTableOptions.columns)
this.$refs.abgabeTable.tabulator.setData(d);
},
loadProjektarbeiten() {
this.$fhcApi.factory.lehre.getStudentProjektarbeiten(this.student_uid_prop || this.viewData?.uid || null)
this.$api.call(ApiAbgabe.getStudentProjektarbeiten(this.student_uid))
.then(res => {
if(res?.data) this.setupData(res.data)
})
},
loadAbgaben(details) {
return new Promise((resolve) => {
this.$fhcApi.factory.lehre.getStudentProjektabgaben(details)
this.$api.call(ApiAbgabe.getStudentProjektabgaben(details))
.then(res => {
resolve(res)
})
})
},
handleUuidDefined(uuid) {
this.tabulatorUuid = uuid
},
calcMaxTableHeight() {
const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : ''
const tableDataSet = document.getElementById('filterTableDataset' + tableID);
if(!tableDataSet) return
const rect = tableDataSet.getBoundingClientRect();
this.abgabeTableOptions.height = window.visualViewport.height - rect.top
this.$refs.abgabeTable.tabulator.setHeight(this.abgabeTableOptions.height)
},
async setupMounted() {
this.tableBuiltPromise = new Promise(this.tableResolve)
await this.tableBuiltPromise
this.loadProjektarbeiten()
this.$refs.verticalsplit.collapseBottom()
//this.calcMaxTableHeight()
},
getAccTabHeaderForProjektarbeit(projektarbeit) {
let title = ''
title += projektarbeit.titel ?? this.$p.t('abgabetool/keinTitel')
return title
},
getMailLink(projektarbeit) {
if(projektarbeit.email) {
return 'mailto:'+projektarbeit.email
} else return ''
},
getNoteBezeichnung(projektarbeit) {
if(projektarbeit.note && this.notenOptions) {
const noteOpt = this.notenOptions.find(opt => opt.note == projektarbeit.note)
return noteOpt?.bezeichnung
} else {
return ''
}
},
handleDownloadBeurteilung1(projektarbeit) {
window.open(projektarbeit.beurteilung1)
},
handleDownloadBeurteilung2(projektarbeit) {
window.open(projektarbeit.beurteilung2)
}
},
watch: {
@@ -227,37 +319,152 @@ export const AbgabetoolStudent = {
computed: {
isViewMode() {
return this.student_uid !== this.viewData.uid
},
student_uid() {
return this.student_uid_prop || this.viewData?.uid || null
}
},
created() {
async created() {
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
this.loading = true
//TODO: SWITCH TO NOTEN API ONCE NOTENTOOL IS IN MASTER TO AVOID DUPLICATE API
await this.$api.call(ApiAbgabe.getNoten()).then(res => {
if(res.meta.status == 'success') {
this.notenOptions = res.data[0]
this.allowedNotenOptions = this.notenOptions.filter(
opt => res.data[1].includes(opt.note)
)
}
}).finally(() => {
this.loading = false
})
// fetch abgabetypen options
this.$api.call(ApiAbgabe.getPaAbgabetypen()).then(res => {
this.abgabeTypeOptions = res.data
}).catch(e => {
this.loading = false
})
// fetch config to avoid hard coded links
this.$api.call(ApiAbgabe.getConfigStudent()).then(res => {
this.moodle_link = res.data?.moodle_link
}).catch(e => {
this.loading = false
})
},
mounted() {
this.setupMounted()
},
template: `
<vertical-split ref="verticalsplit">
<template #top>
<h2>{{$p.t('abgabetool/abgabetoolTitle')}}</h2>
<hr>
<core-filter-cmpt
@uuidDefined="handleUuidDefined"
:title="''"
ref="abgabeTable"
:tabulator-options="abgabeTableOptions"
:tabulator-events="abgabeTableEventHandlers"
tableOnly
:sideMenu="false"
/>
</template>
<template #bottom>
<div v-show="selectedProjektarbeit">
<AbgabeDetail :viewMode="isViewMode" :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</div>
<template v-if="phrasenResolved">
<FhcOverlay :active="loading"></FhcOverlay>
<bs-modal ref="modalContainerAbgabeDetail" class="bootstrap-prompt"
dialogClass="modal-xl" :allowFullscreenExpand="true">
<template v-slot:title>
<div>
{{$capitalize( $p.t('abgabetool/c4abgabeStudentDetailTitle') )}}
</div>
</template>
</vertical-split>
<template v-slot:default>
<AbgabeDetail :projektarbeit="selectedProjektarbeit"></AbgabeDetail>
</template>
</bs-modal>
<h2>{{$capitalize( $p.t('abgabetool/abgabetoolTitle') )}}</h2>
<hr>
<div v-if="projektarbeiten === null">
{{$capitalize( $p.t('abgabetool/c4abgabeStudentNoProjectsFound') )}}
</div>
<Accordion :multiple="true" :activeIndex="activeTabIndex">
<template v-for="projektarbeit in projektarbeiten">
<AccordionTab>
<template #header>
<div class="d-flex row w-100">
<div class="text-start" :class="projektarbeit.note != null ? 'col-6' : 'col-12'">
<span>{{getAccTabHeaderForProjektarbeit(projektarbeit)}}</span>
</div>
<div class="col-6 text-end">
<span>{{getNoteBezeichnung(projektarbeit)}}</span>
</div>
</div>
</template>
<div class="row">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4details') )}}</div>
<div class="col-8 col-md-9">
<button @click="setDetailComponent(projektarbeit.details)" class="btn btn-primary">
{{$capitalize( $p.t('abgabetool/c4projektdetailsOeffnen') )}} <a><i class="fa fa-folder-open"></i></a>
</button>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4beurteilung') )}}</div>
<div class="col-8 col-md-9">
<button v-if="projektarbeit.beurteilung1" @click="handleDownloadBeurteilung1(projektarbeit)" class="btn btn-primary">
<a> {{$capitalize( $p.t('abgabetool/c4downloadBeurteilungErstbetreuer') )}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
</button>
<a v-else>{{$capitalize( $p.t('abgabetool/c4nobeurteilungVorhanden') )}}</a>
<button v-if="projektarbeit.beurteilung2" @click="handleDownloadBeurteilung2(projektarbeit)" class="btn btn-primary" style="margin-left: 4px;">
<a> {{$capitalize( $p.t('abgabetool/c4downloadBeurteilungZweitbetreuer') )}} <i class="fa fa-file-pdf" style="margin-left:4px; cursor: pointer;"></i></a>
</button>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4sem') )}}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.sem }}
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4stg') )}}</div>
<div class="col-8 col-md-9">
<div class="col-1 d-flex justify-content-start align-items-start">
{{ projektarbeit.stg }}
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{ projektarbeit?.betreuerart_kurzbz ? $capitalize( $p.t('abgabetool/c4betrart' + projektarbeit.betreuerart_kurzbz) ) : $capitalize( $p.t('abgabetool/c4betreuer') ) }}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.betreuerart_kurzbz ? projektarbeit.betreuer : '' }}
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4betreuerEmailKontakt') )}}</div>
<div class="col-8 col-md-9">
<a :href="getMailLink(projektarbeit)"><i class="fa fa-envelope" style="color:#00649C"></i></a>
</div>
</div>
<div v-if="projektarbeit.zweitbetreuer_person_id || projektarbeit.zweitbetreuer" class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{ projektarbeit.zweitbetreuer_betreuerart_kurzbz ? $p.t('abgabetool/c4betrart' + projektarbeit.zweitbetreuer_betreuerart_kurzbz) : '' }}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.zweitbetreuer?.first }}
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4projekttyp') )}}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.projekttypbezeichnung }}
</div>
</div>
<div class="row mt-2">
<div class="col-4 col-md-3 fw-bold">{{$capitalize( $p.t('abgabetool/c4titel') )}}</div>
<div class="col-8 col-md-9">
{{ projektarbeit.titel }}
</div>
</div>
</AccordionTab>
</template>
</Accordion>
</template>
`,
};
@@ -1,4 +1,5 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import ApiAbgabe from '../../../api/factory/abgabe.js'
export const DeadlineOverview = {
name: "DeadlineOverview",
@@ -25,19 +26,21 @@ export const DeadlineOverview = {
tabulatorUuid: Vue.ref(0),
tableBuiltResolve: null,
tableBuiltPromise: null,
phrasenPromise: null,
phrasenResolved: false,
deadlineTableOptions: {
height: 700,
index: 'projektarbeit_id',
layout: 'fitColumns',
placeholder: this.$p.t('global/noDataAvailable'),
placeholder: Vue.computed(() => this.$p.t('global/noDataAvailable')),
columns: [
{title: Vue.computed(() => this.$p.t('abgabetool/c4zieldatum')), field: 'datum', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4fixtermin')), field: 'fixterminstring', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$p.t('abgabetool/c4abgabetyp')), field: 'typ_bezeichnung', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4abgabekurzbz')), field: 'kurzbz', formatter: this.centeredTextFormatter, widthGrow: 3},
{title: Vue.computed(() => this.$p.t('person/studentIn')), field: 'student', formatter: this.centeredTextFormatter, widthGrow: 2},
{title: Vue.computed(() => this.$p.t('abgabetool/c4stg')), field: 'stg', formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$p.t('abgabetool/c4sem')), field: 'semester', formatter: this.centeredTextFormatter, widthGrow: 1}
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4zieldatum'))), field: 'datum', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4fixterminv4'))), field: 'fixterminstring', formatter: this.centeredTextFormatter, widthGrow: 1, tooltip: false},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabetyp'))), field: 'typ_bezeichnung', formatter: this.centeredTextFormatter, widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4abgabekurzbz'))), field: 'kurzbz', formatter: this.centeredTextFormatter, widthGrow: 3},
{title: Vue.computed(() => this.$capitalize(this.$p.t('person/studentIn'))), field: 'student', formatter: this.centeredTextFormatter, widthGrow: 2},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4stg'))), field: 'stg', formatter: this.centeredTextFormatter,widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('abgabetool/c4sem'))), field: 'semester', formatter: this.centeredTextFormatter, widthGrow: 1}
],
persistence: false,
},
@@ -84,7 +87,7 @@ export const DeadlineOverview = {
this.tableBuiltResolve = resolve
},
loadDeadlines() {
this.$fhcApi.factory.lehre.fetchDeadlines(this.person_uid_prop ?? null)
this.$api.call(ApiAbgabe.fetchDeadlines(this.person_uid_prop ?? null))
.then(res => {
if(res?.data) this.setupData(res.data)
})
@@ -109,7 +112,7 @@ export const DeadlineOverview = {
if(!tableDataSet) return
const rect = tableDataSet.getBoundingClientRect();
this.deadlineTableOptions.height = window.visualViewport.height - rect.top
this.deadlineTableOptions.height = window.visualViewport.height - rect.top - 30
this.$refs.deadlineTable.tabulator.setHeight(this.deadlineTableOptions.height)
},
async setupMounted() {
@@ -127,7 +130,8 @@ export const DeadlineOverview = {
},
created() {
this.phrasenPromise = this.$p.loadCategory(['abgabetool', 'global'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
},
mounted() {
this.setupMounted()
@@ -0,0 +1,83 @@
export const AbgabeterminStatusLegende = {
name: 'AbgabeterminStatusLegende',
template: `
<div class="text-center">
<div class="col" style="width: 80%; margin-left: 12px;">
<div class="row" style="margin-bottom: 2px">
<div class="col-auto verspaetet-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-triangle-exclamation"></i>
</div>
<div class="col-auto" style="display: flex; align-items: center;">
<h5>{{ $capitalize($p.t('abgabetool/c4tooltipVerspaetet')) }}</h5>
</div>
</div>
<div class="row" style="margin-bottom: 2px">
<div class="col-auto verpasst-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-calendar-xmark"></i>
</div>
<div class="col-auto" style="display: flex; align-items: center;">
<h5>{{ $capitalize($p.t('abgabetool/c4tooltipVerpasst')) }}</h5>
</div>
</div>
<div class="row" style="margin-bottom: 2px">
<div class="col-auto abzugeben-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-hourglass-half"></i>
</div>
<div class="col-auto" style="display: flex; align-items: center;">
<h5>{{ $capitalize($p.t('abgabetool/c4tooltipAbzugeben')) }}</h5>
</div>
</div>
<div class="row" style="margin-bottom: 2px">
<div class="col-auto standard-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-clock"></i>
</div>
<div class="col-auto" style="display: flex; align-items: center;">
<h5>{{ $capitalize($p.t('abgabetool/c4tooltipStandardv2')) }}</h5>
</div>
</div>
<div class="row" style="margin-bottom: 2px">
<div class="col-auto abgegeben-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-paperclip"></i>
</div>
<div class="col-auto" style="display: flex; align-items: center;">
<h5>{{ $capitalize($p.t('abgabetool/c4tooltipAbgegeben')) }}</h5>
</div>
</div>
<div class="row" style="margin-bottom: 2px">
<div class="col-auto beurteilungerforderlich-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-list-check"></i>
</div>
<div class="col-auto" style="display: flex; align-items: center;">
<h5>{{ $capitalize($p.t('abgabetool/c4tooltipBeurteilungerforderlich')) }}</h5>
</div>
</div>
<div class="row" style="margin-bottom: 2px">
<div class="col-auto bestanden-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-check"></i>
</div>
<div class="col-auto" style="display: flex; align-items: center;">
<h5>{{ $capitalize($p.t('abgabetool/c4tooltipBestanden')) }}</h5>
</div>
</div>
<div class="row" style="margin-bottom: 2px">
<div class="col-auto nichtbestanden-header" style="height: 36px; width:36px; padding: 0px; display: flex; align-items: center; justify-content: center;">
<i class="fa-solid fa-circle-exclamation"></i>
</div>
<div class="col-auto" style="display: flex; align-items: center;">
<h5>{{ $capitalize($p.t('abgabetool/c4tooltipNichtBestanden')) }}</h5>
</div>
</div>
</div>
</div>
`
};
export default AbgabeterminStatusLegende;
@@ -31,7 +31,7 @@ export default {
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
].join(": "));
} else {console.log(this.event.lektor);
} else {;
tooltipArray.push([
this.$p.t('lehre/lektor'),
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
@@ -151,10 +151,6 @@ export default {
db: this.dashboard
}
}).then(res => {
res.data.retval.forEach(widget => {
widget.arguments = JSON.parse(widget.arguments);
widget.setup = JSON.parse(widget.setup);
});
this.widgets = res.data.retval;
}).catch(err => console.error('ERROR:', err));
+1 -1
View File
@@ -144,7 +144,7 @@ export default {
},
async created() {
this.widget = await CachedWidgetLoader.loadWidget(this.id);
let component = (await import("../" + this.widget.setup.file)).default;
let component = (await import(this.widget.setup.file)).default;
this.$options.components["widget" + this.widget.widget_id] = component;
this.component = "widget" + this.widget.widget_id;
this.arguments = { ...this.widget.arguments, ...this.config };
+1 -1
View File
@@ -27,7 +27,7 @@ export default {
<li class="form-upload-dms-item">
<span class="col-auto"><i class="fa fa-file me-1"></i></span>
<span class="col">{{ modelValue.name }}</span>
<a v-if="preview" :href="preview" target="_blank" class="col-auto btn btn-outline-secondary btn-p-0 me-1">
<a v-if="preview" :href="preview" target="_blank" class="col-auto btn btn-outline-secondary btn-p-0 me-2">
<i class="fa fa-download"></i>
</a>
<button class="col-auto btn btn-outline-secondary btn-p-0" @click="$emit('delete')">
@@ -8,7 +8,6 @@ import LvTabs from "./Setup/Tabs.js";
import ApiDetails from "../../api/lehrveranstaltung/details.js";
import ApiLektor from "../../api/lehrveranstaltung/lektor.js";
import ApiGruppe from "../../api/lehrveranstaltung/gruppe.js";
import ApiStudiengangTree from "../../api/lehrveranstaltung/studiengangtree.js";
import ApiSearchbar from "../../api/factory/searchbar.js";
@@ -205,6 +204,21 @@ export default {
this.$router.push({ name: routeName, params: newParams });
this.selected = [];
},
isDisabled(item)
{
if (!item?.requires?.length)
return false;
const values = {
stg: this.stg,
emp: this.emp,
studiensemester: this.selectedStudiensemester,
semester: this.semester
};
return item.requires.some(req => !values[req]);
}
},
created() {
this.$p.loadCategory(['lehre', 'person', 'global'])
@@ -239,6 +253,74 @@ export default {
})
.catch(this.$fhcAlert.handleSystemError);
},
computed:
{
appMenuExtraItems()
{
let extraItems = [];
const studiengang_kz = this.stg || '';
const studiensemester = this.selectedStudiensemester;
const semester = this.semester || '';
const uid = this.emp || '';
extraItems.push(
{
description: 'lehre/berichte',
requires: ['stg'],
children: [
{
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'content/statistik/lvplanung.xls.php'
+ '?studiengang_kz=' + studiengang_kz
+ '&studiensemester_kurzbz=' + studiensemester
+ '&semester=' + semester,
description: 'lehre/lvplanung',
requires: ['stg']
},
{
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'content/statistik/lehrauftragsliste_gst.xls.php'
+ '?studiengang_kz=' + studiengang_kz
+ '&studiensemester_kurzbz=' + studiensemester
+ '&semester=' + semester,
description: 'lehre/lehrauftragsliste',
requires: ['stg']
},
{
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'content/pdfExport.php?xml=lehrauftrag.xml.php'
+ '&xsl=Lehrauftrag'
+ '&stg_kz=' + studiengang_kz
+ '&ss=' + studiensemester,
description: 'lehre/lehrauftraege',
requires: ['stg']
},
{
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'content/pdfExport.php?xml=lehrauftrag.xml.php'
+ '&xsl=Lehrauftrag'
+ '&stg_kz=' + studiengang_kz
+ '&ss=' + studiensemester
+ '&uid=' + uid,
description: 'lehre/lehrauftragslisteemp',
requires: ['emp']
}
]
},
{
link: FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'vilesci/lehre/lehrveranstaltung.php'
+ '?stg_kz=' + studiengang_kz,
description: 'lehre/extrakvverwaltung',
requires: ['stg']
}
);
return extraItems;
}
},
template: /* html */`
<div class="stv">
@@ -278,7 +360,40 @@ export default {
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<div class="offcanvas-body">
<app-menu app-identifier="lvv" />
<app-menu app-identifier="lvv">
<template v-for="(item, key) in appMenuExtraItems" :key="key">
<li v-if="item.children" class="dropend">
<a
class="dropdown-toggle"
href="#"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
data-bs-popper-config='{"strategy":"fixed"}'
>
{{ $p.t(item.description) }}
</a>
<ul class="dropdown-menu p-0">
<li
v-for="(child, childKey) in item.children"
:key="childKey"
>
<a class="dropdown-item" :href="child.link" target="_blank" :class="{ disabled: isDisabled(child) }">
{{ $p.t(child.description) }}
</a>
</li>
</ul>
</li>
<li v-else>
<a
:href="item.link"
target="_blank"
:class="{ disabled: isDisabled(item) }">
{{ $p.t(item.description) }}
</a>
</li>
</template>
</app-menu>
</div>
</aside>
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100">
@@ -243,6 +243,7 @@ export default {
title: this.$p.t('global', 'aktionen')
});
*/
this.$emit('tabulator_tablebuilt');
}
},
{
+8 -2
View File
@@ -56,6 +56,7 @@ export default {
},
data() {
return {
tablebuilt: false,
isVisibleDiv: false,
messageId: null
}
@@ -139,8 +140,10 @@ export default {
},
resetMessageId(){
this.messageId = null;
},
tableBuilt: function() {
this.tablebuilt = true;
}
},
template: `
<div class="core-messages h-100 pb-3">
@@ -155,6 +158,7 @@ export default {
</form>
<message-modal
v-if="tablebuilt || id.length > 1"
ref="modalMsg"
:type-id="typeId"
:id="id"
@@ -166,8 +170,9 @@ export default {
</message-modal>
<!--in same page-->
<div v-show="isVisibleDiv" class="overflow-auto m-3" style="max-height: 500px; border: 1px solid #ccc;">
<div v-if="isVisibleDiv" class="overflow-auto m-3" style="max-height: 500px; border: 1px solid #ccc;">
<form-only
v-if="tablebuilt || id.length > 1"
ref="templateNewDivMessage"
:type-id="typeId"
:id="id"
@@ -187,6 +192,7 @@ export default {
:openMode="openMode"
@newMessage="handleMessage"
@replyToMessage="handleMessage"
@tabulator_tablebuilt="tableBuilt"
>
</table-messages>
</div>
+1 -1
View File
@@ -154,7 +154,7 @@ export default {
let button = document.createElement('button');
button.className = 'btn btn-outline-secondary btn-action';
button.title = this.$p.t('ui', 'notiz_edit');
button.title = this.$p.t('notiz', 'notiz_edit');
button.innerHTML = '<i class="fa fa-edit"></i>';
button.addEventListener(
'click',
@@ -0,0 +1,25 @@
export const FhcOverlay = {
name: 'FhcOverlay',
props: {
active: {
type: Boolean,
default: false
}
},
template: `
<div v-show="active"
style="
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255,255,255,0.5);
z-index: 99999999999;
pointer-events: none;
">
<i class="fa-solid fa-spinner fa-pulse fa-5x"></i>
</div>
`
};
export default FhcOverlay;
@@ -40,7 +40,7 @@ export default {
return this.$fhcAlert.alertError(this.$p.t('stv', 'error_combinePeople_samePerson'));
}
let linkCombinePeople = this.cisRoot + 'vilesci/stammdaten/personen_wartung.php?person_id_1=' + person1_id + '&person_id_2='+ person2_id;
let linkCombinePeople = FHC_JS_DATA_STORAGE_OBJECT.app_root + 'vilesci/stammdaten/personen_wartung.php?person_id_1=' + person1_id + '&person_id_2='+ person2_id;
this.openLink(linkCombinePeople);
},
openLink(url) {
@@ -1,3 +1,4 @@
import { splitMailsHelper } from "../../../../helpers/EmailHelpers.js"
export default {
name: "Kontaktieren",
computed: {
@@ -22,60 +23,16 @@ export default {
},
methods: {
async splitMails(mails, event) {
let splititem = ",";
let maillist = mails.join(splititem);
let mailto = "";
if (maillist.length > 2024)
{
if (await this.$fhcAlert.confirm({message: this.$p.t('stv', 'zuvieleEMails') }) === false)
return;
}
let firstrun = true;
let useBcc = event?.ctrlKey || event?.metaKey;
while (maillist.length > 0)
{
if (maillist.length > 2024)
{
let splitposition = maillist.lastIndexOf(splititem, 1900);
mailto = maillist.substring(0, splitposition);
maillist = maillist.substring(splitposition + 1);
}
else
{
mailto = maillist;
maillist = "";
}
let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`;
if (firstrun)
{
window.location.href = mailLink;
firstrun = false;
}
else
{
if (await this.$fhcAlert.confirm({message: this.$p.t('stv', 'weitereEMail')}) === true)
{
window.location.href = mailLink;
}
}
}
},
internMail(event) {
if (this.internMails.length)
{
this.splitMails(this.internMails, event);
splitMailsHelper(this.privateMails, event, null, this.$fhcAlert, this.$p)
}
},
privateMail(event) {
if (this.privateMails.length)
{
this.splitMails(this.privateMails, event);
splitMailsHelper(this.privateMails, event, null, this.$fhcAlert, this.$p)
}
}
},
@@ -32,15 +32,15 @@ export default {
},
hasZGVBakkPermission: {
from: 'hasZGVBakkPermission',
default: false
default: []
},
hasZGVMasterPermission: {
from: 'hasZGVMasterPermission',
default: false
default: []
},
hasZGVDoctorPermission: {
from: 'hasZGVDoctorPermission',
default: false
default: []
},
hasBismeldenPermission: {
from: 'hasBismeldenPermission',
@@ -89,6 +89,15 @@ export default {
computed: {
deltaLength() {
return Object.keys(this.deltaArray).length;
},
disableZgvBakk: function() {
return !this.hasZGVBakkPermission.includes(this.modelValue.studiengang_kz.toString());
},
disableZgvMaster: function() {
return !this.hasZGVMasterPermission.includes(this.modelValue.studiengang_kz.toString());
},
disableZgvDoctor: function() {
return !this.hasZGVDoctorPermission.includes(this.modelValue.studiengang_kz.toString());
}
},
watch: {
@@ -289,7 +298,7 @@ export default {
dropdown
name="zgv_code"
@complete="filterZgvs"
:disabled="!hasZGVBakkPermission"
:disabled="disableZgvBakk"
>
<template #option="slotProps">
<div
@@ -308,7 +317,6 @@ export default {
type="text"
v-model="data.zgvort"
name="zgvort"
:disabled="!hasZGVBakkPermission"
>
</form-input>
<form-input
@@ -325,7 +333,6 @@ export default {
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
:disabled="!hasZGVBakkPermission"
>
</form-input>
<form-input
@@ -335,7 +342,6 @@ export default {
type="select"
v-model="data.zgvnation"
name="zgvnation"
:disabled="!hasZGVBakkPermission"
>
<!-- TODO(chris): gesperrte nationen können nicht ausgewählt werden! Um das zu realisieren müsste man ein pseudo select machen -->
<option value="">&nbsp;</option>
@@ -356,7 +362,7 @@ export default {
dropdown
name="zgvmas_code"
@complete="filterMasterZgvs"
:disabled="!hasZGVMasterPermission"
:disabled="disableZgvMaster"
>
<template #option="slotProps">
<div
@@ -375,7 +381,6 @@ export default {
type="text"
v-model="data.zgvmaort"
name="zgvmaort"
:disabled="!hasZGVMasterPermission"
>
</form-input>
<form-input
@@ -392,7 +397,6 @@ export default {
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
:disabled="!hasZGVMasterPermission"
>
</form-input>
<form-input
@@ -402,7 +406,6 @@ export default {
type="select"
v-model="data.zgvmanation"
name="zgvmanation"
:disabled="!hasZGVMasterPermission"
>
<!-- TODO(chris): gesperrte nationen können nicht ausgewählt werden! Um das zu realisieren müsste man ein pseudo select machen -->
<option value="">&nbsp;</option>
@@ -424,7 +427,7 @@ export default {
dropdown
name="zgvdoktor_code"
@complete="filterDoktorZgvs"
:disabled="!hasZGVDoctorPermission"
:disabled="disableZgvDoctor"
>
<template #option="slotProps">
<div
@@ -443,7 +446,6 @@ export default {
type="text"
v-model="data.zgvdoktorort"
name="zgvdoktorort"
:disabled="!hasZGVDoctorPermission"
>
</form-input>
<form-input
@@ -460,7 +462,6 @@ export default {
format="dd.MM.yyyy"
preview-format="dd.MM.yyyy"
:teleport="true"
:disabled="!hasZGVDoctorPermission"
>
</form-input>
<form-input
@@ -470,7 +471,6 @@ export default {
type="select"
v-model="data.zgvdoktornation"
name="zgvdoktornation"
:disabled="!hasZGVDoctorPermission"
>
<!-- TODO(chris): gesperrte nationen können nicht ausgewählt werden! Um das zu realisieren müsste man ein pseudo select machen -->
<option value="">&nbsp;</option>
@@ -487,7 +487,6 @@ export default {
type="checkbox"
v-model="data.zgv_erfuellt"
name="zgv_erfuellt"
:disabled="!hasZGVBakkPermission"
>
</form-input>
</div>
@@ -499,7 +498,6 @@ export default {
type="checkbox"
v-model="data.zgvmas_erfuellt"
name="zgvmas_erfuellt"
:disabled="!hasZGVMasterPermission"
>
</form-input>
</div>
@@ -511,7 +509,6 @@ export default {
type="checkbox"
v-model="data.zgvdoktor_erfuellt"
name="zgvdoktor_erfuellt"
:disabled="!hasZGVDoctorPermission"
>
</form-input>
</div>
@@ -91,7 +91,15 @@ export default{
layoutColumnsOnNewData: false,
height: 'auto',
index: 'pruefung_id',
persistenceID: 'stv-details-pruefung-list-2025112402'
persistenceID: 'stv-details-pruefung-list-2026012701',
persistence: {
sort: false,
columns: ["width", "visible", "frozen"],
filter: false,
headerFilter: false,
group: false,
page: false
}
},
tabulatorEvents: [
{
@@ -16,8 +16,6 @@ export default {
__widgetsStarted[id] = new Promise((resolve, reject) => {
axios.get(__path, {params:{id}}).then(res => {
res.data.retval.arguments = JSON.parse(res.data.retval.arguments);
res.data.retval.setup = JSON.parse(res.data.retval.setup);
__widgets[id] = res.data.retval;
__widgetsStarted[id] = undefined;
resolve(__widgets[id]);
+45
View File
@@ -0,0 +1,45 @@
export async function splitMailsHelper(mails, event, subject, alertPluginRef, phrasenPluginRef) {
let splititem = ",";
let maillist = mails.join(splititem);
let mailto = "";
// take subject line length + '?subject=' length into account
const subjectlength = subject && typeof subject === 'string' ? subject.length + 9 : 0
if (maillist.length > 2024)
{
if (await alertPluginRef.confirm({message: phrasenPluginRef.t('stv', 'zuvieleEMails') }) === false)
return;
}
let firstrun = true;
let useBcc = event?.ctrlKey || event?.metaKey;
while (maillist.length > 0)
{
if (maillist.length + subjectlength > 2024)
{
let splitposition = maillist.lastIndexOf(splititem, 1900);
mailto = maillist.substring(0, splitposition);
maillist = maillist.substring(splitposition + 1);
}
else
{
mailto = maillist;
maillist = "";
}
let mailLink = useBcc ? `mailto:?bcc=${mailto}` : `mailto:${mailto}`;
if(subject && typeof subject === 'string') mailLink += `?subject=${subject}`
if (firstrun)
{
window.location.href = mailLink;
firstrun = false;
}
else
{
if (await alertPluginRef.confirm({message: phrasenPluginRef.t('stv', 'weitereEMail')}) === true)
{
window.location.href = mailLink;
}
}
}
}
+124
View File
@@ -0,0 +1,124 @@
export function addTagInTable(addedTag, rows, matchKey, tagsKey = "tags")
{
if (!addedTag || !Array.isArray(addedTag.response))
return;
rows.forEach(row =>
{
const rowData = row.getData();
let updated = false;
addedTag.response.forEach(tag =>
{
if (rowData[matchKey] !== tag[matchKey])
return;
let tags;
try {
tags = JSON.parse(rowData[tagsKey] || "[]");
} catch (e) {
tags = [];
}
if (!Array.isArray(tags))
tags = [];
if (tags.some(t => t?.id === tag.id))
return;
let newTag = { ...addedTag, id: tag.id };
tags.unshift(newTag);
rowData[tagsKey] = JSON.stringify(tags);
updated = true;
});
if (updated)
row.update(rowData);
});
}
export function deleteTagInTable(deletedTag, rows, tagsKeys = ['tags'])
{
if (!Array.isArray(tagsKeys))
tagsKeys = [tagsKeys];
rows.forEach(row => {
let rowData = row.getData();
let updates = {};
let changed = false;
tagsKeys.forEach(key => {
let tags;
try {
tags = JSON.parse(rowData[key] || "[]");
} catch (e) {
tags = [];
}
if (!Array.isArray(tags))
return;
let filtered = tags.filter(tag => tag?.id !== deletedTag);
if (filtered.length !== tags.length)
{
updates[key] = JSON.stringify(filtered);
changed = true;
}
});
if (changed) {
row.update(updates);
row.reformat();
}
});
}
export function updateTagInTable(updatedTag, rows, fields = ['tags'])
{
if (!Array.isArray(fields))
fields = [fields];
rows.forEach(row =>
{
const rowData = row.getData();
let updated = false;
fields.forEach(field =>
{
if (!rowData[field])
return;
let fieldData;
try {
fieldData = JSON.parse(rowData[field] || "[]");
} catch (e) {
return;
}
if (!Array.isArray(fieldData))
return;
let index = fieldData.findIndex(tag => tag?.id === updatedTag.id);
if (index !== -1)
{
fieldData[index] = { ...updatedTag };
let updatedFieldData = JSON.stringify(fieldData);
if (updatedFieldData !== rowData[field])
{
rowData[field] = updatedFieldData;
updated = true;
}
}
});
if (updated)
row.update(rowData);
});
}
+2 -1
View File
@@ -380,7 +380,8 @@ export default {
}
return result;
}
const fhcApiAxios = axios.create({
timeout: 500000,
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root
@@ -146,7 +146,7 @@ export function tagHeaderFilter(headerValue, rowValue, rowData, filterParams)
if (Array.isArray(data))
{
combinedText = data
.filter(item => item?.done === false)
.filter(item => item?.done !== true)
.map(item => `${item?.beschreibung} ${item?.notiz}`)
.join(' ');
}
+67
View File
@@ -0,0 +1,67 @@
export function tagFormatter(cell, tagComponent)
{
let tags = cell.getValue();
if (!tags) return;
let container = document.createElement('div');
container.className = "d-flex gap-1";
let parsedTags = JSON.parse(tags);
let maxVisibleTags = 2;
const rowData = cell.getRow().getData();
if (rowData._tagExpanded === undefined) {
rowData._tagExpanded = false;
}
const renderTags = () => {
container.innerHTML = '';
parsedTags = parsedTags.filter(item => item !== null);
parsedTags.sort((a, b) => {
let adone = a.done ? 1 : 0;
let bbone = b.done ? 1 : 0;
if (adone !== bbone)
{
return adone - bbone;
}
return b.id - a.id;
});
const tagsToShow = rowData._tagExpanded ? parsedTags : parsedTags.slice(0, maxVisibleTags);
tagsToShow.forEach(tag => {
if (!tag) return;
let tagElement = document.createElement('span');
tagElement.innerText = tag.beschreibung;
tagElement.title = tag.notiz;
tagElement.className = "tag " + tag.style;
if (tag.done) tagElement.className += " tag_done";
tagElement.addEventListener('click', (event) => {
event.stopPropagation();
event.preventDefault();
tagComponent.editTag(tag.id);
});
container.appendChild(tagElement);
});
if (parsedTags.length > maxVisibleTags) {
let toggle = document.createElement('button');
toggle.innerText = (rowData._tagExpanded ? '- ' : '+ ') + (parsedTags.length - maxVisibleTags);
toggle.className = "display_all";
toggle.title = rowData._tagExpanded ? "Tags ausblenden" : "Tags einblenden";
toggle.addEventListener('click', () => {
rowData._tagExpanded = !rowData._tagExpanded;
renderTags();
});
container.appendChild(toggle);
}
};
renderTags();
return container;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

+5 -2
View File
@@ -86,9 +86,12 @@ require_once('dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php');
require_once('dbupdate_3.4/47972_pruefungsverwaltung_ects_angabe.php');
require_once('dbupdate_3.4/62063_lv_evaluierung.php');
require_once('dbupdate_3.4/67490_studstatus_suche_abort_controller_haengt.php');
require_once('dbupdate_3.4/61164_abgabetool_quality_gates.php');
require_once('dbupdate_3.4/69065_Projektarbeiten_Firmen_verwalten.php');
require_once('dbupdate_3.4/68744_StV_settings.php');
require_once('dbupdate_3.4/62889_reihungstest_ueberwachung_mit_constructor.php');
require_once('dbupdate_3.4/71399_dashboard_update_widget_paths.php');
require_once('dbupdate_3.4/71645_studvw_messagetab_ladezeit.php');
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
@@ -167,8 +170,8 @@ $tabellen=array(
"campus.tbl_news" => array("news_id","uid","studiengang_kz","fachbereich_kurzbz","semester","betreff","text","datum","verfasser","updateamum","updatevon","insertamum","insertvon","datum_bis","content_id"),
"campus.tbl_notenschluessel" => array("lehreinheit_id","note","punkte"),
"campus.tbl_notenschluesseluebung" => array("uebung_id","note","punkte"),
"campus.tbl_paabgabetyp" => array("paabgabetyp_kurzbz","bezeichnung"),
"campus.tbl_paabgabe" => array("paabgabe_id","projektarbeit_id","paabgabetyp_kurzbz","fixtermin","datum","kurzbz","abgabedatum", "insertvon","insertamum","updatevon","updateamum"),
"campus.tbl_paabgabetyp" => array("paabgabetyp_kurzbz","bezeichnung","aktiv","upload_allowed_default","benotbar"),
"campus.tbl_paabgabe" => array("paabgabe_id","projektarbeit_id","paabgabetyp_kurzbz","fixtermin","datum","kurzbz","abgabedatum", "insertvon","insertamum","updatevon","updateamum","note","upload_allowed","beurteilungsnotiz"),
"campus.tbl_pruefungsfenster" => array("pruefungsfenster_id","studiensemester_kurzbz","oe_kurzbz","start","ende"),
"campus.tbl_pruefung" => array("pruefung_id","mitarbeiter_uid","studiensemester_kurzbz","pruefungsfenster_id","pruefungstyp_kurzbz","titel","beschreibung","methode","einzeln","storniert","insertvon","insertamum","updatevon","updateamum","pruefungsintervall"),
"campus.tbl_pruefungstermin" => array("pruefungstermin_id","pruefung_id","von","bis","teilnehmer_max","teilnehmer_min","anmeldung_von","anmeldung_bis","ort_kurzbz","sammelklausur", "anderer_raum"),
+8 -8
View File
@@ -39,7 +39,7 @@ if($result = @$db->db_query("SELECT 1 FROM dashboard.tbl_widget WHERE widget_kur
'hallowelt',
'Hallo Welt Widget',
'{"css": "d-flex justify-content-center align-items-center h-100", "title": "Hallo Welt"}'::jsonb,
'{"file": "DashboardWidget/Default.js", "icon": "https://upload.wikimedia.org/wikipedia/commons/8/8a/Farben-Testbild.svg", "name": "Hallo Welt", "width": {"max": 99}, "height": {"max": 99}, "hideFooter": false}'::jsonb
'{"file": "public/js/components/DashboardWidget/Default.js", "icon": "https://upload.wikimedia.org/wikipedia/commons/8/8a/Farben-Testbild.svg", "name": "Hallo Welt", "width": {"max": 99}, "height": {"max": 99}, "hideFooter": false}'::jsonb
);
EOWHW;
@@ -71,7 +71,7 @@ if($result = @$db->db_query("SELECT 1 FROM dashboard.tbl_widget WHERE widget_kur
'news',
'News Widget',
'{}'::jsonb,
'{"file": "DashboardWidget/News.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "News", "width": {"max": 4, "min": 1}, "height": {"max": 2, "min": 1}, "cis4link": "/CisVue/Cms/news", "hideFooter": false}'::jsonb
'{"file": "public/js/components/DashboardWidget/News.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "News", "width": {"max": 4, "min": 1}, "height": {"max": 2, "min": 1}, "cis4link": "/CisVue/Cms/news", "hideFooter": false}'::jsonb
);
EOWHW;
@@ -103,7 +103,7 @@ if($result = @$db->db_query("SELECT 1 FROM dashboard.tbl_widget WHERE widget_kur
'url',
'Bookmark Widget',
'{}'::jsonb,
'{"file": "DashboardWidget/Url.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "Bookmark", "width": 1, "height": {"max": 2, "min": 1}, "hideFooter": true}'::jsonb
'{"file": "public/js/components/DashboardWidget/Url.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "Bookmark", "width": 1, "height": {"max": 2, "min": 1}, "hideFooter": true}'::jsonb
);
EOWHW;
@@ -135,7 +135,7 @@ if($result = @$db->db_query("SELECT 1 FROM dashboard.tbl_widget WHERE widget_kur
'ampel',
'Ampel Widget',
'{}'::jsonb,
'{"file": "DashboardWidget/Ampel.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "Ampel", "width": 1, "height": {"max": 2, "min": 1}, "hideFooter": false}'::jsonb
'{"file": "public/js/components/DashboardWidget/Ampel.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "Ampel", "width": 1, "height": {"max": 2, "min": 1}, "hideFooter": false}'::jsonb
);
EOWHW;
@@ -162,7 +162,7 @@ if($result = @$db->db_query("SELECT 1 FROM dashboard.tbl_widget WHERE widget_kur
widget_kurzbz = 'lvplan',
beschreibung = 'LV-Plan Widget',
arguments = '{"bodyClass": "p-0"}'::jsonb,
setup = '{"file": "DashboardWidget/LvPlan.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "LV-Plan", "width": {"max": 4, "min": 1}, "height": {"max": 3, "min": 1}, "cis4link": "/Cis/LvPlan", "hideFooter": false}'::jsonb
setup = '{"file": "public/js/components/DashboardWidget/LvPlan.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "LV-Plan", "width": {"max": 4, "min": 1}, "height": {"max": 3, "min": 1}, "cis4link": "/Cis/LvPlan", "hideFooter": false}'::jsonb
WHERE
widget_kurzbz = 'stundenplan';
EOWHW;
@@ -195,7 +195,7 @@ if($result = @$db->db_query("SELECT 1 FROM dashboard.tbl_widget WHERE widget_kur
'lvplan',
'LV-Plan Widget',
'{"bodyClass": "p-0"}'::jsonb,
'{"file": "DashboardWidget/LvPlan.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "LV-Plan", "width": {"max": 4, "min": 1}, "height": {"max": 3, "min": 1}, "cis4link": "/Cis/LvPlan", "hideFooter": false}'::jsonb
'{"file": "public/js/components/DashboardWidget/LvPlan.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "LV-Plan", "width": {"max": 4, "min": 1}, "height": {"max": 3, "min": 1}, "cis4link": "/Cis/LvPlan", "hideFooter": false}'::jsonb
);
EOWHW;
@@ -227,7 +227,7 @@ if($result = @$db->db_query("SELECT 1 FROM dashboard.tbl_widget WHERE widget_kur
'studiengang',
'Das Studiengang-Widget enthält Informationen über den Studiengang eines Studenten.',
'{}'::jsonb,
'{"file": "DashboardWidget/Studiengang.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "Studiengang", "width": {"max": 2, "min": 1}, "height": {"max": 4, "min": 1}, "hideFooter": true}'::jsonb
'{"file": "public/js/components/DashboardWidget/Studiengang.js", "icon": "/skin/images/fh_technikum_wien_illustration_klein.png", "name": "Studiengang", "width": {"max": 2, "min": 1}, "height": {"max": 4, "min": 1}, "hideFooter": true}'::jsonb
);
EOWHW;
@@ -349,4 +349,4 @@ EOGP;
echo 'dashboard.tbl_dashboard_preset: Added Student Preset for Dashboard "CIS"<br>';
}
}
}
}
@@ -0,0 +1,379 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table_schema = 'campus' AND table_name = 'tbl_paabgabetyp' AND column_name = 'aktiv'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "ALTER TABLE campus.tbl_paabgabetyp
ADD COLUMN IF NOT EXISTS aktiv BOOLEAN DEFAULT true;";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp column aktiv default true hinzugefuegt';
}
}
if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table_schema = 'campus' AND table_name = 'tbl_paabgabetyp' AND column_name = 'upload_allowed_default'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "ALTER TABLE campus.tbl_paabgabetyp
ADD COLUMN IF NOT EXISTS upload_allowed_default BOOLEAN DEFAULT true;";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp column upload_allowed_default default true hinzugefuegt';
}
}
if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table_schema = 'campus' AND table_name = 'tbl_paabgabetyp' AND column_name = 'benotbar'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "ALTER TABLE campus.tbl_paabgabetyp
ADD COLUMN IF NOT EXISTS benotbar BOOLEAN DEFAULT true;";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp column benotbar default true hinzugefuegt';
}
}
// TODO DEFINE ACTUAL VALUES BENOTBAR / UPLOAD_ALLOWED_DEFAULT / AKTIV FOR EACH PAABGABETYPE - DEVLOPER DEFAULTS BELOW
if($result = $db->db_query("SELECT 1 FROM campus.tbl_paabgabetyp WHERE paabgabetyp_kurzbz='qualgate1'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO campus.tbl_paabgabetyp (paabgabetyp_kurzbz, bezeichnung, benotbar, upload_allowed_default, aktiv)
VALUES('qualgate1', 'Quality Gate 1', true, true, true);";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp quality gate 1 hinzugefuegt';
}
}
// set new cols for zwischenabgabe
if($result = $db->db_query("SELECT 1 FROM campus.tbl_paabgabetyp WHERE paabgabetyp_kurzbz='qualgate2'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "UPDATE campus.tbl_paabgabetyp
SET benotbar = false,
upload_allowed_default = true,
aktiv = true
WHERE paabgabetyp_kurzbz='zwischen';";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp zwischen updated benotbar = false, upload_allowed_default = true, aktiv = true';
}
}
// set new cols for note
if($result = $db->db_query("SELECT 1 FROM campus.tbl_paabgabetyp WHERE paabgabetyp_kurzbz='qualgate2'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "UPDATE campus.tbl_paabgabetyp
SET benotbar = false,
upload_allowed_default = false,
aktiv = false
WHERE paabgabetyp_kurzbz='note';";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp note updated benotbar = false, upload_allowed_default = false, aktiv = false';
}
}
// set new cols for abstract / entwurf
if($result = $db->db_query("SELECT 1 FROM campus.tbl_paabgabetyp WHERE paabgabetyp_kurzbz='qualgate2'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "UPDATE campus.tbl_paabgabetyp
SET benotbar = false,
upload_allowed_default = true,
aktiv = true
WHERE paabgabetyp_kurzbz='abstract';";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp abstract updated benotbar = false, upload_allowed_default = true, aktiv = true';
}
}
// set new cols for endabgabe / end
if($result = $db->db_query("SELECT 1 FROM campus.tbl_paabgabetyp WHERE paabgabetyp_kurzbz='qualgate2'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "UPDATE campus.tbl_paabgabetyp
SET benotbar = false,
upload_allowed_default = true,
aktiv = true
WHERE paabgabetyp_kurzbz='end';";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp end updated benotbar = false, upload_allowed_default = true, aktiv = true';
}
}
// set new cols for endabgabe im sekretariat / enda
if($result = $db->db_query("SELECT 1 FROM campus.tbl_paabgabetyp WHERE paabgabetyp_kurzbz='qualgate2'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "UPDATE campus.tbl_paabgabetyp
SET benotbar = false,
upload_allowed_default = false,
aktiv = false
WHERE paabgabetyp_kurzbz='enda';";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp enda updated benotbar = false, upload_allowed_default = false, aktiv = false';
}
}
if($result = $db->db_query("SELECT 1 FROM campus.tbl_paabgabetyp WHERE paabgabetyp_kurzbz='qualgate2'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO campus.tbl_paabgabetyp (paabgabetyp_kurzbz, bezeichnung, benotbar, upload_allowed_default, aktiv)
VALUES('qualgate2', 'Quality Gate 2', true, true, true);";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabetyp: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabetyp quality gate 2 hinzugefuegt';
}
}
if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table_schema = 'campus' AND table_name = 'tbl_paabgabe' AND column_name = 'note'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "ALTER TABLE campus.tbl_paabgabe
ADD COLUMN IF NOT EXISTS note SMALLINT DEFAULT NULL,
ADD CONSTRAINT tbl_paabgabe_note_fkey
FOREIGN KEY (note)
REFERENCES lehre.tbl_note(note)
ON UPDATE CASCADE ON DELETE RESTRICT;";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabe: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabe column note default 9 (noch nicht eingetragen) hinzugefuegt';
}
}
if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table_schema = 'campus' AND table_name = 'tbl_paabgabe' AND column_name = 'upload_allowed'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "ALTER TABLE campus.tbl_paabgabe
ADD COLUMN IF NOT EXISTS upload_allowed boolean DEFAULT true;";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabe: '.$db->db_last_error().'</strong><br>';
else
echo '<br>paabgabe column upload_allowed default false hinzugefuegt';
}
}
if($result = $db->db_query("SELECT 1 FROM information_schema.columns WHERE table_schema = 'campus' AND table_name = 'tbl_paabgabe' AND column_name = 'beurteilungsnotiz'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "ALTER TABLE campus.tbl_paabgabe
ADD COLUMN IF NOT EXISTS beurteilungsnotiz text DEFAULT NULL;";
if(!$db->db_query($qry))
echo '<strong>campus.tbl_paabgabe: '.$db->db_last_error().'</strong><br>';
else
echo "<br>paabgabe column beurteilungsnotiz default '' hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'basis/abgabe_student'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung)
SELECT 'basis/abgabe_student', 'Recht um Abgabetool für Studenten zu bedienen'";
if(!$db->db_query($qry))
echo '<strong>system.tbl_berechtigung: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_berechtigung insert basis/abgabe_student hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'basis/abgabe_lektor'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung)
SELECT 'basis/abgabe_lektor', 'Recht um Abgabetool für Lektoren zu bedienen'";
if(!$db->db_query($qry))
echo '<strong>system.tbl_berechtigung: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_berechtigung insert basis/abgabe_lektor hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'basis/abgabe_assistenz'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung)
SELECT 'basis/abgabe_assistenz', 'Recht um Abgabetool für Assistenzen zu bedienen'";
if(!$db->db_query($qry))
echo '<strong>system.tbl_berechtigung: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_berechtigung insert basis/abgabe_assistenz hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM information_schema.routines WHERE routine_schema = 'campus' AND routine_name = 'get_betreuer_details'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "CREATE OR REPLACE FUNCTION campus.get_betreuer_details(b_person_id INT)
RETURNS TABLE (
full_name TEXT
)
LANGUAGE sql
AS $$
SELECT DISTINCT
trim(
COALESCE(titelpre,'') || ' ' ||
COALESCE(vorname,'') || ' ' ||
COALESCE(nachname,'') || ' ' ||
COALESCE(titelpost,'')
) AS full_name
FROM public.tbl_person
JOIN lehre.tbl_projektbetreuer
ON lehre.tbl_projektbetreuer.person_id = public.tbl_person.person_id
LEFT JOIN public.tbl_benutzer
ON public.tbl_benutzer.person_id = public.tbl_person.person_id
LEFT JOIN public.tbl_mitarbeiter
ON public.tbl_benutzer.uid = public.tbl_mitarbeiter.mitarbeiter_uid
WHERE public.tbl_person.person_id = b_person_id;
$$;";
if(!$db->db_query($qry))
echo '<strong>campus.get_betreuer_details: '.$db->db_last_error().'</strong><br>';
else
echo "<br>campus.get_betreuer_details function hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'basis/abgabe_assistenz'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung)
SELECT 'basis/abgabe_assistenz', 'Recht um Abgabetool für Assistenzen zu bedienen'";
if(!$db->db_query($qry))
echo '<strong>system.tbl_berechtigung: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_berechtigung insert basis/abgabe_assistenz hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM public.tbl_vorlage WHERE vorlage_kurzbz = 'PaabgabeUpdatesBetSM'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO public.tbl_vorlage (vorlage_kurzbz, bezeichnung, anmerkung, mimetype)
VALUES ('PaabgabeUpdatesBetSM', 'PaabgabeUpdatesBetSM', null, 'text/html')
ON CONFLICT (vorlage_kurzbz) DO NOTHING;";
if(!$db->db_query($qry))
echo '<strong>system.tbl_vorlage: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_vorlage PaabgabeUpdatesBetSM hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM public.tbl_vorlage WHERE vorlage_kurzbz = 'PaabgabeUpdatesSammelmail'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO public.tbl_vorlage (vorlage_kurzbz, bezeichnung, anmerkung, mimetype)
VALUES ('PaabgabeUpdatesSammelmail', 'PaabgabeUpdatesSammelmail', null, 'text/html')
ON CONFLICT (vorlage_kurzbz) DO NOTHING;";
if(!$db->db_query($qry))
echo '<strong>system.tbl_vorlage: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_vorlage PaabgabeUpdatesSammelmail hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM public.tbl_vorlage WHERE vorlage_kurzbz = 'PAAChangesBetSM'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO public.tbl_vorlage (vorlage_kurzbz, bezeichnung, anmerkung, mimetype)
VALUES ('PAAChangesBetSM', 'PAAChangesBetSM', null, 'text/html')
ON CONFLICT (vorlage_kurzbz) DO NOTHING;";
if(!$db->db_query($qry))
echo '<strong>system.tbl_vorlage: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_vorlage PAAChangesBetSM hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM public.tbl_vorlage WHERE vorlage_kurzbz = 'PAAChangesAssSM'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO public.tbl_vorlage (vorlage_kurzbz, bezeichnung, anmerkung, mimetype)
VALUES ('PAAChangesAssSM', 'PAAChangesAssSM', null, 'text/html')
ON CONFLICT (vorlage_kurzbz) DO NOTHING;";
if(!$db->db_query($qry))
echo '<strong>system.tbl_vorlage: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_vorlage PAAChangesAssSM hinzugefuegt";
}
}
if($result = $db->db_query("SELECT 1 FROM public.tbl_vorlage WHERE vorlage_kurzbz = 'QualGateNegativ'"))
{
if($db->db_num_rows($result) === 0)
{
$qry = "INSERT INTO public.tbl_vorlage (vorlage_kurzbz, bezeichnung, anmerkung, mimetype)
VALUES ('QualGateNegativ', 'QualGateNegativ', null, 'text/html')
ON CONFLICT (vorlage_kurzbz) DO NOTHING;";
if(!$db->db_query($qry))
echo '<strong>system.tbl_vorlage: '.$db->db_last_error().'</strong><br>';
else
echo "<br>system.tbl_vorlage QualGateNegativ hinzugefuegt";
}
}
@@ -0,0 +1,77 @@
<?php
/* Copyright (C) 2017 fhcomplete.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Authors: Harald Bamberger <harald.bamberger@technikum-wien.at>,
*
* Beschreibung:
* Dashboard DB Aenderungen
*/
if (! defined('DB_NAME')) exit('No direct script access allowed');
$corewidgetssql = 'select tw.* from dashboard.tbl_widget tw where regexp_match((setup->\'file\')::text, \'^"DashboardWidget\') is not NULL';
if (($rescore = $db->db_query($corewidgetssql)))
{
if ($db->db_num_rows($rescore) > 0)
{
$coreqry = <<<EOCOREQRY
update
dashboard.tbl_widget
set
setup = jsonb_set(setup, '{file}', regexp_replace((setup->'file')::text, '^"DashboardWidget', '"public/js/components/DashboardWidget')::jsonb)
where
regexp_match((setup->'file')::text, '^"DashboardWidget') is not NULL;
EOCOREQRY;
if (!$db->db_query($coreqry))
{
echo '<strong>Dashboard Core Widgets Paths Update: '.$db->db_last_error().'</strong><br>';
}
else
{
echo '<br>Dashboard Core Widgets Paths updated.';
}
}
}
$extwidgetssql = 'select tw.* from dashboard.tbl_widget tw where regexp_match((setup->\'file\')::text, \'^"../../\') is not NULL';
if (($resext = $db->db_query($extwidgetssql)))
{
if ($db->db_num_rows($resext) > 0)
{
$extqry = <<<EOEXTQRY
update
dashboard.tbl_widget
set
setup = jsonb_set(setup, '{file}', regexp_replace((setup->'file')::text, '^"../../', '"public/')::jsonb)
where
regexp_match((setup->'file')::text, '^"../../') is not NULL;
EOEXTQRY;
if (!$db->db_query($extqry))
{
echo '<strong>Dashboard Extensions Widgets Paths Update: '.$db->db_last_error().'</strong><br>';
}
else
{
echo '<br>Dashboard Extensions Widgets Paths updated.';
}
}
}
@@ -0,0 +1,28 @@
<?php
if (! defined('DB_NAME')) exit('No direct script access allowed');
if ($result = $db->db_query("SELECT * FROM pg_class WHERE relname='idx_tbl_msg_message_person_id'"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "CREATE INDEX idx_tbl_msg_message_person_id ON public.tbl_msg_message USING btree (person_id)";
if (! $db->db_query($qry))
echo '<strong>idx_tbl_msg_message_person_id: ' . $db->db_last_error() . '</strong><br>';
else
echo 'Index idx_tbl_msg_message_person_id angelegt<br>';
}
}
if ($result = $db->db_query("SELECT * FROM pg_class WHERE relname='idx_tbl_msg_recipient_person_id'"))
{
if ($db->db_num_rows($result) == 0)
{
$qry = "CREATE INDEX idx_tbl_msg_recipient_person_id ON public.tbl_msg_recipient USING btree (person_id)";
if (! $db->db_query($qry))
echo '<strong>idx_tbl_msg_recipient_person_id: ' . $db->db_last_error() . '</strong><br>';
else
echo 'Index idx_tbl_msg_recipient_person_id angelegt<br>';
}
}
+2687 -23
View File
File diff suppressed because it is too large Load Diff
+23 -9
View File
@@ -390,30 +390,34 @@ if($result = $db->db_query($qry))
$error_log.=(!empty($error_log)?', ':'')."Matrikelnummer ('".trim($row->matr_nr)."') ist nicht 8 Zeichen lang";
}
//SVNR mu߸ 10-stellig sein
/* Alle SVNR Checks entfernt
if($row->svnr!='' && $row->svnr!=null && mb_strlen(trim($row->svnr))!=10)
{
$error_log.=(!empty($error_log)?', ':'')."SVNR ('".trim($row->svnr)."') ist nicht 10 Zeichen lang";
}
}*/
//Ersatzkennzeichen muß 10-stellig sein
if($row->ersatzkennzeichen!='' && $row->ersatzkennzeichen!=null && mb_strlen(trim($row->ersatzkennzeichen))!=10)
{
$error_log.=(!empty($error_log)?', ':'')."Ersatzkennzeichen ('".trim($row->ersatzkennzeichen)."') ist nicht 10 Zeichen lang";
}
//Vergleich der letzten 6 Stellen der SVNR mit Geburtsdatum - ausser bei 01.01. und 01.07.
/* Alle SVNR Checks entfernt
if($row->svnr!='' && $row->svnr!=null && substr($row->svnr,4,6)!=$row->vdat && substr($row->vdat,0,4)!='0101' && substr($row->vdat,0,4)!='0107')
{
$error_log_hinweis.=(!empty($error_log_hinweis)?', ':'')."SVNR ('".$row->svnr."') enth&auml;lt Geburtsdatum (".$row->gebdatum.") nicht";
}
}*/
//Vergleich der letzten 6 Stellen des Ersatzkennzeichen mit Geburtsdatum
if($row->ersatzkennzeichen!='' && $row->ersatzkennzeichen!=null && substr($row->ersatzkennzeichen,4,6)!=$row->vdat)
{
$error_log.=(!empty($error_log)?', ':'')."Ersatzkennzeichen ('".$row->ersatzkennzeichen."') enth&auml;lt Geburtsdatum (".$row->gebdatum.") nicht";
}
// Wenn SVNR fehlt, darf Ersatzkennzeichen nicht fehlen (und umgekehrt)
/* Alle SVNR Checks entfernt
if(($row->svnr=='' || $row->svnr==null)&&($row->ersatzkennzeichen=='' || $row->ersatzkennzeichen==null))
{
$error_log.=(!empty($error_log)?', ':'')."SVNR ('".$row->svnr."') bzw. ErsKz ('".$row->ersatzkennzeichen."') fehlt";
}
}*/
if($row->staatsbuergerschaft=='' || $row->staatsbuergerschaft==null)
{
$error_log.=(!empty($error_log)?', ':'')."Staatsb&uuml;rgerschaft ('".$row->staatsbuergerschaft."')";
@@ -714,7 +718,7 @@ if($result = $db->db_query($qry))
$qry_ap="SELECT * FROM lehre.tbl_abschlusspruefung WHERE student_uid=".$db->db_add_param($row->student_uid)." AND abschlussbeurteilung_kurzbz!='nicht' AND abschlussbeurteilung_kurzbz IS NOT NULL";
if($result_ap = $db->db_query($qry_ap))
{
$ap=0;
$ap = array();
while($row_ap = $db->db_fetch_object($result_ap))
{
if($row_ap->datum=='' || $row_ap->datum==null)
@@ -725,12 +729,19 @@ if($result = $db->db_query($qry))
{
$error_log.=(!empty($error_log)?', ':'')."Datum der Sponsion ('".$row_ap->sponsion."')";
}
$ap++;
if (!isset($ap[$row_ap->pruefungstyp_kurzbz]))
{
$ap[$row_ap->pruefungstyp_kurzbz] = 0;
}
$ap[$row_ap->pruefungstyp_kurzbz]++;
$sponsion=$row_ap->sponsion;
}
if($ap!=1)
foreach ($ap as $typ => $count)
{
$error_log.=(!empty($error_log)?', ':'').$ap." bestandene Abschlusspr&uuml;fungen";
if ($count > 1)
{
$error_log.=(!empty($error_log)?', ':'').$count." bestandene Abschlusspr&uuml;fungen desselben Typs";
}
}
}
}
@@ -815,13 +826,16 @@ if($result = $db->db_query($qry))
<Vorname>".$row->vorname."</Vorname>
<Familienname>".$row->nachname."</Familienname>";
/* Alle SVNR Checks entfernt
if($row->svnr!='')
{
$datei.="
<SVNR>".$row->svnr."</SVNR>";
}
}*/
// Ersatzkennzeichen nur inkludieren wenn svnr nicht gesetzt
if($row->ersatzkennzeichen!='' && $row->svnr == null)
// Alle SVNR Checks entfernt
// if($row->ersatzkennzeichen!='' && $row->svnr == null)
if($row->ersatzkennzeichen!='')
{
$datei.="
<ErsKz>".$row->ersatzkennzeichen."</ErsKz>";
+11 -5
View File
@@ -930,26 +930,29 @@ function GenerateXMLStudentBlock($row)
{
$error_log.=(!empty($error_log)?', ':'')."Matrikelnummer ('".trim($row->matr_nr)."') ist nicht 8 Zeichen lang";
}
/* Alle SVNR Checks entfernt
if($row->svnr!='' && $row->svnr!=null && mb_strlen(trim($row->svnr))!=10)
{
$error_log.=(!empty($error_log)?', ':'')."SVNR ('".trim($row->svnr)."') ist nicht 10 Zeichen lang";
}
}*/
if($row->ersatzkennzeichen!='' && $row->ersatzkennzeichen!=null && mb_strlen(trim($row->ersatzkennzeichen))!=10)
{
$error_log.=(!empty($error_log)?', ':'')."Ersatzkennzeichen ('".trim($row->ersatzkennzeichen)."') ist nicht 10 Zeichen lang";
}
/* Alle SVNR Checks entfernt
if($row->svnr!='' && $row->svnr!=null && substr($row->svnr,4,6)!=$row->vdat && substr($row->vdat,0,4)!='0101' && substr($row->vdat,0,4)!='0107')
{
$error_log_hinweis.=(!empty($error_log_hinweis)?', ':'')."SVNR ('".$row->svnr."') enth&auml;lt Geburtsdatum (".$datum_obj->formatDatum($row->gebdatum,'d.m.Y').") nicht (Nicht BIS-Relevant)";
}
}*/
if($row->ersatzkennzeichen!='' && $row->ersatzkennzeichen!=null && substr($row->ersatzkennzeichen,4,6)!=$row->vdat)
{
$error_log.=(!empty($error_log)?', ':'')."Ersatzkennzeichen ('".$row->ersatzkennzeichen."') enth&auml;lt Geburtsdatum (".$datum_obj->formatDatum($row->gebdatum,'d.m.Y').") nicht";
}
/* Alle SVNR Checks entfernt
if(($row->svnr=='' || $row->svnr==null)&&($row->ersatzkennzeichen=='' || $row->ersatzkennzeichen==null))
{
$error_log.=(!empty($error_log)?', ':'')."SVNR ('".$row->svnr."') bzw. ErsKz ('".$row->ersatzkennzeichen."') fehlt";
}
}*/
if($row->staatsbuergerschaft=='' || $row->staatsbuergerschaft==null)
{
$error_log.=(!empty($error_log)?', ':'')."Staatsb&uuml;rgerschaft ('".$row->staatsbuergerschaft."')";
@@ -1510,14 +1513,17 @@ function GenerateXMLStudentBlock($row)
<Vorname>" . $row->vorname . "</Vorname>
<Familienname>" . $row->nachname . "</Familienname>";
/* Alle SVNR Checks entfernt
if ($row->svnr != '')
{
$datei .= "
<SVNR>" . $row->svnr . "</SVNR>";
}
}*/
// Ersatzkennzeichen nur inkludieren wenn svnr nicht gesetzt
if ($row->ersatzkennzeichen != '' && $row->svnr == null)
// Alle SVNR Checks entfernt
// if ($row->ersatzkennzeichen != '' && $row->svnr == null)
if ($row->ersatzkennzeichen != '')
{
$datei .= "
<ErsKz>" . $row->ersatzkennzeichen . "</ErsKz>";
+3 -3
View File
@@ -80,9 +80,9 @@ $trenner->loadVariables($getuid);
$sql_query = "SELECT *,
(SELECT orgform_kurzbz
FROM tbl_prestudentstatus
WHERE prestudent_id=(Select prestudent_id from tbl_student where student_uid=xy.uid limit 1)
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1
FROM tbl_prestudentstatus
WHERE prestudent_id=(Select prestudent_id from tbl_student where student_uid=xy.uid limit 1)
ORDER BY datum DESC, insertamum DESC, ext_id DESC LIMIT 1
) as organisationsform
FROM (SELECT DISTINCT ON(tbl_projektarbeit.projektarbeit_id) public.tbl_studiengang.bezeichnung as stgbez,tbl_projekttyp.bezeichnung AS prjbez,* FROM lehre.tbl_projektarbeit
LEFT JOIN public.tbl_benutzer on(uid=student_uid)