Merge branch 'master' into fradrive/frontend

This commit is contained in:
Sarah Vaupel 2022-12-15 18:51:25 +01:00
commit a7ed5d637b
474 changed files with 2001 additions and 12404 deletions

View File

@ -2,6 +2,45 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [27.0.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v26.6.6...v27.0.0) (2022-12-14)
### ⚠ BREAKING CHANGES
* remove applications and allocations
### Features
* **add-users:** add page-action to add users from TUsersR ([9c62c9e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9c62c9ee8ca365ceea206c696d3596e46927391c))
* **add-users:** connect confirmation form with handler ([c013ae9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c013ae9efcd7df812b26ae32ce04ba3da4e6aef4))
* **add-users:** correctly add users and reroute ([fecc752](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/fecc752d6ce3aa952efabdac2ff17064f4091050))
* **add-users:** more page-actions for convenience ([a882a3c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a882a3c0d03ffc57902cbec779621fb58617ed3b))
* **avs:** add page-action and form handler for registering avs participants ([747d619](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/747d6198c4efdafab009012dc46ed65b02303a38))
* **avs:** register course participants for day groups per default ([64d3ceb](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/64d3ceb56d4a7ce09d7760c7452f48e12b182070))
* **course-users:** fuse avs register form with CAddUserR ([4a00907](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/4a00907bdad26208329080ad87fe52daa33775ff))
* **course-users:** register avs-upserted users ([cba73bf](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cba73bf2ca6825e9d4d00e51440354aba4cf57f0))
* **course-users:** set new tutorials to Schulung ([69de448](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/69de44893c9e37a809cde350d404f60a14e5052b))
* **tutorial-users:** replace study-fields column with qualifications column ([9850e1d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9850e1dd88a5371abe67fd5fb69458d7f52ea8e8))
* **tutorial-users:** table action for granting qualifications ([fa0caba](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/fa0caba55d05f080f5ed98b0b83dde3c6cebe7b7))
* **users-add:** add error message for users not found in avs ([e273c60](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e273c60a2325f75033d2393725ce7a25368821bf))
* **users-add:** redirect to different routes depending on tutorial ([93c6853](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/93c6853b082a5d2195bb55cbf7b792d2f4307254))
* **users-add:** upsert tutorial participants ([662445e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/662445e8cc92cf9f5815851d7e9d0b559cef289e))
### Bug Fixes
* **add-users:** fix and refactor confirm post param handling ([727d78c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/727d78cabc01e9f520b7140336bdacebc6188e2b))
* **add-users:** fix confirm secret field decoding ([57c9535](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/57c9535733b9ca2888a01d940a4a8888ca342c97))
* **add-users:** fix typo in message ([5e02c99](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5e02c99c44783672a66f2562bc92d14287b0ff48))
* **build:** accepting linter suggestions ([d25dd64](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d25dd64eec0a75b8d0e53795341088835f34fd85))
* **routes:** remove redundant auth tag ([5ef36f1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5ef36f1d1c0bd92773bbb58af417bae8307a3610))
* **users-add:** upsert tutorial only if users not empty ([e65d388](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e65d38898e78dffa3dedf89292bf28e39a2ce3cf))
* remove applications and allocations ([66b4cf8](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/66b4cf8542b1e8c16346861ceed2833d9a56f35f))
## [26.6.6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v26.6.5...v26.6.6) (2022-12-12)
## [26.6.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v26.6.4...v26.6.5) (2022-12-05) ## [26.6.5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v26.6.4...v26.6.5) (2022-12-05)
## [26.6.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v26.6.3...v26.6.4) (2022-12-02) ## [26.6.4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v26.6.3...v26.6.4) (2022-12-02)

View File

@ -276,16 +276,8 @@ user-defaults:
show-sex: false show-sex: false
exam-office-get-synced: true exam-office-get-synced: true
exam-office-get-labels: true exam-office-get-labels: true
prefers-postal: true
# During central allocations lecturer-given ratings of applications (as
# ExamGrades) are combined with a central priority.
# This encodes the weight of the lecturer ratings on the same scale as the
# centrally supplied priorities.
allocation-grade-scale: 25
# This encodes, as a proportion of the number of places, how many
# ordinal places lecturer ratings may move students up or down when
# central priorities are supplied as ordered list.
allocation-grade-ordinal-proportion: 0.075
instance-id: "_env:INSTANCE_ID:instance" instance-id: "_env:INSTANCE_ID:instance"
ribbon: "_env:RIBBON:" ribbon: "_env:RIBBON:"

View File

@ -4,4 +4,5 @@
AmbiguousButtons: Mehrere Submit-Buttons aktiv AmbiguousButtons: Mehrere Submit-Buttons aktiv
WrongButtonValue: Submit-Button hat falschen Wert WrongButtonValue: Submit-Button hat falschen Wert
MultipleButtonValues: Submit-Button hat mehrere Werte MultipleButtonValues: Submit-Button hat mehrere Werte
BtnFormOutdated: Knopfdruck verworfen wegen zwischenzeitlicher Datenänderungen

View File

@ -5,3 +5,4 @@
AmbiguousButtons: Multiple active submit buttons AmbiguousButtons: Multiple active submit buttons
WrongButtonValue: Submit button has wrong value WrongButtonValue: Submit button has wrong value
MultipleButtonValues: Submit button has multiple values MultipleButtonValues: Submit button has multiple values
BtnFormOutdated: Button ignored due to interim data changes

View File

@ -8,5 +8,4 @@ FAQCampusCantLogin: Ich kann mich mit meiner Fraport AG Kennung (Büko-Login) ni
FAQCourseCorrectorsTutors: Wie kann ich Tutoren oder Korrektoren für meinen Kurs konfigurieren? FAQCourseCorrectorsTutors: Wie kann ich Tutoren oder Korrektoren für meinen Kurs konfigurieren?
FAQNotLecturerHowToCreateCourses: Wie kann ich einen neuen Kurs anlegen? FAQNotLecturerHowToCreateCourses: Wie kann ich einen neuen Kurs anlegen?
FAQExamPoints: Warum kann ich bei meiner Klausur keine Punkte eintragen? FAQExamPoints: Warum kann ich bei meiner Klausur keine Punkte eintragen?
FAQInvalidCredentialsAdAccountDisabled: Ich kann mich nicht anmelden und bekomme die Meldung „Benutzereintrag gesperrt“ FAQInvalidCredentialsAdAccountDisabled: Ich kann mich nicht anmelden und bekomme die Meldung „Benutzereintrag gesperrt“
FAQAllocationNoPlaces: Ich habe über eine Zentralanmeldung keine Plätze/nicht die Plätze, die ich möchte, erhalten

View File

@ -8,5 +8,4 @@ FAQCampusCantLogin: I can't log in using my Fraport AG credentials (Büko login)
FAQCourseCorrectorsTutors: How can I add tutors or correctors to my course? FAQCourseCorrectorsTutors: How can I add tutors or correctors to my course?
FAQNotLecturerHowToCreateCourses: How can I create new courses? FAQNotLecturerHowToCreateCourses: How can I create new courses?
FAQExamPoints: Why can't I enter achievements for my exam as points? FAQExamPoints: Why can't I enter achievements for my exam as points?
FAQInvalidCredentialsAdAccountDisabled: I can't log in and am instead given the message “Account disabled” FAQInvalidCredentialsAdAccountDisabled: I can't log in and am instead given the message “Account disabled”
FAQAllocationNoPlaces: I did not receive any places/the places I wanted from a central allocation

View File

@ -79,7 +79,7 @@ StudyFeatureInferenceNoNameConflicts: Keine Konflikte beobachtet
StudyFeatureInferenceNameConflictsHeading: Studiengangseinträge mit beobachteten Konflikten StudyFeatureInferenceNameConflictsHeading: Studiengangseinträge mit beobachteten Konflikten
AdminHeading !ident-ok: Administration AdminHeading !ident-ok: Administration
AdminPageEmpty: Diese Seite soll eine Übersichtsseite für Administrator:innen werden. Aktuell finden sich hier nur Links zu wichtigen Administrator-Funktionalitäten.
BearerTokenImpersonate: Auftreten als BearerTokenImpersonate: Auftreten als
BearerTokenImpersonateNone: Keine Änderung BearerTokenImpersonateNone: Keine Änderung
BearerTokenImpersonateSingle: Einzelner Benutzer/Einzelne Benutzerin BearerTokenImpersonateSingle: Einzelner Benutzer/Einzelne Benutzerin
@ -94,3 +94,23 @@ BearerTokenArchiveName !ident-ok: tokens.zip
TestDownloadDirect: Direkte Generierung TestDownloadDirect: Direkte Generierung
TestDownloadInTransaction: Generierung während Datenbank-Transaktion TestDownloadInTransaction: Generierung während Datenbank-Transaktion
TestDownloadFromDatabase: Generierung während Download aus Datenbank TestDownloadFromDatabase: Generierung während Download aus Datenbank
ProblemsHeading: Problemübersicht
ProblemsHeadingDrivers: Fahrberechtigungen
ProblemsAvsProblem: Synchronisation mit AVS/MoBaKo komplett fehlgeschlagen
ProblemsDriverSynch n@Int: #{tshow n} Diskrepanzen zwischen AVS und FRADrive
ProblemsDriverSynch0: Alle Sperrungen von Fahrberechtigungen sind im AVS eingetragen
ProblemsDriverSynch1: Alle gültigen Vorfeld-Fahrberechtigungen 'F' sind im AVS eingetragen
ProblemsDriverSynch2: Alle gültigen Rollfeld-Fahrberechtigungen 'R' sind im AVS eingetragen
ProblemsRDriversHaveFs: Alle Inhaber einer Rollfeld-Fahrberechtigung besitzen auch eine gültige Vorfeld-Fahrberechtigung
ProblemsDriversHaveAvsIds: Alle Inhaber einer Fahrberechtigung konnten einer AVS Identifikationsnummer zugeordnet werden
ProblemsHeadingUsers: Allgemein
ProblemsUsersAreReachable: Für alle Benutzer ist eine E-Mail oder postalische Adresse bekannt
ProblemsNoStalePrintJobs n@Integer: Alle Briefversandaufträge der vergangenen #{show n} Tage wurden von der Druckerei bestätigt
ProblemsUnreachableHeading: Unerreichbare Benutzer
ProblemsUnreachableBody: Benutzer ohne E-Mail oder Postadresse, welche z.B. bei ablaufenden Berechtigungen nicht benachrichtigt werden können:
ProblemsRWithoutFHeading: Fahrer mit R ohne F
ProblemsRWithoutFBody: Diese Fahrer sind wegen einer ungültigen Vorfeld-Fahrberechtigung komplett gesperrt, obwohl eine gültige Rollfeld-Fahrberechtigung besteht:
ProblemsNoAvsIdHeading: Fahrer ohne AVS-Id
ProblemsNoAvsIdBody: Fahrer mit gültiger Fahrberechtigung in FRADrive, welche trotzdem nicht fahren dürfen, da die Fahrberechtigung aufgrund einer unbekannten AVS Id nicht an die Ausweisstelle übermittelt werden konnte:
ProblemsAvsSynchHeading: Synchronisation AVS Fahrberechtigungen

View File

@ -79,7 +79,6 @@ StudyFeatureInferenceNoNameConflicts: No observed conflicts
StudyFeatureInferenceNameConflictsHeading: Fields of study with observed conflicts StudyFeatureInferenceNameConflictsHeading: Fields of study with observed conflicts
AdminHeading: Administration AdminHeading: Administration
AdminPageEmpty: This page shall provide an overview for administrators in the future. For now there are only links to important administrator-functions.
BearerTokenImpersonate: Impersonate BearerTokenImpersonate: Impersonate
BearerTokenImpersonateNone: No one BearerTokenImpersonateNone: No one
@ -95,3 +94,23 @@ BearerTokenArchiveName: tokens.zip
TestDownloadDirect: Direct generation TestDownloadDirect: Direct generation
TestDownloadInTransaction: Generate during database transaction TestDownloadInTransaction: Generate during database transaction
TestDownloadFromDatabase: Generate while streaming from database TestDownloadFromDatabase: Generate while streaming from database
ProblemsHeading: Overview Problems
ProblemsHeadingDrivers: Driving Licences
ProblemsAvsProblem: Synchronisation with AVS/MoBaKo failed entirely
ProblemsDriverSynch n: #{tshow n} mismatches between AVS and FRADrive
ProblemsDriverSynch0: All revocations of driving licences were successfully registered with AVS
ProblemsDriverSynch1: All valid apron driving licences 'F' were successfully registered with AVS
ProblemsDriverSynch2: All valid maneuvering area driving licences 'R' were successfully registered with AVS
ProblemsRDriversHaveFs: All driving licence 'R' holders also have a valid 'F' licence
ProblemsDriversHaveAvsIds: All driving licence holder could be matched with their AVS id
ProblemsHeadingUsers: Miscellaneous
ProblemsUsersAreReachable: Either Email or postal address is known for all users
ProblemsNoStalePrintJobs n: All requests for letter mailing within the last #{show n} days were acknowledged as printed by the airport printing center
ProblemsUnreachableHeading: Unreachable Users
ProblemsUnreachableBody: Users without Email nor postal address, who thus cannot be notified about expiring qualifications:
ProblemsRWithoutFHeading: Drivers having 'R' but not 'F'
ProblemsRWithoutFBody: Drivers without apron driving licence are prohibited from driving, even if they own a valid maneuvering driving licence:
ProblemsNoAvsIdHeading: Drivers without AVS id
ProblemsNoAvsIdBody: Drivers having a valid apron driving licence within FRADrive only, but who may not drive since a missing AVS id prevents communication of the driving licence to AVS:
ProblemsAvsSynchHeading: Synchronisation AVS Driving Licences

View File

@ -30,10 +30,8 @@ UnauthorizedSystemExamOffice: Sie sind nicht mit systemweiter Prüfungsverwaltun
UnauthorizedSystemPrinter: Sie sind nicht mit systemweitem Druck und Briefversand beauftragt. UnauthorizedSystemPrinter: Sie sind nicht mit systemweitem Druck und Briefversand beauftragt.
UnauthorizedExternalExamExamOffice: Es existieren keine Prüfungsergebnisse für Nutzer:innen, für die Sie mit der Prüfungsverwaltung beauftragt sind. UnauthorizedExternalExamExamOffice: Es existieren keine Prüfungsergebnisse für Nutzer:innen, für die Sie mit der Prüfungsverwaltung beauftragt sind.
UnauthorizedEvaluation: Sie sind nicht mit der Kursumfragenverwaltung beauftragt. UnauthorizedEvaluation: Sie sind nicht mit der Kursumfragenverwaltung beauftragt.
UnauthorizedAllocationAdmin: Sie sind nicht mit der Administration von Zentralanmeldungen beauftragt.
UnauthorizedSchoolLecturer: Sie sind nicht als Veranstalter:in für dieses Institut eingetragen. UnauthorizedSchoolLecturer: Sie sind nicht als Veranstalter:in für dieses Institut eingetragen.
UnauthorizedLecturer: Sie sind nicht als Veranstalter:in für diese Veranstaltung eingetragen. UnauthorizedLecturer: Sie sind nicht als Veranstalter:in für diese Veranstaltung eingetragen.
UnauthorizedAllocationLecturer: Sie sind nicht als Veranstalter:in für eine Veranstaltung dieser Zentralanmeldung eingetragen.
UnauthorizedCorrector: Sie sind nicht als Korrektor:in für diese Veranstaltung eingetragen. UnauthorizedCorrector: Sie sind nicht als Korrektor:in für diese Veranstaltung eingetragen.
UnauthorizedSheetCorrector: Sie sind nicht als Korrektor:in für dieses Übungsblatt eingetragen. UnauthorizedSheetCorrector: Sie sind nicht als Korrektor:in für dieses Übungsblatt eingetragen.
UnauthorizedExamCorrector: Sie sind nicht als Korrektor:in für diese Prüfung eingetragen. UnauthorizedExamCorrector: Sie sind nicht als Korrektor:in für diese Prüfung eingetragen.
@ -41,19 +39,14 @@ UnauthorizedCorrectorAny: Sie sind nicht als Korrektor:in für eine Veranstaltun
UnauthorizedRegistered: Sie sind nicht als Teilnehmer:in für diese Veranstaltung registriert. UnauthorizedRegistered: Sie sind nicht als Teilnehmer:in für diese Veranstaltung registriert.
UnauthorizedRegisteredExam: Sie sind nicht als Teilnehmer:in für diese Prüfung registriert. UnauthorizedRegisteredExam: Sie sind nicht als Teilnehmer:in für diese Prüfung registriert.
UnauthorizedRegisteredAnyExam: Sie sind nicht als Teilnehmer:in für eine Prüfung registriert. UnauthorizedRegisteredAnyExam: Sie sind nicht als Teilnehmer:in für eine Prüfung registriert.
UnauthorizedAllocationRegistered: Sie sind nicht als Teilnehmer:in für diese Zentralanmeldung registriert.
UnauthorizedExamResult: Sie haben keine Ergebnisse in dieser Prüfung. UnauthorizedExamResult: Sie haben keine Ergebnisse in dieser Prüfung.
UnauthorizedExamOccurrenceRegistration: Anmeldung zur Prüfung erfolgt nicht inkl. Raum/Termin. UnauthorizedExamOccurrenceRegistration: Anmeldung zur Prüfung erfolgt nicht inkl. Raum/Termin.
UnauthorizedExternalExamResult: Sie haben keine Ergebnisse in dieser Prüfung. UnauthorizedExternalExamResult: Sie haben keine Ergebnisse in dieser Prüfung.
UnauthorizedParticipant: Angegebener Benutzer/Angegebene Benutzerin ist nicht als Teilnehmer:in dieser Veranstaltung registriert. UnauthorizedParticipant: Angegebener Benutzer/Angegebene Benutzerin ist nicht als Teilnehmer:in dieser Veranstaltung registriert.
UnauthorizedParticipantSelf: Sie sind nicht Teilnehmer:in dieser Veranstaltung. UnauthorizedParticipantSelf: Sie sind nicht Teilnehmer:in dieser Veranstaltung.
UnauthorizedApplicant: Angegebener Benutzer/Angegebene Benutzerin hat sich nicht für diese Veranstaltung beworben.
UnauthorizedApplicantSelf: Sie sind nicht Bewerber:in für diese Veranstaltung.
UnauthorizedCourseTime: Dieser Kurs ist momentan nicht freigegeben. UnauthorizedCourseTime: Dieser Kurs ist momentan nicht freigegeben.
UnauthorizedCourseRegistrationTime: Dieser Kurs erlaubt momentan keine Anmeldungen. UnauthorizedCourseRegistrationTime: Dieser Kurs erlaubt momentan keine Anmeldungen.
UnauthorizedAllocationRegisterTime: Diese Zentralanmeldung erlaubt momentan keine Bewerbungen.
UnauthorizedSheetTime: Dieses Übungsblatt ist momentan nicht freigegeben. UnauthorizedSheetTime: Dieses Übungsblatt ist momentan nicht freigegeben.
UnauthorizedApplicationTime: Diese Bewerbung ist momentan nicht freigegeben.
UnauthorizedMaterialTime: Dieses Material ist momentan nicht freigegeben. UnauthorizedMaterialTime: Dieses Material ist momentan nicht freigegeben.
UnauthorizedTutorialTime: Dieses Tutorium erlaubt momentan keine Anmeldungen. UnauthorizedTutorialTime: Dieses Tutorium erlaubt momentan keine Anmeldungen.
UnauthorizedCourseNewsTime: Diese Nachricht ist momentan nicht freigegeben. UnauthorizedCourseNewsTime: Diese Nachricht ist momentan nicht freigegeben.
@ -84,9 +77,6 @@ UnauthorizedExternalExamLecturer: Sie sind nicht als Prüfer:in für diese exter
UnauthorizedSubmissionSubmissionGroup: Sie sind nicht Mitglied in einer der registrierten Abgabegruppen, die an dieser Abgabe beteiligt sind UnauthorizedSubmissionSubmissionGroup: Sie sind nicht Mitglied in einer der registrierten Abgabegruppen, die an dieser Abgabe beteiligt sind
UnauthorizedSheetSubmissionGroup: Sie sind nicht Mitglied in einer registrierten Abgabegruppe UnauthorizedSheetSubmissionGroup: Sie sind nicht Mitglied in einer registrierten Abgabegruppe
UnauthorizedAllocatedCourseRegister: Direkte Anmeldungen zum Kurs sind aufgrund einer Zentralanmeldung aktuell nicht gestattet
UnauthorizedAllocatedCourseDeregister: Abmeldungen vom Kurs sind aufgrund einer Zentralanmeldung aktuell nicht gestattet
UnauthorizedAllocatedCourseDelete: Kurse, die an einer Zentralanmeldung teilnehmen, dürfen nicht gelöscht werden
UnauthorizedWorkflowInitiate: Sie dürfen keinen neuen laufenden Workflow initiieren UnauthorizedWorkflowInitiate: Sie dürfen keinen neuen laufenden Workflow initiieren
UnauthorizedWorkflowWrite: Sie dürfen aktuell keinen Zustandsübergang im Workflow auslösen UnauthorizedWorkflowWrite: Sie dürfen aktuell keinen Zustandsübergang im Workflow auslösen
UnauthorizedWorkflowRead: Der Workflow enthält aktuell keine Zustände oder Daten die Sie einsehen dürfen UnauthorizedWorkflowRead: Der Workflow enthält aktuell keine Zustände oder Daten die Sie einsehen dürfen

View File

@ -25,7 +25,6 @@ UnauthorizedSchoolAdmin: You are no administrator for this department.
UnauthorizedAdminEscalation: You aren't an administrator for all departments for which this user is an administrator. UnauthorizedAdminEscalation: You aren't an administrator for all departments for which this user is an administrator.
UnauthorizedExamOffice: You are not part of an exam office. UnauthorizedExamOffice: You are not part of an exam office.
UnauthorizedEvaluation: You are not charged with course evaluation. UnauthorizedEvaluation: You are not charged with course evaluation.
UnauthorizedAllocationAdmin: You are not charged with the administration of central allocations.
UnauthorizedExamExamOffice: You are not part of the appropriate exam office for any of the participants of this exam. UnauthorizedExamExamOffice: You are not part of the appropriate exam office for any of the participants of this exam.
UnauthorizedSchoolExamOffice: You are not part of an exam office for this school. UnauthorizedSchoolExamOffice: You are not part of an exam office for this school.
UnauthorizedSystemExamOffice: You are not charged with system wide exam administration. UnauthorizedSystemExamOffice: You are not charged with system wide exam administration.
@ -33,7 +32,6 @@ UnauthorizedSystemPrinter: You are not charged with system wide letter printing.
UnauthorizedExternalExamExamOffice: You are not part of the appropriate exam office for any of the participants of this exam. UnauthorizedExternalExamExamOffice: You are not part of the appropriate exam office for any of the participants of this exam.
UnauthorizedSchoolLecturer: You are no lecturer for this department. UnauthorizedSchoolLecturer: You are no lecturer for this department.
UnauthorizedLecturer: You are no administrator for this course. UnauthorizedLecturer: You are no administrator for this course.
UnauthorizedAllocationLecturer: You are no administrator for any of the courses of this central allocation.
UnauthorizedCorrector: You are no sheet corrector for this course. UnauthorizedCorrector: You are no sheet corrector for this course.
UnauthorizedSheetCorrector: You are no corrector for this sheet. UnauthorizedSheetCorrector: You are no corrector for this sheet.
UnauthorizedExamCorrector: You are no corrector for this exam. UnauthorizedExamCorrector: You are no corrector for this exam.
@ -41,19 +39,14 @@ UnauthorizedCorrectorAny: You are no corrector for any course.
UnauthorizedRegistered: You are no participant in this course. UnauthorizedRegistered: You are no participant in this course.
UnauthorizedRegisteredExam: You are not registered for this exam. UnauthorizedRegisteredExam: You are not registered for this exam.
UnauthorizedRegisteredAnyExam: You are not registered for an exam. UnauthorizedRegisteredAnyExam: You are not registered for an exam.
UnauthorizedAllocationRegistered: You are no participant in this central allocation.
UnauthorizedExamResult: You have no results in this exam. UnauthorizedExamResult: You have no results in this exam.
UnauthorizedExamOccurrenceRegistration: Registration for exam is not done including occurrence/room. UnauthorizedExamOccurrenceRegistration: Registration for exam is not done including occurrence/room.
UnauthorizedExternalExamResult: You have no results in this exam. UnauthorizedExternalExamResult: You have no results in this exam.
UnauthorizedParticipant: The specified user is no participant of this course. UnauthorizedParticipant: The specified user is no participant of this course.
UnauthorizedParticipantSelf: You are no participant of this course. UnauthorizedParticipantSelf: You are no participant of this course.
UnauthorizedApplicant: The specified user is no applicant for this course.
UnauthorizedApplicantSelf: You are no applicant for this course.
UnauthorizedCourseTime: This course is not currently available. UnauthorizedCourseTime: This course is not currently available.
UnauthorizedCourseRegistrationTime: This course does not currently allow enrollment. UnauthorizedCourseRegistrationTime: This course does not currently allow enrollment.
UnauthorizedAllocationRegisterTime: This central allocation does not currently allow applications.
UnauthorizedSheetTime: This sheet is not currently available. UnauthorizedSheetTime: This sheet is not currently available.
UnauthorizedApplicationTime: This allocation is not currently available.
UnauthorizedMaterialTime: This course material is not currently available. UnauthorizedMaterialTime: This course material is not currently available.
UnauthorizedTutorialTime: This tutorial does not currently allow registration. UnauthorizedTutorialTime: This tutorial does not currently allow registration.
UnauthorizedCourseNewsTime: This news item is not currently available. UnauthorizedCourseNewsTime: This news item is not currently available.
@ -84,9 +77,6 @@ UnauthorizedExternalExamLecturer: You are not an associated person for this exte
UnauthorizedSubmissionSubmissionGroup: You are not member in any of the submission groups for this submission UnauthorizedSubmissionSubmissionGroup: You are not member in any of the submission groups for this submission
UnauthorizedSheetSubmissionGroup: You are not member in any submission group UnauthorizedSheetSubmissionGroup: You are not member in any submission group
UnauthorizedAllocatedCourseRegister: Direct enrollment to this course is currently not allowed due to participation in a central allocation
UnauthorizedAllocatedCourseDeregister: Deregistration from this course is currently not allowed due to participation in a central allocation
UnauthorizedAllocatedCourseDelete: Courses that participate in a central allocation may not be deleted
UnauthorizedWorkflowInitiate: You currently may not initiate a new running workflow UnauthorizedWorkflowInitiate: You currently may not initiate a new running workflow
UnauthorizedWorkflowWrite: You are currently not allowed to initiate any state transition within the workflow UnauthorizedWorkflowWrite: You are currently not allowed to initiate any state transition within the workflow
UnauthorizedWorkflowRead: The workflow currently contains no states or data you are permitted to view UnauthorizedWorkflowRead: The workflow currently contains no states or data you are permitted to view

View File

@ -12,4 +12,13 @@ AvsVersionNo: Versionsnummer
AvsQueryEmpty: Bitte mindestens ein Anfragefeld ausfüllen! AvsQueryEmpty: Bitte mindestens ein Anfragefeld ausfüllen!
AvsQueryStatusInvalid t@Text: Nur numerische IDs eingeben, durch Komma getrennt! Erhalten: #{show t} AvsQueryStatusInvalid t@Text: Nur numerische IDs eingeben, durch Komma getrennt! Erhalten: #{show t}
AvsLicence: Fahrberechtigung AvsLicence: Fahrberechtigung
AvsPersonNoNotId: AVS Personennummer dient zur menschlichen Kommunikation mit der Ausweisstelle und darf nicht verwechselt werden mit der maschinell verwendeten AVS Personen Id AvsPersonNoNotId: AVS Personennummer dient zur menschlichen Kommunikation mit der Ausweisstelle und darf nicht verwechselt werden mit der maschinell verwendeten AVS Personen Id
AvsTitleLicenceSynch: Abgleich Fahrberechtigungen zwischen AVS und FRADrive
BtnRevokeAvsLicences: Fahrberechtigungen im AVS sofort entziehen
BtnImportUnknownAvsIds: Daten unbekannter Personen importieren
AvsImportIDs n@Int m@Int: AVS Persondendaten importiert: #{show n}/#{show m}
RevokeUnknownLicencesOk: AVS Fahrberechtigungen unbekannter Fahrer wurden gesperrt
RevokeUnknownLicencesFail: Nicht alle AVS Fahrberechtigungen unbekannter Fahrer konnten entzogen werden, siehe Log für Details
AvsCommunicationError: AVS Schnittstelle lieferte einen unerwarteten Fehler.
LicenseTableChangeAvs: Im AVS ändern
LicenseTableChangeFDrive: In FRADrive ändern

View File

@ -12,4 +12,13 @@ AvsVersionNo: Version number
AvsQueryEmpty: At least one query field must be filled! AvsQueryEmpty: At least one query field must be filled!
AvsQueryStatusInvalid t: Numeric IDs only, comma seperated! #{show t} AvsQueryStatusInvalid t: Numeric IDs only, comma seperated! #{show t}
AvsLicence: Driving Licence AvsLicence: Driving Licence
AvsPersonNoNotId: AVS person number is used in human communication only and must not be mistaken for the AVS personen id used in machine communications AvsPersonNoNotId: AVS person number is used in human communication only and must not be mistaken for the AVS personen id used in machine communications
AvsTitleLicenceSynch: Synchronisation driving licences between AVS and FRADrive
BtnRevokeAvsLicences: Revoke AVS driving licences immediately
BtnImportUnknownAvsIds: Import unknown person data
AvsImportIDs n m: AVS person daten importet: #{show n}/#{show m}
RevokeUnknownLicencesOk: AVS driving licences of unknown drivers revoked
RevokeUnknownLicencesFail: Not all AVS driving licences of unknown drivers could be revoked, see log for details
AvsCommunicationError: AVS interface returned an unexpected error.
LicenseTableChangeAvs: Change in AVS
LicenseTableChangeFDrive: Change within FRADrive

View File

@ -1,265 +0,0 @@
# SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
AllocationAcceptFormDoesNotMatchSession: Das Formular zum Akzeptieren der Vergabe wurde für ein anderes Vergabeergebnis erzeugt, als aktuell in Ihrer Session gespeichert ist.
AllocationAccepted: Zentralvergabe gespeichert.
HeadingAllocationAccept: Platzvergabe akzeptieren
AllocationAddUserUserNotFound: E-Mail Adresse konnte keinem Benutzer zugeordnet werden
AllocationAddUserUser: Benutzer:in
AllocationAddUserUserPlaceholder: E-Mail
AllocationAddUserTotalCoursesLessThanOne: Anzahl angefragter Plätze muss größer null sein
AllocationAddUserTotalCourses: Angefragte Plätze
AllocationAddUserSetPriority: Zentrale Dringlichkeit eintragen?
AllocationAddUserPriority: Zentrale Dringlichkeit
AllocationAddUserApplications: Bewerbungen/Bewertungen
AllocationAddUserTitle termText@Text ssh'@SchoolShorthand allocation@AllocationName: #{termText} - #{ssh'} - #{allocation}: Bewerber:in hinzufügen
AllocationAddUserShortTitle tid@TermId ssh@SchoolId ash@AllocationShorthand: #{tid}-#{ssh}-#{ash}: Bewerber:in hinzufügen
AllocationAddUserUserAdded: Bewerber:in erfolgreich zur Zentralanmeldung hinzugefügt
AllocationAddUserUserExists: Der/Die angegebene Benutzer/Benutzerin ist bereits ein/eine Bewerber/Bewerberin zur Zentralanmeldung
AllocationEditUserUserEdited: Bewerber:in erfolgreich bearbeitet
AllocationEditUserTitle termText@Text ssh@SchoolShorthand ash@AllocationShorthand userDisplayName@Text: #{termText} - #{ssh} - #{ash}, Bewerber:in bearbeiten: #{userDisplayName}
AllocationEditUserShortTitle tid@TermId ssh@SchoolId ash@AllocationShorthand userDisplayName@Text !ident-ok: #{tid}-#{ssh}-#{ash}: #{userDisplayName}
AllocationPriority: Priorität
CourseAllocationCourseAcceptsSubstitutesUntil: Akzeptiert Nachrücker:innen bis
CourseAllocationCourseAcceptsSubstitutesNever: Akzeptiert keine Nachrücker:innen
CourseAllocationApplicationInstructionsApplication: Anweisungen zur Bewerbung
CourseAllocationApplicationTemplateApplication: Bewerbungsvorlage(n)
CourseApplication: Bewerbung
AllocationCoursePriority i@Natural: #{i}. Wahl
AllocationCourseNoApplication: Keine Bewerbung
ApplicationPriority: Priorität
ApplicationVeto !ident-ok: Veto
ApplicationVetoTip: Bewerber:in mit Veto werden garantiert nicht dem Kurs zugeteilt
ApplicationRatingPoints: Bewertung
ApplicationRatingPointsTip: Bewerber:in mit 5.0 werden garantiert nicht dem Kurs zugeteilt
ApplicationRatingComment: Kommentar
ApplicationRatingCommentVisibleTip: Feedback an Bewerber:in
ApplicationRatingCommentInvisibleTip: Dient zunächst nur als Notiz für Kursverwalter:innen
ApplicationRatingSection: Bewertung
ApplicationRatingSectionSelfTip: Sie verfügen über hinreichende Authorisierung um sowohl die Bewerbung als auch ihre Bewertung zu editieren.
CourseApplicationExists: Sie haben sich bereits für diesen Kurs beworben
CourseApplicationCreated csh@CourseShorthand: Erfolgreich zu #{csh} beworben
CourseApplicationInvalidAction: Angegebene Aktion kann nicht durchgeführt werden
CourseApplicationEdited csh@CourseShorthand: Bewerbung zu #{csh} erfolgreich angepasst
CourseApplicationNotEdited csh@CourseShorthand: Bewerbung zu #{csh} hat sich nicht verändert
CourseApplicationRated: Bewertung erfolgreich angepasst
CourseApplicationRatingDeleted: Bewertung erfolgreich entfernt
CourseApplicationDeleted csh@CourseShorthand: Bewerbung zu #{csh} erfolgreich zurückgezogen
AllocationUsersMissingPriorities: Teilnehmer:innen ohne zentrale Dringlichkeit
AllocationUsersMissingPrioritiesTip: Es muss sichergestellt sein, dass keine Teilnehmer:innen unberechtigt aus der Zentralvergabe ausgeschlossen werden, indem ihnen keine zentrale Dringlichkeit zugewiesen wurde.
AllocationUsersMissingPrioritiesOk: Es wurde sichergestellt, dass es für jeden der genannten Benutzer:innen einen zulässigen Grund gibt, warum dieser nicht an der Zentralanmeldung teilnehmen sollte.
AllocationUsersMissingPrioritiesNotOk: Zentralvergabe kann nicht erfolgen, solange nicht allen Teilnehmer:innen, die nicht explizit von der Vergabe ausgeschlossen wurden („Teilnehmer:in ohne zentrale Dringlichkeit”), eine zentrale Dringlichkeit zugewiesen wurde!
AllocationRestrictCourses: Kurse einschränken
AllocationRestrictCoursesTip: Sollen nur Plätze für eine Teilmenge von Kursen zugewiesen werden? So können u.A. Nachrücker:innen verteilt werden. Diese Funktionalität sollte nur verwendet werden, wenn manche Kurse aus zulässigen Gründen ausgeschlossen werden müssen; z.B. weil ein Seminar bereits ein Treffen zur Organisation hatte und nun keine weiteren Teilnehmer:innen mehr akzeptieren kann.
AllocationRestrictCoursesSelection: Kurse
AllocationRestrictCoursesSelectionTip: Teilnehmer:innen werden nur auf die Kurse verteilt, die hier angegeben werden.
AllocationOnlyCompute: Durch Senden dieses Formulars wird zunächst nur eine mögliche Zentralvergabe berechnet und zur Kontrolle temporär gespeichert. Es werden keine Änderungen am Stand der Datenbank vorgenommen oder Benachrichtigungen verschickt.
AllocationComputed: Eine mögliche Zentralvergabe wurde berechnet und in Ihrer Session gespeichert. Es wurden noch keine Änderungen vorgenommen!
HeadingAllocationCompute: Platzvergabe berechnen
HeadingAllocationInfo: Hinweise zum Ablauf einer Zentralanmeldung
AllocationAvailableCourses: Kurse
AllocationAppliedCourses: Bewerbungen
AllocationListTitle: Zentralanmeldungen
AllocationMissingPrioritiesIgnored: Bewerber:innen, für die keine zentrale Priorität angegeben wird, werden bei der Vergabe ignoriert!
AllocationPriorities: Zentrale Dringlichkeiten
AllocationPrioritiesTitle tid@TermId ssh@SchoolId ash@AllocationShorthand: #{tid}-#{ssh}-#{ash}: Zentrale Dringlichkeiten
AllocationPrioritiesFile: CSV-Datei
AllocationPrioritiesSunk num@Int64: Zentrale Prioritäten für #{num} Bewerber erfolgreich hinterlegt
AllocationPrioritiesMissing num@Int64: Für #{num} #{pluralDE num "Bewerber:in" "Bewerber:innen"} ist keine zentrale Priorität hinterlegt, da in der hochgeladenen CSV-Datei die #{pluralDE num "entsprechende Matrikelnummer" "entsprechenden Matrikelnummern"} nicht gefunden #{pluralDE num "wurde" "wurden"}
AllocationPrioritiesMode: Modus
AllocationTotalCoursesNegative: Gewünschte Kursanzahl muss größer null sein
AllocationTotalCourses: Gewünschte Anzahl von Kursen
AllocationTotalCoursesTip: Sie werden im Laufe dieser Zentralanmeldung maximal so vielen Kursen zugeteilt, wie Sie hier angeben
AllocationRegistered: Teilnahme an der Zentralanmeldung erfolgreich registriert
AllocationRegistrationEdited: Einstellungen zur Teilnahme an der Zentralanmeldung erfolgreich angepasst
AllocationTitle termText@Text ssh'@SchoolShorthand allocation@AllocationName !ident-ok: #{termText} - #{ssh'}: #{allocation}
AllocationShortTitle termText@Text ssh'@SchoolShorthand ash@AllocationShorthand !ident-ok: #{termText} - #{ssh'} - #{ash}
AllocationNotificationNewCourse: Benachrichtigung bei neuen Kursen
AllocationNotificationNewCourseTip: Wollen Sie per E-Mail benachrichtigt werden, wenn für diese Zentralanmeldung ein neuer Kurs eingetragen wird? Dies überschreibt die systemweite Einstellung in "Anpassen".
AllocationNotificationNewCourseSuccessForceOn: Sie werden benachrichtigt, wenn ein neuer Kurs eingetragen wird
AllocationNotificationNewCourseSuccessForceOff: Sie werden nicht benachrichtigt, wenn ein neuer Kurs eingetragen wird
AllocationNotificationNewCourseCurrentlyOff: Aktuell würden Sie keine Benachrichtigung erhalten.
AllocationNotificationNewCourseCurrentlyOn: Aktuell würden Sie benachrichtigt werden.
CsvColumnAllocationUserSurname: Nachname(n) des/der Bewerbers/Bewerberin
CsvColumnAllocationUserFirstName: Vorname(n) des/der Bewerbers/Bewerberin
CsvColumnAllocationUserName: Voller Name des/der Bewerbers/Bewerberin
CsvColumnAllocationUserMatriculation: Matrikelnummer des/der Bewerbers/Bewerberin
CsvColumnAllocationUserStudyFeatures: Studiendaten
CsvColumnAllocationUserRequested: Maximale Anzahl von Plätzen, die der Bewerber bereit ist, zu akzeptieren
CsvColumnAllocationUserApplied: Anzahl von Bewerbungen, die der/die Bewerber/Bewerberin eingereicht hat
CsvColumnAllocationUserVetos: Anzahl von Bewerbungen, die von Kursverwalter:innen ein Veto oder eine Note erhalten haben, die äquivalent ist zu "Nicht Bestanden" (5.0)
CsvColumnAllocationUserAssigned: Anzahl von Plätzen, die der/die Bewerber/Bewerberin durch diese Zentralanmeldung bereits erhalten hat
CsvColumnAllocationUserNewAssigned: Anzahl von Plätzen, die der/die Bewerber/Bewerberin, nach Akzeptieren der berechneten Verteilung, zusätzlich erhalten würde
CsvColumnAllocationUserPriority: Zentrale Dringlichkeit des/der Bewerbers/Bewerberin; entweder einzelne Zahl für Sortierungsbasierte Dringlichkeiten (höhere Dringlichkeit entspricht größerer Zahl) oder Komma-separierte Liste von numerischen Dringlichkeiten in eckigen Klammern (z.B. [1, 2, 3])
AllocationUsersCsvName tid@TermId ssh@SchoolId ash@AllocationShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase ash}-bewerber:in
AllocationUserNewMatches: Neue Zuteilungen
AllocationUsers: Bewerber:innen
AllocationUsersTitle tid@TermId ssh@SchoolId ash@AllocationShorthand: #{tid}-#{ssh}-#{ash}: Bewerber:innen
#templates allocation/show/course
AllocationNoApplication: Keine Bewerbung
CourseAllocationCourseParticipants: Teilnehmer:innen
CourseMembersCount n@Int !ident-ok: #{n}
CourseMembersCountLimited n@Int max@Int !ident-ok: #{n}/#{max}
CourseAllocationCourseRatings ratings@Word64 vetos@Word64: #{ratings} #{pluralDE ratings "Bewertung" "Bewertungen"} (#{vetos} #{pluralDE vetos "Veto" "Vetos"})
#templates allocation/accept
ComputedAllocation: Berechnete Vergabe
AllocationUsersCount: Teilnehmer:innen
AllocationCoursesCount: Kurse
AllocationRequestedPlaces: Angefragte Plätze
AllocationOfferedPlaces: Angebotene Plätze
AllocationUnmatchedUsers: Teilnehmer:innen ohne zugeteilte Plätze
AllocationUnmatchedCourses: Kurse ohne zugeteilte Teilnehmer:innen
AllocationTime: Zeitpunkt der Vergabe
AllocationCourseEligible: Berücksichtigt
AllocationMatchedUsers: Neu zugeteilt
#templates allocation/show
AllocationSchool: Institut
AllocationSemester !ident-ok: Semester
AllocationDescription: Beschreibung
AllocationStaffDescription: Beschreibung für Dozierende
AllocationStaffRegisterFrom: Eintragung der Kurse ab
AllocationStaffRegisterTo: Eintragung der Kurse bis
AllocationStaffRegister: Eintragung der Kurse
AllocationStaffAllocationFrom: Bewertung der Bewerbungen ab
AllocationStaffAllocationTo: Bewertung der Bewerbungen bis
AllocationStaffAllocation: Bewertung der Bewerbungen
AllocationRegisterFrom: Bewerbung ab
AllocationRegister: Bewerbung
AllocationRegisterClosed: Die Zentralanmeldung ist aktuell geschlossen.
AllocationRegisterOpensIn difftime@Text: Die Zentralanmeldung öffnet voraussichtlich in #{difftime}
AllocationRegisterByStaff: An- und Abmeldung durch Kursverwalter:innen
AllocationRegisterByStaffFrom: An- und Abmeldung durch Kursverwalter:innen ab
AllocationRegisterByStaffTo: An- und Abmeldung durch Kursverwalter:innen bis
AllocationRegisterByStaffTip: In diesem Zeitraum können Kursverwalter:innen Teilnehmer:innen zu und von ihren Kursen an- und abmelden.
AllocationRegisterByStaffFromTip: Ab diesem Zeitpunkt können Kursverwalter:innen Teilnehmer:innen zu und von ihren Kursen an- und abmelden.
AllocationRegisterByCourse: Direkte An- und Abmeldung
AllocationRegisterByCourseFrom: Direkte An- und Abmeldung ab
AllocationRegisterByCourseFromTip: Frühestens ab diesem Zeitpunkt ist die eigentständige An- und Abmeldung zu und von den Kursen, die an der Zentralanmeldung teilnehmen, möglich. Kontrolle über die genauen Fristen haben die Kursverwalter:innen.
AllocationRegisterTo: Anmeldungen bis
AllocationNextSubstitutesDeadline: Nächster Kurs akzeptiert Nachrücker:innen bis
AllocationNextSubstitutesDeadlineNever: Keine Kurse akzeptieren mehr Nachrücker:innen
AllocationFreeCapacity: Freie Plätze
AllocationOverrideDeregister: Abmeldung von den Kursen nur bis
AllocationParticipation: Teilnahme an der Zentralanmeldung
AllocationParticipationLoginFirst: Um an der Zentralanmeldung teilzunehmen, loggen Sie sich bitte zunächst ein.
AllocationNotificationLoginFirst: Um Ihre Benachrichtigungseinstellungen zu ändern, loggen Sie sich bitte zunächst ein.
AllocationCourses: Kurse dieser Zentralanmeldung
AllocationPriorityTip: Kurse, denen Sie eine höhere Priorität zuteilen, werden bei der Platzvergabe präferiert.
AllocationPriorityRelative: Die absoluten Prioritäts-Werte sind bedeutungslos, es wird nur jeweils betrachtet ob ein Kurs höhere Priorität hat als ein anderer.
ApplicationEditTip: Während des Bewerbungszeitraums können eigene Bewerbungen beliebig angepasst und auch wieder zurückgezogen werden.
AllocationNumCoursesAvailableApplied available@Int applied@Int: Sie haben sich bisher für #{applied}/#{available} #{pluralDE applied "Kurs" "Kursen"} beworben
AllocationCourseRestrictionDontRestrict: Nicht einschränken
AllocationCourseRestrictionSubstitutes: Kurse, die aktuell Nachrücker akzeptieren
AllocationCourseRestrictionCustom: Benutzerdefiniert
AllocationName !ident-ok: Name
Allocation: Zentralanmeldung
AllocationActive: Aktiv
AllocationUsersApplied: Bewerbungen
AllocationUsersAssigned: Zuweisungen
AllocationUsersVetoed !ident-ok: Vetos
AllocationUsersRequested: Angefragte Plätze
AllocationUsersPriority: Zentrale Dringlichkeit
AllocationPriorityNumericValues: Numerische Werte
AllocationPriorityNumericValuesTip: Komma-separierte ganze Zahlen
AllocationPriorityNumericNoValues: Es wurden keine numerischen Werte angegeben
AllocationPriorityNumericNoParse val@Text: Ganze Zahl konnte nicht geparst werden: „#{val}“
AllocationPriorityOrdinalValueNegative: Sortier-Index darf nicht negativ sein
AllocationPriorityOrdinalValue: Sortier-Index
AllocationPriorityOrdinalValueTip: Null entspricht dem ersten Eintrag der Liste, höhere Indizes entsprechen später in der sortierten Liste vorkommenden Bewerbern und damit einer höheren Dringlichkeit
AllocationPriorityNumeric': Numerisch
AllocationPriorityOrdinal': Nach Sortierung
AllocationPrioritiesNumeric: Numerische Dringlichkeiten
AllocationPrioritiesOrdinal: Dringlichkeiten durch Sortierung
AllocationUsersCsvSheetName tid@TermId ssh@SchoolId ash@AllocationShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase ash} Bewerber
AllocationApplication: Bewerbung
AllocationProcess: Platzvergabe
AllocationSubstitutesProcess: Platzvergabe an Nachrücker:innen
AllocationFormTerm: Semester
AllocationFormSchool: Institut
AllocationFormShorthand: Kürzel
AllocationFormName !ident-ok: Name
AllocationFormLegacyShorthands: Alte Kürzel
AllocationFormLegacyShorthandsTip: Zentralanmeldungen werden gelegentlich mit vorherigen Versionen in Bezug gesetzt (z.B. um Kapazität/Auslastung zu vergleichen). Dies geschieht anhand des Kürzels, bzw. anhand der hier angegebenen alten Kürzel. (Komma-separierte Liste)
AllocationFormDescriptions: Beschreibungen
AllocationFormDescription: Beschreibung
AllocationFormDescriptionTip: Wird allen Benutzern auf der Seite der Zentralanmeldung angezeigt
AllocationFormStaffDescription: Beschreibung für Dozierende
AllocationFormStaffDescriptionTip: Wird nur (potentiellen) Dozierenden angezeigt; sowohl beim Anlegen eines Kurses, wie auch auf der Seite der Zentralanmeldung
AllocationFormDeadlines: Fristen
AllocationFormStaffRegisterFrom: Eintragung der Kurse ab
AllocationFormStaffRegisterFromTip: Ab diesem Zeitpunkt können Kursverwalter:innen ihre Kurse selbstständig zur Zentralanmeldung eintragen. Falls kein Zeitpunkt angegeben wird, ist dies nie gestattet.
AllocationFormStaffRegisterTo: Eintragung der Kurse bis
AllocationFormStaffRegisterToTip: Falls kein Zeitpunkt angegeben wird, ist die Eintragung zunächst für immer möglich.
AllocationFormStaffAllocationFrom: Bewertung der Bewerbungen ab
AllocationFormStaffAllocationFromTip: Ab diesem Zeitpunkt dürfen Kursverwalter:innen Bewerbungen zu ihren Kursen bewerten. Falls kein Zeitpunkt angegeben wird, ist dies nie gestattet.
AllocationFormStaffAllocationTo: Bewertung der Bewerbungen bis
AllocationFormStaffAllocationToTip: Falls kein Zeitpunkt angegeben wird, ist die Bewertung zunächst für immer möglich.
AllocationFormRegisterFrom: Bewerbung ab
AllocationFormRegisterFromTip: Ab diesem Zeitpunkt dürfen alle Benutzer Bewerbungen zu den teilnehmenden Kursen hinterlegen. Falls kein Zeitpunkt angegeben wird, ist dies nie gestattet.
AllocationFormRegisterTo: Bewerbung bis
AllocationFormRegisterToTip: Falls kein Zeitpunkt angegeben wird, ist Bewerbung zunächst für immer möglich.
AllocationFormRegisterByStaffFrom: An- und Abmeldung durch Kursverwalter:innen ab
AllocationFormRegisterByStaffFromTip: Ab diesem Zeitpunkt dürfen Kursverwalter:innen Studierende direkt zu ihren Kursen anmelden. Falls kein Zeitpunkt angegeben wird, ist dies für an der Zentralanmeldung teilnehmende Kurse nie gestattet.
AllocationFormRegisterByStaffTo: An- und Abmeldung durch Kursverwalter:innen bis
AllocationFormRegisterByStaffToTip: Falls kein Zeitpunkt angegeben wird, ist An- und Abmeldung zunächst für immer möglich.
AllocationFormRegisterByCourse: An- und Abmeldung nach Kursregeln ab
AllocationFormRegisterByCourseTip: Ab diesem Zeitpunkt folgt selbstständige An- und Abmeldung zu bzw. von den teilnehmenden Kursen wieder den Regeln des jeweiligen Kurses. Falls kein Zeitpunkt angegeben wird, ist direkte An- und Abmeldung nie gestattet.
AllocationFormOverrideDeregister: Direkte Abmeldung frühestens ab
AllocationFormOverrideDeregisterTip: Bis zu diesem Zeitpunkt dürfen Teilnehmende sich nicht von teilnehmenden Kursen abmelden, selbst wenn dies nach Kursregeln gestattet wäre. Falls kein Zeitpunkt angegeben wird, ist Abmeldung nach Kursregeln gestattet (bzw. ab „_{MsgAllocationFormRegisterByCourse}“).
AllocationFormStaffRegisterToMustBeAfterFrom: „_{MsgAllocationFormStaffRegisterTo}“ muss nach „_{MsgAllocationFormStaffRegisterFrom}“ liegen.
AllocationFormStaffAllocationToMustBeAfterFrom: „_{MsgAllocationFormStaffAllocationTo}“ muss nach „_{MsgAllocationFormStaffAllocationFrom}“ liegen.
AllocationFormRegisterToMustBeAfterFrom: „_{MsgAllocationFormRegisterTo}“ muss nach „_{MsgAllocationFormRegisterFrom}“ liegen.
AllocationFormRegisterByStaffToMustBeAfterFrom: „_{MsgAllocationFormRegisterByStaffTo}“ muss nach „_{MsgAllocationFormRegisterByStaffFrom}“ liegen.
AllocationFormStaffRegisterFromMustBeBeforeStaffAllocationFrom: „_{MsgAllocationFormStaffRegisterFrom}“ muss vor „_{MsgAllocationFormStaffAllocationFrom}“ liegen.
AllocationFormStaffRegisterToMustBeBeforeStaffAllocationTo: „_{MsgAllocationFormStaffRegisterTo}“ muss vor „_{MsgAllocationFormStaffAllocationTo}“ liegen.
AllocationFormStaffRegisterFromMustBeBeforeRegisterFrom: „_{MsgAllocationFormStaffRegisterFrom}“ muss vor „_{MsgAllocationFormRegisterFrom}“ liegen.
AllocationFormStaffRegisterToMustBeBeforeRegisterTo: „_{MsgAllocationFormStaffRegisterTo}“ muss vor „_{MsgAllocationFormRegisterTo}“ liegen.
AllocationFormStaffAllocationToShouldBeBeforeRegisterByStaffFrom: „_{MsgAllocationFormStaffAllocationTo}“ sollte vor „_{MsgAllocationFormRegisterByStaffFrom}“ liegen.
AllocationFormStaffAllocationToShouldBeBeforeRegisterByCourse: „_{MsgAllocationFormStaffAllocationTo}“ sollte vor „_{MsgAllocationFormRegisterByCourse}“ liegen.
AllocationFormStaffAllocationToShouldBeAfterRegisterTo: „_{MsgAllocationFormStaffAllocationTo}“ sollte nach „_{MsgAllocationFormRegisterTo}“ liegen.
AllocationFormRegisterToShouldBeBeforeRegisterByStaffFrom: „_{MsgAllocationFormRegisterTo}“ sollte vor „_{MsgAllocationFormRegisterByStaffFrom}“ liegen.
AllocationFormRegisterToShouldBeBeforeRegisterByCourse: „_{MsgAllocationFormRegisterTo}“ sollte vor „_{MsgAllocationFormRegisterByCourse}“ liegen.
AllocationFormRegisterByStaffFromShouldBeBeforeRegisterByCourse: „_{MsgAllocationFormRegisterByStaffFrom}“ sollte vor „_{MsgAllocationFormRegisterByCourse}“ liegen.
AllocationNewSuccess: Zentralanmeldung erfolgreich angelegt
AllocationNewAlreadyExists: Eine Zentralanmeldung mit diesem Namen/Kürzel existiert bereits für diese Kombination aus Semester und Institut.
TitleAllocationNew: Neue Zentralanmeldung anlegen
TitleAllocationEdit tid@TermId ssh@SchoolId ash@AllocationShorthand: #{toPathPiece tid}-#{ssh}-#{ash}: Zentralanmeldung bearbeiten
HeadingAllocationEdit tid@TermId ssh@SchoolId aname@AllocationName: Zentralanmeldung bearbeiten: _{ShortTermIdentifier (unTermKey tid)}, #{ssh}, #{aname}
AllocationEditSuccess: Zentralanmeldung erfolgreich bearbeitet
AllocationEditAlreadyExists: Eine andere Zentralanmeldung mit diesem Namen/Kürzel existiert bereits für diese Kombination aus Semester und Institut.
TitleAllocationMatchings tid@TermId ssh@SchoolId ash@AllocationShorthand: #{toPathPiece tid}-#{ssh}-#{ash}: Verteilungen
HeadingAllocationMatchings tid@TermId ssh@SchoolId aname@AllocationName: Verteilungen: _{ShortTermIdentifier (unTermKey tid)}, #{ssh}, #{aname}
AllocationMatchingsNone: Noch keine Verteilungen
AllocationMatchingsLog: Protokoll
AllocationMatchingsTime: Zeitpunkt
AllocationMatchingsFingerprint: Prüfsumme
AllocationMatchingLogFileName tid@TermId ssh@SchoolId ash@AllocationShorthand cID@CryptoUUIDAllocationMatching: za-verteilung.#{toPathPiece tid}-#{ssh}-#{ash}.#{toPathPiece cID}.log
AllocationUserDeleteQuestion: Wollen Sie den/die unten aufgeführten Benutzer:in wirklich aus der Zentralanmeldung entfernen?
AllocationUserDeleted: Benutzer:in erfolgreich entfernt
AllocationApplicationsCount n@Word64: #{n} #{pluralDE n "Bewerbung" "Bewerbungen"}
AllocationAllocationsCount n@Word64: #{n} #{pluralDE n "Zuweisung" "Zuweisungen"}
AllocationCourseHasRatings ratings@Word64 vetos@Word64: Dieser Kurs hat bereits #{ratings} #{pluralDE ratings "Bewertung" "Bewertungen"} (#{vetos} #{pluralDE vetos "Veto" "Vetos"})
AllocationCourseParticipantFormCourse: Kurs
AllocationCourseParticipantFormIsRegistered: Registriert?
AllocationCourseParticipantFormIsSelfInflicted: Selbstverschuldet abgemeldet (Grund)?
AllocationCourseParticipantFormDefaultReason: Kein Grund
AllocationUserCourseParticipantFormTitle: Anmeldungen
AllocationUserAllocationUserFormTitle: Teilnahme an der Zentralanmeldung

View File

@ -1,264 +0,0 @@
# SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
AllocationAcceptFormDoesNotMatchSession: The form to accept the computed allocation was generated for a different result than the one, that is currently saved in your session.
AllocationAccepted: Successfully saved allocation
HeadingAllocationAccept: Accept allocation
AllocationAddUserUserNotFound: Email could not be resolved to an user
AllocationAddUserUser: User
AllocationAddUserUserPlaceholder: Email
AllocationAddUserTotalCoursesLessThanOne: Number of requested courses needs to be greater than zero
AllocationAddUserTotalCourses: Requested courses
AllocationAddUserSetPriority: Set central priority?
AllocationAddUserPriority: Central priority
AllocationAddUserApplications: Applications/Ratings
AllocationAddUserTitle termText ssh' allocation: #{termText} - #{ssh'} - #{allocation}: Add applicant
AllocationAddUserShortTitle tid@TermId ssh@SchoolId ash@AllocationShorthand: #{tid}-#{ssh}-#{ash}: Add applicant
AllocationAddUserUserAdded: Successfully added applicant to central allocation
AllocationAddUserUserExists: The specified user is already an applicant for the central allocation
AllocationEditUserUserEdited: Successfully edited applicant
AllocationEditUserTitle termText ssh ash userDisplayName: #{termText} - #{ssh} - #{ash}, Edit applicant: #{userDisplayName}
AllocationEditUserShortTitle tid ssh ash userDisplayName !ident-ok: #{tid}-#{ssh}-#{ash}: #{userDisplayName}
AllocationPriority: Priority
CourseAllocationCourseAcceptsSubstitutesUntil: Accepts substitutes until
CourseAllocationCourseAcceptsSubstitutesNever: Does not accept substitutes
CourseAllocationApplicationInstructionsApplication: Instructions for application
CourseAllocationApplicationTemplateApplication: Application template(s)
CourseApplication: Application
AllocationCoursePriority i: #{ordinalEN i}
AllocationCourseNoApplication: No
ApplicationPriority: Priority
ApplicationVeto: Veto
ApplicationVetoTip: Vetoed applicants will not be assigned to the course
ApplicationRatingPoints: Grading
ApplicationRatingPointsTip: Applicants graded 5.0 will not be assigned to the course
ApplicationRatingComment: Comment
ApplicationRatingCommentVisibleTip: Feedback for the applicant
ApplicationRatingCommentInvisibleTip: Currently only a note for course administrators
ApplicationRatingSection: Grading
ApplicationRatingSectionSelfTip: You are authorised to edit the application as well as it's grading.
CourseApplicationExists: You already applied for this course
CourseApplicationCreated csh: Successfully applied for #{csh}
CourseApplicationInvalidAction: Invalid action
CourseApplicationEdited csh: Successfully changed application for #{csh}
CourseApplicationNotEdited csh: Application for #{csh} not changed
CourseApplicationRated: Successfully edited rating
CourseApplicationRatingDeleted: Successfully deleted rating
CourseApplicationDeleted csh: Successfully withdrew application for #{csh}
AllocationUsersMissingPriorities: Participants without central priority
AllocationUsersMissingPrioritiesTip: Care must be taken, that no participant is excluded from the allocation by not having been assigned a central priority.
AllocationUsersMissingPrioritiesOk: It was ensured, that all participants mentioned above, are excluded from the allocation on valid grounds.
AllocationUsersMissingPrioritiesNotOk: Central allocation cannot occur until all participants, that were not excluded explicitly (“Participants without central priority”), have been assigned a central priority!
AllocationRestrictCourses: Restrict courses
AllocationRestrictCoursesTip: Should places be assigned only in a subset of courses? This functionality can be used to make alternate placements in the case that some participants withdraw from their assigned courses. This functionality should only be used to exclude courses on valid grounds. E.g. if a seminar already had a planning meeting and is thus unable to accept new participants.
AllocationRestrictCoursesSelection: Courses
AllocationRestrictCoursesSelectionTip: Participants will only be assigned to courses listed here.
AllocationOnlyCompute: By sending this form a possible allocation will be computed and saved temporarily. You can then check that the computed allocation is as expected. No changes will yet be made to the state of the database and no notifications will be sent.
AllocationComputed: A possible allocation has been computed and stored in your session. No changes have yet been made!
HeadingAllocationCompute: Compute allocation
HeadingAllocationInfo: Information regarding central allocations
AllocationAvailableCourses: Courses
AllocationAppliedCourses: Applications
AllocationListTitle: Central allocations
AllocationMissingPrioritiesIgnored: Applicants for whom no central priority has been registered will be ignored during assignment!
AllocationPriorities: Central priorities
AllocationPrioritiesTitle tid ssh ash: #{tid}-#{ssh}-#{ash}: Central priorities
AllocationPrioritiesFile: CSV file
AllocationPrioritiesSunk num: Successfully registered central priorities for #{num} #{pluralEN num "applicant" "applicants"}
AllocationPrioritiesMissing num: Could not register central priorities for #{num} #{pluralEN num "applicant" "applicants"} because their matriculation was not found in the uploaded CSV file
AllocationPrioritiesMode: Mode
AllocationTotalCoursesNegative: Requested number of placements must be greater than zero
AllocationTotalCourses: Requested number of placements
AllocationTotalCoursesTip: During the allocation process you will be placed in at most as many courses as specified
AllocationRegistered: Successfully registered participation in this central allocation
AllocationRegistrationEdited: Successfully edited registration for this central allocation
AllocationTitle termText ssh' allocation: #{termText} - #{ssh'}: #{allocation}
AllocationShortTitle termText ssh' ash: #{termText} - #{ssh'} - #{ash}
AllocationNotificationNewCourse: Notifications for new courses
AllocationNotificationNewCourseTip: Do you want to be notified if a new course is added to this central allocation? This overrides the system wide setting under “Settings”.
AllocationNotificationNewCourseSuccessForceOn: You will be notified if a new course is added
AllocationNotificationNewCourseSuccessForceOff: You will not be notified if a new course is added
AllocationNotificationNewCourseCurrentlyOff: Currently you would not receive a notification.
AllocationNotificationNewCourseCurrentlyOn: Currently you would be notified.
CsvColumnAllocationUserSurname: Applicant's surname(s)
CsvColumnAllocationUserFirstName: Applicants's first name(s)
CsvColumnAllocationUserName: Applicant's full name
CsvColumnAllocationUserMatriculation: Applicant's matriculation
CsvColumnAllocationUserStudyFeatures: Features of study
CsvColumnAllocationUserRequested: Maximum number of placements the applicant is prepared to accept
CsvColumnAllocationUserApplied: Number of applications the applicant has provided
CsvColumnAllocationUserVetos: Number of applications that have received a veto from a course administrator or have been rated with a grade that is equivalent to "failed" (5.0)
CsvColumnAllocationUserAssigned: Number of assignments the applicant has already received
CsvColumnAllocationUserNewAssigned: Number of assignments the applicant would receive, if the calculated matching is accepted
CsvColumnAllocationUserPriority: Central priority of this applicant; either a number based on the applicants position in the list sorted by priority (higher numbers mean a higher priority) or a comma-separated list of numerical priorities in square brackets (e.g. [1, 2, 3])
AllocationUsersCsvName tid ssh ash: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase ash}-applicants
AllocationUserNewMatches: New allocations
AllocationUsers: Applicants
AllocationUsersTitle tid ssh ash: #{tid}-#{ssh}-#{ash}: Applicants
#templates allocation/show/course
AllocationNoApplication: No application
CourseAllocationCourseParticipants: Participants
CourseMembersCount n: #{n}
CourseMembersCountLimited n max: #{n}/#{max}
CourseAllocationCourseRatings ratings vetos: #{ratings} #{pluralENs ratings "rating"} (#{vetos} #{pluralENs vetos "veto"})
#templates allocation/accept
ComputedAllocation: Computed allocation
AllocationUsersCount: Participants
AllocationCoursesCount: Courses
AllocationRequestedPlaces: Requested places
AllocationOfferedPlaces: Offered places
AllocationUnmatchedUsers: Participants without assigned places
AllocationUnmatchedCourses: Courses without assigned participants
AllocationTime: Time of allocation
AllocationCourseEligible: Considered
AllocationMatchedUsers: Newly assigned
#templates allocation/show
AllocationSchool: Department
AllocationSemester: Semester
AllocationDescription: Description
AllocationStaffDescription: Staff description
AllocationStaffRegisterFrom: Registration of courses starts
AllocationStaffRegisterTo: Register courses until
AllocationStaffRegister: Registration of courses
AllocationStaffAllocationFrom: Grading of applications starts
AllocationStaffAllocationTo: Rating of applications until
AllocationStaffAllocation: Grading of applications
AllocationRegisterFrom: Application period start
AllocationRegister: Application period
AllocationRegisterClosed: This central allocation is currently closed.
AllocationRegisterOpensIn difftime: This central allocation is expected to open in #{difftime}
AllocationRegisterByStaff: Enrollment by course administrators
AllocationRegisterByStaffFrom: Enrollment by course administrators starts
AllocationRegisterByStaffTo: Enrollment by course administrators ends
AllocationRegisterByStaffTip: In this periods course administrators may enroll participants in their courses.
AllocationRegisterByStaffFromTip: Starting at this time course administrators may enroll participants in their courses.
AllocationRegisterByCourse: Direct enrollment
AllocationRegisterByCourseFrom: Direct enrollment starts
AllocationRegisterByCourseFromTip: Starting at this time course administrators participating in this central allocation may open their courses for participants to manage their participation themselves.
AllocationRegisterTo: Registration until
AllocationNextSubstitutesDeadline: Next course accepts substitutes until
AllocationNextSubstitutesDeadlineNever: No course currently accepts substitutes
AllocationFreeCapacity: Free capacity
AllocationOverrideDeregister: Leaving courses only until
AllocationParticipation: Your participation in this central allocation
AllocationParticipationLoginFirst: To participate in this central allocation, please log in first
AllocationNotificationLoginFirst: To change your notification settings, please log in first.
AllocationCourses: Centrally allocated courses
AllocationPriorityTip: Courses to which you assign a higher priority are preferred during the allocation process.
AllocationPriorityRelative: The absolute priority values are meaningless. The only consideration is whether one course has a higher priority than another.
ApplicationEditTip: During the application period you may edit and retract your applications at will.
AllocationNumCoursesAvailableApplied available applied: You have applied for #{applied}/#{available} #{pluralEN applied "course" "courses"}
AllocationCourseRestrictionDontRestrict: Don't restrict
AllocationCourseRestrictionSubstitutes: Courses which currently allow substitute registrations
AllocationCourseRestrictionCustom: Custom
AllocationName: Name
Allocation: Central allocation
AllocationActive: Active
AllocationUsersApplied: Applications
AllocationUsersAssigned: Assignments
AllocationUsersVetoed: Vetos
AllocationUsersRequested: Requested assignments
AllocationUsersPriority: Central priority
AllocationPriorityNumericValues: Numerical values
AllocationPriorityNumericValuesTip: Comma separated whole numbers
AllocationPriorityNumericNoValues: No numerical values were provided
AllocationPriorityNumericNoParse val: Whole number could not be parsed: “#{val}”
AllocationPriorityOrdinalValueNegative: Sorting index may not be negative
AllocationPriorityOrdinalValue: Sorting index
AllocationPriorityOrdinalValueTip: Zero corresponds to the first entry in the list; higher indices correspond to applicants occurring later in the sorted list and thus to higher central priorities
AllocationPriorityNumeric': Numerical
AllocationPriorityOrdinal': Based on sorted list
AllocationPrioritiesNumeric: Numeric priorities
AllocationPrioritiesOrdinal: Priorities based on sorted list
AllocationUsersCsvSheetName tid ssh ash: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase ash} Applicants
AllocationApplication: Application
AllocationProcess: Allocation process
AllocationSubstitutesProcess: Allocation of substitutes
AllocationFormTerm: Term
AllocationFormSchool: Department
AllocationFormShorthand: Shorthand
AllocationFormName: Name
AllocationFormLegacyShorthands: Legacy shorthands
AllocationFormLegacyShorthandsTip: Allocations are occasionally related to previous versions (e.g. to compare capacity/utilisation). This is done via their shorthand and via the legacy shorthands given here. (Comma-separated list)
AllocationFormDescriptions: Description
AllocationFormDescription: Description
AllocationFormDescriptionTip: Will be shown to all users on the page of the allocation
AllocationFormStaffDescription: Description for lecturers
AllocationFormStaffDescriptionTip: Will only be shown to (potential) lecturers; will be displayed both when creating a course and on the page of the allocation
AllocationFormDeadlines: Deadlines
AllocationFormStaffRegisterFrom: Register courses from
AllocationFormStaffRegisterFromTip: Starting at this time lecturers may register their courses for the allocation. If no timestamp is given, this will never be permitted.
AllocationFormStaffRegisterTo: Register courses until
AllocationFormStaffRegisterToTip: If no timestamp is given, registration will initially be possible forever.
AllocationFormStaffAllocationFrom: Rating of applications from
AllocationFormStaffAllocationFromTip: Starting at this time lecturers may rate applications for their courses. If no timestamp is given, this will never be permitted.
AllocationFormStaffAllocationTo: Rating of applications until
AllocationFormStaffAllocationToTip: If no timestamp is given, rating will initially be possible forever.
AllocationFormRegisterFrom: Application from
AllocationFormRegisterFromTip: Starting at this time all users may provide applications for courses participating in this allocation. If no timestamp is given, this will never be permitted.
AllocationFormRegisterTo: Application to
AllocationFormRegisterToTip: If no timestamp is given, application will initially be possible forever.
AllocationFormRegisterByStaffFrom: (De-)registration by lecturers from
AllocationFormRegisterByStaffFromTip: Starting at this time lecturers may (de-)register participants from/to their courses directly. If no timestamp is given, this will never be permitted.
AllocationFormRegisterByStaffTo: (De-)registration by lecturers until
AllocationFormRegisterByStaffToTip: If no timestamp is given, (de-)registration will initially be possible forever.
AllocationFormRegisterByCourse: (De-)registration according to course rules from
AllocationFormRegisterByCourseTip: Starting at this time users may (de-)register themselves from/to courses participating in the allocation according to the rules of the respective course. If no timestamp is given, direct (de-)registration will never be permitted.
AllocationFormOverrideDeregister: Direct deregistration from
AllocationFormOverrideDeregisterTip: Up to this time participants of courses participating in the allocation may not deregister themselves, even if it would be allowed according to course rules. If no timestamp is given, deregistration is allowed according to course rules (or as per “_{MsgAllocationFormRegisterByCourse}”).
AllocationFormStaffRegisterToMustBeAfterFrom: “_{MsgAllocationFormStaffRegisterTo}” must be after “_{MsgAllocationFormStaffRegisterFrom}”.
AllocationFormStaffAllocationToMustBeAfterFrom: “_{MsgAllocationFormStaffAllocationTo}” must be after “_{MsgAllocationFormStaffAllocationFrom}”.
AllocationFormRegisterToMustBeAfterFrom: “_{MsgAllocationFormRegisterTo}” must be after “_{MsgAllocationFormRegisterFrom}”.
AllocationFormRegisterByStaffToMustBeAfterFrom: “_{MsgAllocationFormRegisterByStaffTo}” must be after “_{MsgAllocationFormRegisterByStaffFrom}”.
AllocationFormStaffRegisterFromMustBeBeforeStaffAllocationFrom: “_{MsgAllocationFormStaffRegisterFrom}” must be before “_{MsgAllocationFormStaffAllocationFrom}”.
AllocationFormStaffRegisterToMustBeBeforeStaffAllocationTo: “_{MsgAllocationFormStaffRegisterTo}” must be before “_{MsgAllocationFormStaffAllocationTo}”.
AllocationFormStaffRegisterFromMustBeBeforeRegisterFrom: “_{MsgAllocationFormStaffRegisterFrom}” must be before “_{MsgAllocationFormRegisterFrom}”.
AllocationFormStaffRegisterToMustBeBeforeRegisterTo: “_{MsgAllocationFormStaffRegisterTo}” must be before “_{MsgAllocationFormRegisterTo}”.
AllocationFormStaffAllocationToShouldBeBeforeRegisterByStaffFrom: “_{MsgAllocationFormStaffAllocationTo}” should be before “_{MsgAllocationFormRegisterByStaffFrom}”.
AllocationFormStaffAllocationToShouldBeBeforeRegisterByCourse: “_{MsgAllocationFormStaffAllocationTo}” should be before “_{MsgAllocationFormRegisterByCourse}”.
AllocationFormStaffAllocationToShouldBeAfterRegisterTo: “_{MsgAllocationFormStaffAllocationTo}” should be after “_{MsgAllocationFormRegisterTo}”.
AllocationFormRegisterToShouldBeBeforeRegisterByStaffFrom: “_{MsgAllocationFormRegisterTo}” should be before “_{MsgAllocationFormRegisterByStaffFrom}”.
AllocationFormRegisterToShouldBeBeforeRegisterByCourse: “_{MsgAllocationFormRegisterTo}” should be before “_{MsgAllocationFormRegisterByCourse}”.
AllocationFormRegisterByStaffFromShouldBeBeforeRegisterByCourse: “_{MsgAllocationFormRegisterByStaffFrom}” should be before “_{MsgAllocationFormRegisterByCourse}”.
AllocationNewSuccess: Successfully created allocation
AllocationNewAlreadyExists: An allocation with this name/shorthand already exists for this term and department.
TitleAllocationNew: Create new allocation
TitleAllocationEdit tid ssh ash: #{toPathPiece tid}-#{ssh}-#{ash}: Edit allocation
HeadingAllocationEdit tid ssh aname: Edit allocation: _{ShortTermIdentifier (unTermKey tid)}, #{ssh}, #{aname}
AllocationEditSuccess: Successfully edited allocation
AllocationEditAlreadyExists: Another allocation with this name/shorthand already exists for this term and department.
TitleAllocationMatchings tid ssh ash: #{toPathPiece tid}-#{ssh}-#{ash}: Matchings
HeadingAllocationMatchings tid ssh aname: Matchings: _{ShortTermIdentifier (unTermKey tid)}, #{ssh}, #{aname}
AllocationMatchingsNone: No matchings yet
AllocationMatchingsLog: Log
AllocationMatchingsTime: Timestamp
AllocationMatchingsFingerprint: Fingerprint
AllocationMatchingLogFileName tid ssh ash cID: allocation-matching.#{toPathPiece tid}-#{ssh}-#{ash}.#{toPathPiece cID}.log
AllocationUserDeleteQuestion: Do you really want to remove the allocation participant listed below?
AllocationUserDeleted: Participant successfully removed
AllocationApplicationsCount n: #{n} #{pluralENs n "application"}
AllocationAllocationsCount n: #{n} #{pluralENs n "allocation"}
AllocationCourseHasRatings ratings vetos: This course already has #{ratings} #{pluralENs ratings "rating"} (#{vetos} #{pluralENs vetos "veto"})
AllocationCourseParticipantFormCourse: Course
AllocationCourseParticipantFormIsRegistered: Registered?
AllocationCourseParticipantFormIsSelfInflicted: Deregistration “self inflicted” (reason)?
AllocationCourseParticipantFormDefaultReason: No Reason
AllocationUserCourseParticipantFormTitle: Course registrations
AllocationUserAllocationUserFormTitle: Participation in allocation

View File

@ -1,45 +0,0 @@
# SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
CourseApplicationTitle displayName@Text csh@CourseShorthand: Bewerbung für #{csh}: #{displayName}
CourseApplicationArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand appId@CryptoFileNameCourseApplication displayName@Text !ident-ok: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase (toPathPiece appId)}-#{foldCase displayName}
CourseApplicationsAllocatedDirectory: zentral
CourseApplicationsNotAllocatedDirectory: direkt
CsvColumnApplicationsAllocation: Zentralanmeldung über die die Bewerbung eingegangen ist
CsvColumnApplicationsApplication: Eindeutige Nummer der Bewerbung (zur Zuordnung im ZIP-Archiv aller Bewerbungsdateien)
CsvColumnApplicationsName: Voller Name des/der Bewerbers/Bewerberin
CsvColumnApplicationsMatriculation: Matrikelnummer des/der Bewerbers/Bewerberin
CsvColumnApplicationsEmail: E-Mail-Adresse des/der Bewerbers/Bewerberin
CsvColumnApplicationsText: Text-Bewerbung
CsvColumnApplicationsHasFiles: Hat der/die Bewerber/Bewerberin Dateien zu seiner Bewerbung eingereicht (siehe ZIP-Archiv aller Bewerbungsdateien)?
CsvColumnApplicationsVeto: Bewerber:in mit Veto werden garantiert nicht dem Kurs zugeteilt; "veto" oder leer
CsvColumnApplicationsRating: Bewertung der Bewerbung; "1.0", "1.3", "1.7", ..., "4.0", "5.0" (Leer wird behandelt wie eine Note zwischen 2.3 und 2.7)
CsvColumnApplicationsComment: Kommentar zur Bewerbung; je nach Kurs-Einstellungen entweder nur als Notiz für die Kursverwalter:innen oder Feedback für den/die Bewerber/Bewerberin
CourseApplicationsTableCsvName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-bewerbungen
CourseApplicationIsParticipant: Kursteilnehmer
ApplicationUserColumns: Bewerbung
ApplicationRatingColumns: Bewertung
ApplicationGeneratedColumns: Stammdaten
ApplicationGeneratedColumnsTip: Stammdaten eines/einer Bewerbers/Bewerberin sind Daten, welche dem System zu diesem/dieser Benutzer/Benutzerin bekannt sind und welche der/die Benutzer/Benutzerin im Zuge der Bewerbung nicht beeinflussen kann.
CourseApplicationVeto !ident-ok: Veto
CourseApplicationNoVeto: Kein Veto
CourseApplicationNoRatingPoints: Keine Bewertung
CourseApplicationNoRatingComment: Kein Kommentar
CourseApplicationsListTitle: Bewerbungen
AcceptApplicationsMode: Bewerbungen akzeptieren
AcceptApplicationsModeTip: Sollen akzeptierte Bewerber:innen direkt als Teilnehmer:in im Kurs eingetragen werden oder sollen Einladungen per E-Mail verschickt werden?
AcceptApplicationsSecondary: Gleichstände auflösen
AcceptApplicationsSecondaryTip: Wenn es im Laufe des Verfahrens mehrere Bewerber:innen mit der selben Bewertung für den selben Platz gibt, wie soll der Gleichstand aufgelöst werden?
CsvColumnUserAppStudyFeatures: Alle relevanten Studiendaten des/der Teilnehmers/Teilnehmerin als Semikolon (;) separierte Liste
ApplicationAllocationNoAllocationUser: Dieser Student/diese Studentin nimmt nicht (mehr) an der Zentralvergabe teil. Die Bewerbung existiert zwar noch, wird jedoch bis auf Weiteres sicherlich nicht zu einer Anmeldung führen.
CourseApplicationId: Bewerbungsnummer
CourseApplicationRatingPoints: Bewertung
#not used as Msg
AcceptApplicationsDirect: Direkt anmelden
AcceptApplicationsInvite: Einladungen verschicken
AcceptApplicationsSecondaryRandom: Zufällig
AcceptApplicationsSecondaryTime: Nach Zeitpunkt der Bewerbung
CourseApplicationsTableCsvSheetName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh} Bewerbungen

View File

@ -1,46 +0,0 @@
# SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
CourseApplicationTitle displayName csh: Application for #{csh}: #{displayName}
CourseApplicationArchiveName tid ssh csh appId displayName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase (toPathPiece appId)}-#{foldCase displayName}
CourseApplicationsAllocatedDirectory: central
CourseApplicationsNotAllocatedDirectory: direct
CsvColumnApplicationsAllocation: Central allocation for which this application was made
CsvColumnApplicationsApplication: Globally unique number of application (for matching with the ZIP archive of all application files)
CsvColumnApplicationsName: Participant's full name
CsvColumnApplicationsMatriculation: Participant's matriculation
CsvColumnApplicationsEmail: Participant's email address
CsvColumnApplicationsText: Application text
CsvColumnApplicationsHasFiles: Did the applicant provide any additional files with their application (see ZIP archive of all application files)?
CsvColumnApplicationsVeto: Vetoed applicants are never assigned to the course; "veto" or empty
CsvColumnApplicationsRating: Application grading; Any number grade ("1.0", "1.3", "1.7", ..., "4.0", "5.0"); Empty cells will be treated as if they contained a grade between 2.3 and 2.7
CsvColumnApplicationsComment: Application comment; depending on course settings this might purely be a note for course administrators or be feedback for the applicant
CourseApplicationsTableCsvName tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-applications
CourseApplicationIsParticipant: Course participant
ApplicationUserColumns: Application
ApplicationRatingColumns: Rating
ApplicationGeneratedColumns: Master data
ApplicationGeneratedColumnsTip: An applicant's master data is data which is known to the system about this user and which the user cannot modify when applying for the course.
CourseApplicationVeto: Veto
CourseApplicationNoVeto: No veto
CourseApplicationNoRatingPoints: No grading
CourseApplicationNoRatingComment: No comment
CourseApplicationsListTitle: Applications
AcceptApplicationsMode: Accept applications
AcceptApplicationsModeTip: Should accepted applications be enrolled in the course directly or should invitations be sent via email?
AcceptApplicationsSecondary: Breaking ties
AcceptApplicationsSecondaryTip: If a tie occurs during the acceptance process, how should it be broken?
CsvColumnUserAppStudyFeatures: All relevant features of study for the participant, separated by semicolon (;)
ApplicationAllocationNoAllocationUser: This student does not (or does no longer) participate in the allocation. The application still exists, but will certainly not lead to a registriation while it is in this state.
CourseApplicationId: Application number
CourseApplicationRatingPoints: Grading
#not used as Msg
AcceptApplicationsDirect: Enroll directly
AcceptApplicationsInvite: Send invitations
AcceptApplicationsSecondaryRandom: Randomly
AcceptApplicationsSecondaryTime: By time of application
CourseApplicationsTableCsvSheetName tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh} Applications

View File

@ -12,9 +12,6 @@ FilterCourseSearchShorthand: Kürzel-Suche
FilterCourseSearchTitle: Titel-Suche FilterCourseSearchTitle: Titel-Suche
FilterCourseRegistered: Registriert FilterCourseRegistered: Registriert
FilterCourseRegisterOpen: Anmeldung möglich FilterCourseRegisterOpen: Anmeldung möglich
FilterCourseAllocation: Zentralanmeldung
FilterCourseAllocationNone: Keine Zentralanmeldung
FilterCourseAllocationOption tid@TermId ssh@SchoolId aname@AllocationName !ident-ok: #{toPathPiece tid} #{ssh} #{aname}
CourseRegistered: Angemeldet CourseRegistered: Angemeldet
CourseRegistration: Anmeldung CourseRegistration: Anmeldung
CourseDescription: Beschreibung CourseDescription: Beschreibung
@ -25,13 +22,6 @@ CourseLecturerAlreadyAdded: Diese:r Nutzer:in ist bereits als Kursverwalter:in e
CourseLecturerType: Rolle CourseLecturerType: Rolle
LecturerType: Rolle LecturerType: Rolle
CourseLecturerRightsIdentical: Alle Sorten von Kursverwalter:innen haben identische Rechte. CourseLecturerRightsIdentical: Alle Sorten von Kursverwalter:innen haben identische Rechte.
CourseAllocationOption term@Text name@Text !ident-ok: #{name} (#{term})
CourseAllocationParticipate: Teilnahme an Zentralanmeldung
CourseNoAllocationsAvailable: Es sind aktuell keine Zentralanmeldungen verfügbar
CourseAllocation: Zentralanmeldung
CourseAllocationMinCapacity: Minimale Teilnehmeranzahl
CourseAllocationMinCapacityTip: Wenn der Veranstaltung bei der Zentralanmeldung weniger als diese Anzahl von Teilnehmer:innen zugeteilt würden, werden diese stattdessen auf andere Kurse umverteilt
CourseAllocationMinCapacityMustBeNonNegative: Minimale Teilnehmeranzahl darf nicht negativ sein
CourseAcceptSubstitutesUntil: Nachrücker:innen akzeptieren bis CourseAcceptSubstitutesUntil: Nachrücker:innen akzeptieren bis
CourseAcceptSubstitutesUntilTip: Bis zu welchem Zeitpunkt sollen durch die Zentralanmeldung Nachrücker:innen diesem Kurs zugewiesen werden? Wird kein Datum angegeben werden nach der Initialen Verteilung nie Nachrücker:innen zugewiesen. Diese Frist sollte nicht willkürlich früh bzw. nicht gesetzt werden, um für die Studierenden keine unnötige Beschränkung darzustellen. Geeignet ist z.B. bei einem Seminar wenige Stunden vor dem ersten Treffen zum Verteilen der Themen. CourseAcceptSubstitutesUntilTip: Bis zu welchem Zeitpunkt sollen durch die Zentralanmeldung Nachrücker:innen diesem Kurs zugewiesen werden? Wird kein Datum angegeben werden nach der Initialen Verteilung nie Nachrücker:innen zugewiesen. Diese Frist sollte nicht willkürlich früh bzw. nicht gesetzt werden, um für die Studierenden keine unnötige Beschränkung darzustellen. Geeignet ist z.B. bei einem Seminar wenige Stunden vor dem ersten Treffen zum Verteilen der Themen.
CourseDeregisterNoShow: „Nicht erschienen“ bei Abmeldung CourseDeregisterNoShow: „Nicht erschienen“ bei Abmeldung
@ -53,18 +43,6 @@ CourseVisibleToTip: Der Kurs ist ab "Sichtbar ab" bis zu diesem Zeitpunkt für a
CourseMaterialFree: Das Kursmaterial ist ohne Anmeldung frei zugänglich CourseMaterialFree: Das Kursmaterial ist ohne Anmeldung frei zugänglich
CourseFormSectionRegistration: Anmeldung zum Kurs CourseFormSectionRegistration: Anmeldung zum Kurs
CourseFormSectionAdministration: Verwaltung CourseFormSectionAdministration: Verwaltung
CourseApplicationInstructions: Anweisungen zur Bewerbung/Anmeldung
CourseApplicationInstructionsTip: Wird den Studierenden angezeigt, wenn diese sich für Ihre Veranstaltung bewerben bzw. bei dieser anmelden
CourseApplicationRequired: Bewerbungsverfahren
CourseApplicationRequiredTip: Sollen Anmeldungen zu diesem Kurs zunächst provisorisch (ohne Kapazitätsbeschränkung) sein, bis sie durch einen Kursverwalter (nach Bewertung der Bewerbungen) akzeptiert werden?
CourseApplicationTemplate: Bewerbungsvorlagen
CourseApplicationTemplateTip: Werden den Studierenden zum download angeboten, wenn diese sich für Ihre Veranstaltung bewerben bzw. bei dieser anmelden
CourseApplicationsText: Text-Bewerbungen
CourseApplicationsTextTip: Sollen die Studierenden bei Ihrer Bewerbung bzw. Anmeldung (ggf. zusätzlich zu abgegebenen Dateien) auch unformatierten Text einreichen können?
CourseApplicationsFiles: Bewerbungsdateien
CourseApplicationsFilesTip: Sollen die Studierenden bei Ihrer Bewerbung bzw. Anmeldung (ggf. zusätzlich zu unformatiertem Text) auch Dateien abgeben können?
CourseApplicationRatingsVisible: Feedback für Bewerbungen
CourseApplicationRatingsVisibleTip: Sollen Bewertung und Kommentar der Bewerbungen den Studierenden nach Ende der Bewertungs-Phase angezeigt werden?
CourseCapacity: Kapazität CourseCapacity: Kapazität
CourseCapacityTip: Anzahl erlaubter Kursanmeldungen, leer lassen für unbeschränkte Kurskapazität CourseCapacityTip: Anzahl erlaubter Kursanmeldungen, leer lassen für unbeschränkte Kurskapazität
CourseSecretTip: Anmeldung zum Kurs erfordert Eingabe des Passworts, sofern gesetzt CourseSecretTip: Anmeldung zum Kurs erfordert Eingabe des Passworts, sofern gesetzt
@ -80,15 +58,9 @@ CourseDeregisterUntilTip: Abmeldung ist ab "Anmeldungen von" bis zu diesem Zeitp
CourseVisibilityEndMustBeAfterStart: Ende des Sichtbarkeitszeitraums muss nach dem Anfang liegen CourseVisibilityEndMustBeAfterStart: Ende des Sichtbarkeitszeitraums muss nach dem Anfang liegen
CourseRegistrationEndMustBeAfterStart: Ende des Anmeldezeitraums muss nach dem Anfang liegen CourseRegistrationEndMustBeAfterStart: Ende des Anmeldezeitraums muss nach dem Anfang liegen
CourseDeregistrationEndMustBeAfterStart: Ende des Abmeldezeitraums muss nach dem Anfang des Anmeldezeitraums liegen CourseDeregistrationEndMustBeAfterStart: Ende des Abmeldezeitraums muss nach dem Anfang des Anmeldezeitraums liegen
CourseAllocationRequiresCapacity: Bei Teilnahme an einer Zentralanmeldung muss eine Kurskapazität angegeben werden
CourseAllocationTermMustMatch: Kurs-Semester muss mit Semester der Zentralanmeldung übereinstimmen
CourseUserMustBeLecturer: Aktuelle:r Benutzer:in muss als Kursverwalter:in eingetragen sein CourseUserMustBeLecturer: Aktuelle:r Benutzer:in muss als Kursverwalter:in eingetragen sein
CourseAllocationCapacityMayNotBeChanged: Kapazität eines Kurses, der an einer Zentralanmeldung teilnimmt, darf nicht nachträglich verändert werden
AllocationStaffRegisterToExpiredAllocation: Die Frist zur Eintrageng von Kursen in die Zentralanmeldung ist verstrichen. Die Teilnahme darf nicht mehr verändert werden.
AllocationStaffRegisterToExpiredMinCapacity: Die Frist zur Eintrageng von Kursen in die Zentralanmeldung ist verstrichen. Die minimale Kapazität darf nicht mehr verändert werden.
CourseShorthandTooLong: Lange Kurskürzel können zu Problemen bei der Darstellung und der Kommunikation mit den Studierenden führen. Bitte wählen Sie ein weniger langes Kürzel, falls möglich. CourseShorthandTooLong: Lange Kurskürzel können zu Problemen bei der Darstellung und der Kommunikation mit den Studierenden führen. Bitte wählen Sie ein weniger langes Kürzel, falls möglich.
CourseNotAlwaysVisibleDuringRegistration: Um Studierenden über den gesamten Anmeldezeitraum hinweg die Anmeldung zum Kurs zu ermöglichen, sollte der Kurs auch über den gesamten Anmeldezeitraum hinweg sichtbar sein (dies ist aktuell nicht gegeben). CourseNotAlwaysVisibleDuringRegistration: Um Studierenden über den gesamten Anmeldezeitraum hinweg die Anmeldung zum Kurs zu ermöglichen, sollte der Kurs auch über den gesamten Anmeldezeitraum hinweg sichtbar sein (dies ist aktuell nicht gegeben).
CourseApplicationInstructionsRecommended: Studierende können bei ihrer Anmeldung/Bewerbung nach aktuellen Einstellungen Texte bzw. Dateien abgeben. Es wurden jedoch keine Anweisungen zur Bewerbung oder Vorlage-Dateien hinterlegt. Sie sollten entweder keine Texte bzw. Dateien verlangen oder über Anweisungen bzw. Vorlagen klarstellen, was Sie von den Studierenden erwarten.
NoSuchTerm tid@TermId: Semester #{tid} gibt es nicht. NoSuchTerm tid@TermId: Semester #{tid} gibt es nicht.
NoSuchSchool ssh@SchoolId: Institut #{ssh} gibt es nicht. NoSuchSchool ssh@SchoolId: Institut #{ssh} gibt es nicht.
NoSuchCourseShorthand csh@CourseShorthand: Kein Kurs mit Kürzel #{csh} bekannt. NoSuchCourseShorthand csh@CourseShorthand: Kein Kurs mit Kürzel #{csh} bekannt.
@ -107,24 +79,32 @@ CourseParticipantInvitationAccepted courseName@Text: Sie wurden als Teilnehmer:i
CourseParticipantEnlistDirectly: Bekannte Nutzer:innen sofort als Teilnehmer:in eintragen CourseParticipantEnlistDirectly: Bekannte Nutzer:innen sofort als Teilnehmer:in eintragen
CourseSubmissionGroup: Feste Abgabegruppe CourseSubmissionGroup: Feste Abgabegruppe
SubmissionGroupEmptyIsUnsetTip: Leer lassen um Benutzer:innen aus den jeweiligen Abgabegruppen ersatzlos zu entfernen SubmissionGroupEmptyIsUnsetTip: Leer lassen um Benutzer:innen aus den jeweiligen Abgabegruppen ersatzlos zu entfernen
CourseParticipantsRegisterHeading: Kursteilnehmer :innen hinzufügen CourseParticipantsRegisterHeading: Kursteilnehmer:innen hinzufügen
CourseParticipantsRegisterActionAddParticipants: Personen zum Kurs anmelden
CourseParticipantsRegisterActionAddTutorialMembers: Personen zu Kurs und Übungsgruppe anmelden
CourseParticipantsRegisterUsersField: Zum Kurs anzumeldende Personen
CourseParticipantsRegisterUsersFieldTip: Bitte Personalnummer angeben. Mehrere Personen bitte mit Komma getrennt angeben.
CourseParticipantsRegisterTutorialOption: Kursteilnehmer:innen zu Übungsgruppe anmelden?
CourseParticipantsRegisterTutorialField: Übungsgruppe
CourseParticipantsRegisterTutorialFieldTip: Ist aktuell keine Übungsgruppe mit diesem Namen vorhanden, wird eine neue erstellt. Ist bereits eine Übungsgruppe mit diesem Namen vorhanden, werden die Kursteilnehmenden dieser hinzugefügt.
CourseParticipantsRegisterNoneGiven: Es wurden keine anzumeldenden Personen angegeben!
CourseParticipantsRegisterNotFoundInAvs n@Int: Zu #{n} #{pluralDE n "Angabe konnte keine übereinstimmende Person" "Angaben konnten keine übereinstimmenden Personen"} im AVS gefunden werden
CourseParticipantsInvited n@Int: #{n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt CourseParticipantsInvited n@Int: #{n} #{pluralDE n "Einladung" "Einladungen"} per E-Mail verschickt
CourseParticipantsAlreadyRegistered n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} #{pluralDE n "ist" "sind"} bereits angemeldet CourseParticipantsAlreadyRegistered n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} #{pluralDE n "ist" "sind"} bereits zum Kurs angemeldet
CourseParticipantsRegistered n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} erfolgreich angemeldet CourseParticipantsAlreadyTutorialMember n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} #{pluralDE n "ist" "sind"} bereits in dieser Übungsgruppe angemeldet
CourseApplicationText: Text-Bewerbung CourseParticipantsRegistered n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} erfolgreich zum Kurs angemeldet
CourseApplicationFollowInstructions: Beachten Sie die Anweisungen zur Bewerbung! CourseParticipantsRegisteredTutorial n@Int: #{n} #{pluralDE n "Teinehmer:in" "Teilnehmer:innen"} erfolgreich zur Übungsgruppe angemeldet
CourseParticipantsRegisterConfirmationHeading: Teilnehmer:innen hinzufügen
CourseParticipantsRegisterUnnecessary: Alle angeforderten Anmeldungen sind bereits vorhanden. Es wurden keine Änderungen vorgenommen.
CourseParticipantsRegisterConfirmInvalid: Ungültiges Bestätigungsformular!
CourseRegistrationText: Text zur Anmeldung CourseRegistrationText: Text zur Anmeldung
CourseRegistrationFollowInstructions: Beachten Sie die Anweisungen zur Anmeldung! CourseRegistrationFollowInstructions: Beachten Sie die Anweisungen zur Anmeldung!
CourseRegistrationFiles: Datei(en) zur Anmeldung CourseRegistrationFiles: Datei(en) zur Anmeldung
CourseApplicationNoFiles: Keine Datei(en)
CourseApplicationFilesNeedReupload: Bewerbungsdateien müssen neu hochgeladen werden, wann immer die Bewerbung angepasst wird
CourseRegistrationFilesNeedReupload: Dateien zur Anmeldung müssen neu hochgeladen werden, wann immer die Anmeldung angepasst wird CourseRegistrationFilesNeedReupload: Dateien zur Anmeldung müssen neu hochgeladen werden, wann immer die Anmeldung angepasst wird
CourseApplicationFile: Bewerbung
CourseApplicationFiles: Bewerbungsdatei(en)
CourseApplicationArchive: Zip-Archiv der Bewerbungsdatei(en)
CourseRegistrationFile: Datei zur Anmeldung CourseRegistrationFile: Datei zur Anmeldung
CourseRegistrationArchive: Zip-Archiv der Datei(en) zur Anmeldung CourseRegistrationArchive: Zip-Archiv der Datei(en) zur Anmeldung
CourseDeregistrationAllocationLog: Ihr Platz in diesem Kurs stammt aus einer Zentralanmeldung. Wenn Sie sich vom Kurs abmelden wird dieser Umstand permanent im System gespeichert und kann Sie u.U. bei zukünftigen Zentralanmeldungen benachteiligen. Wenn Sie gute Gründe vorzuweisen haben, warum Ihre Abmeldung nicht selbstverschuldet ist, kontaktieren Sie bitte eine:n Kursverwalter:in. Diese haben die Möglichkeit Sie ohne permanente Eintragung im System abzumelden.
CourseDeregistrationNoShow: Wenn Sie sich vom Kurs abmelden, wird für alle Prüfungen des Kurses „nicht erschienen“ gemeldet. Wenn Sie gute Gründe vorzuweisen haben, warum Ihre Abmeldung nicht selbstverschuldet ist, kontaktieren Sie bitte eine:n Kursverwalter:in. Diese haben die Möglichkeit Sie ohne permanente Eintragung im System abzumelden. CourseDeregistrationNoShow: Wenn Sie sich vom Kurs abmelden, wird für alle Prüfungen des Kurses „nicht erschienen“ gemeldet. Wenn Sie gute Gründe vorzuweisen haben, warum Ihre Abmeldung nicht selbstverschuldet ist, kontaktieren Sie bitte eine:n Kursverwalter:in. Diese haben die Möglichkeit Sie ohne permanente Eintragung im System abzumelden.
CourseDeregistrationFromInvisibleCourse: Dieser Kurs ist nur für angemeldete Teilnehmer:innen und Bewerber:innen sichtbar. Wenn Sie sich jetzt abmelden, können Sie danach nicht wieder auf den Kurs zugreifen! CourseDeregistrationFromInvisibleCourse: Dieser Kurs ist nur für angemeldete Teilnehmer:innen und Bewerber:innen sichtbar. Wenn Sie sich jetzt abmelden, können Sie danach nicht wieder auf den Kurs zugreifen!
CourseDeregistrationNoReRegistration: Wenn Sie sich jetzt vom Kurs abmelden, können Sie sich nicht wieder selbstständig anmelden. CourseDeregistrationNoReRegistration: Wenn Sie sich jetzt vom Kurs abmelden, können Sie sich nicht wieder selbstständig anmelden.
@ -134,15 +114,8 @@ CourseRegisterOk: Erfolgreich zum Kurs angemeldet
CourseDeregisterOk: Erfolgreich vom Kurs abgemeldet CourseDeregisterOk: Erfolgreich vom Kurs abgemeldet
CourseApplyOk: Erfolgreich zum Kurs beworben CourseApplyOk: Erfolgreich zum Kurs beworben
CourseRetractApplyOk: Bewerbung zum Kurs erfolgreich zurückgezogen CourseRetractApplyOk: Bewerbung zum Kurs erfolgreich zurückgezogen
CourseApplicationTemplateArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-bewerbungsvorlagen
CourseMemberOf: Teilnehmer:in von CourseMemberOf: Teilnehmer:in von
CourseAssociatedWith: assoziiert mit CourseAssociatedWith: assoziiert mit
CourseDeregistrationAllocationReason: Grund
CourseDeregistrationAllocationReasonTip: Der angegebene Grund wird permanent im System hinterlegt und ist i.A. einziger Anhaltspunkt zur Schlichtung etwaiger Konflikte
CourseDeregistrationAllocationNoShow: „Nicht erschienen“ eintragen
CourseDeregistrationAllocationNoShowTip: Soll für alle Prüfungen dieses Kurses „nicht erschienen“ als Prüfungsleistung eingetragen werden? Dies geschieht einmalig bei der Abmeldung (sofern nicht bereits eine Prüfungsleistung existiert) und automatisch beim Anlegen von neuen Prüfungen.
CourseDeregistrationAllocationShouldLog: Selbstverschuldet
CourseDeregistrationAllocationShouldLogTip: Falls der Platz des Studierenden, der abgemeldet wird, aus einer Zentralanmeldung stammt, ist vorgesehen einen permanenten Eintrag im System zu speichern, der den Studierenden u.U. bei zukünftigen Zentralanmeldungen benachteiligt. Als Kursverwalter:in haben Sie die Möglichkeit dies zu unterbinden, wenn der Studierende gute Gründe vorweisen kann, warum seine Abmeldung nicht selbstverschuldet ist.
CourseLastEdit: Letzte Änderung CourseLastEdit: Letzte Änderung
CourseUserNote: Notiz CourseUserNote: Notiz
CourseUserNoteTooltip: Nur für Verwalter dieses Kurses einsehbar CourseUserNoteTooltip: Nur für Verwalter dieses Kurses einsehbar
@ -201,17 +174,11 @@ CourseParticipantsCount n@Int !ident-ok: #{n}
CourseParticipantsCountOf n@Int m@Int: #{n} von #{m} CourseParticipantsCountOf n@Int m@Int: #{n} von #{m}
CourseVisibility: Sichtbarkeit CourseVisibility: Sichtbarkeit
CourseInvisible: Dieser Kurs ist momentan nur für Dozierende, Assistent:innen, Tutor:innen, Korrektor:innen, angemeldete Teilnehmer:innen und Bewerber:innen sichtbar. CourseInvisible: Dieser Kurs ist momentan nur für Dozierende, Assistent:innen, Tutor:innen, Korrektor:innen, angemeldete Teilnehmer:innen und Bewerber:innen sichtbar.
CourseInvisibleOverridenByAllocation: Da die Zentralanmeldung, an welcher der Kurs teilnimmt aktuell offen für Bewerbungen ist, wird die Kurssichtbarkeit während der Bewerbungsphase forciert. Außerhalb der Bewerbungsphase ist der Kurs nur für Dozierende, Assistent:innen, Tutor:innen, Korrektor:innen, angemeldete Teilnehmer:innen und Bewerber:innen sichtbar.
CourseRegistrationInterval: Anmeldung CourseRegistrationInterval: Anmeldung
CourseDirectRegistrationInterval: Direkte Anmeldung CourseDirectRegistrationInterval: Direkte Anmeldung
CourseDeregisterUntil time@Text: Abmeldung nur bis #{time} CourseDeregisterUntil time@Text: Abmeldung nur bis #{time}
CourseApplicationInstructionsApplication: Anweisungen zur Bewerbung
CourseApplicationInstructionsRegistration: Anweisungen zur Anmeldung
CourseApplicationTemplateApplication: Bewerbungsvorlage(n)
CourseApplicationTemplateRegistration: Anmeldungsvorlage(n)
NotRegistered: Sie sind zu diesem Kurs nicht angemeldet.
CourseApplicationDeleteToEdit: Um Ihre Bewerbung zu editieren müssen Sie sie zunächst zurückziehen und sich erneut bewerben.
CourseMaterial !ident-ok: Material CourseMaterial !ident-ok: Material
NotRegistered: Nicht zum Kurs angemeldet
CourseMaterialNotFree: Das Kursmaterial ist nur für Mitglieder des Kurses einsehbar, also z.B. für Teilnehmer:innen, Tutor:innen, Korrektor:innen und Verwalter:innen. CourseMaterialNotFree: Das Kursmaterial ist nur für Mitglieder des Kurses einsehbar, also z.B. für Teilnehmer:innen, Tutor:innen, Korrektor:innen und Verwalter:innen.
CourseMaterialsFoundHere: Material zum Kurs finden Sie hier CourseMaterialsFoundHere: Material zum Kurs finden Sie hier
CourseMaterialsNoneVisible: Aktuell gibt es zu diesem Kurs kein Material, oder nur Material auf das Sie keinen Zugriff haben (z.B. aufgrund von Fristen bzgl. der Sichtbarkeit). CourseMaterialsNoneVisible: Aktuell gibt es zu diesem Kurs kein Material, oder nur Material auf das Sie keinen Zugriff haben (z.B. aufgrund von Fristen bzgl. der Sichtbarkeit).
@ -235,17 +202,8 @@ StudyTerms: Studiengänge
NoStudyTermsKnown: Keine Studiengänge bekannt NoStudyTermsKnown: Keine Studiengänge bekannt
CourseMembersCountOf n@Int mbNum@(Maybe Int): #{n} Kursanmeldungen #{maybeToMessage " von " mbNum " möglichen"} CourseMembersCountOf n@Int mbNum@(Maybe Int): #{n} Kursanmeldungen #{maybeToMessage " von " mbNum " möglichen"}
#template course/application
CourseAllocationsBounds n@Int: Voraussichtliche Zuteilungen durch #{pluralDE n "Zentralanmeldung" "Zentralanmeldungen"}
CourseAllocationsBoundCoincide numFirstChoice@Int: Vstl. #{numFirstChoice} #{pluralDE numFirstChoice "Teinehmer:in" "Teinehmer:innen"}
CourseAllocationsBound numApps@Int numFirstChoice@Int: Vstl. zwischen #{numFirstChoice} und #{numApps} #{pluralDE numApps "Teinehmer:in" "Teinehmer:innen"}
CourseAllocationsBoundCapped: Die obige Anzeige wurde durch die aktuell angegebene Kurskapazität reduziert.
CourseAllocationsBoundWarningOpen: Diese Informationen entsprechen nur dem aktuellen Stand der Bewerbungen und können sich noch ändern.
CourseApplications: Bewerbungen
CourseLoginToApply: Um sich zum Kurz zu bewerben müssen Sie sich zunächst in Uni2work anmelden CourseLoginToApply: Um sich zum Kurz zu bewerben müssen Sie sich zunächst in Uni2work anmelden
CourseLoginToRegister: Um sich zum Kurs anzumelden müssen Sie zunächst in Uni2work anmelden CourseLoginToRegister: Um sich zum Kurs anzumelden müssen Sie zunächst in Uni2work anmelden
CourseAllApplicationsArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-bewerbungen
CourseLecInviteHeading courseName@Text: Einladung zum/zur Kursverwalter/Kursverwalterin für #{courseName} CourseLecInviteHeading courseName@Text: Einladung zum/zur Kursverwalter/Kursverwalterin für #{courseName}
CourseLecInviteExplanation: Sie wurden eingeladen, Verwalter:in für einen Kurs zu sein. CourseLecInviteExplanation: Sie wurden eingeladen, Verwalter:in für einen Kurs zu sein.
CourseUserHasPersonalisedSheetFilesFilter: Teilnehmer:in hat personalisierte Übungsblatt-Dateien für CourseUserHasPersonalisedSheetFilesFilter: Teilnehmer:in hat personalisierte Übungsblatt-Dateien für
@ -261,13 +219,6 @@ CourseUserDownloadPersonalisedSheetFiles: Personalisierte Übungsblätter herunt
CourseUserSetSubmissionGroup: Gruppenabgabe hinzufügen CourseUserSetSubmissionGroup: Gruppenabgabe hinzufügen
CourseUserReRegister: Teilnehmer:in registrieren CourseUserReRegister: Teilnehmer:in registrieren
CourseApplicationsTableCsvSetVeto: Veto hinzufügen
CourseApplicationsTableCsvSetRating: Rating hinzufügen
CourseApplicationsTableCsvSetComment: Kommentar hinzufügen
CourseApplicationsTableCsvExceptionNoMatchingUser: Kein:e passende:r Benutzer:in
CourseApplicationsTableCsvExceptionNoMatchingAllocation: Keine passende Bewerbung
CourseApplicationsTableCsvExceptionNoMatchingStudyFeatures: Kein passendes Studienfach
CoursePersonalisedSheetFilesArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-personalisierte_dateien CoursePersonalisedSheetFilesArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-personalisierte_dateien
TutorialFreeCapacity: Freie Plätze TutorialFreeCapacity: Freie Plätze
@ -286,3 +237,7 @@ CourseExamRegistrationTime: Angemeldet seit
CourseParticipantStateIsActiveFilter: Ansicht CourseParticipantStateIsActiveFilter: Ansicht
CourseApply: Zum Kurs bewerben CourseApply: Zum Kurs bewerben
CourseAdministrator: Kursadministrator:in CourseAdministrator: Kursadministrator:in
CourseAvsRegisterTitle: Teilnehmer:innen anmelden
CourseAvsRegisterParticipants: Teilnehmer:innen
CourseAvsRegisterParticipantsTip: Mehrere Teilnehmer:innen mit Komma separieren

View File

@ -12,9 +12,6 @@ FilterCourseSearchShorthand: Shorthand search
FilterCourseSearchTitle: Title search FilterCourseSearchTitle: Title search
FilterCourseRegistered: Registered FilterCourseRegistered: Registered
FilterCourseRegisterOpen: Enrolment is allowed FilterCourseRegisterOpen: Enrolment is allowed
FilterCourseAllocation: Central allocation
FilterCourseAllocationNone: No allocation
FilterCourseAllocationOption tid ssh aname: #{toPathPiece tid} #{ssh} #{aname}
CourseRegistered: Enrolled CourseRegistered: Enrolled
CourseRegistration: Enrolment CourseRegistration: Enrolment
CourseDescription: Description CourseDescription: Description
@ -25,13 +22,6 @@ CourseLecturerAlreadyAdded: This user is already configured as a course administ
CourseLecturerType: Role CourseLecturerType: Role
LecturerType: Role LecturerType: Role
CourseLecturerRightsIdentical: All sorts of course administrators have the same permissions. CourseLecturerRightsIdentical: All sorts of course administrators have the same permissions.
CourseAllocationOption term name: #{name} (#{term})
CourseAllocationParticipate: Participate in central allocation
CourseNoAllocationsAvailable: There are no ongoing central allocations
CourseAllocation: Central allocation
CourseAllocationMinCapacity: Minimum number of participants
CourseAllocationMinCapacityTip: If fewer students than this number were to be assigned to this course, then these students would instead be assigned to other courses
CourseAllocationMinCapacityMustBeNonNegative: Minimum number of participants must not be negative
CourseAcceptSubstitutesUntil: Accept substitute registrations until CourseAcceptSubstitutesUntil: Accept substitute registrations until
CourseAcceptSubstitutesUntilTip: Until which time should substitute registrations through the central allocation be accepted to fill free places in the course? If left empty no substitute registrations will be made. This deadline should not arbitrarily be set early or ommitted so as to not be an unneccesarily restrictive for students. For a seminar a valid choice might be a few hours before the first meeting in which topics will be assigned. CourseAcceptSubstitutesUntilTip: Until which time should substitute registrations through the central allocation be accepted to fill free places in the course? If left empty no substitute registrations will be made. This deadline should not arbitrarily be set early or ommitted so as to not be an unneccesarily restrictive for students. For a seminar a valid choice might be a few hours before the first meeting in which topics will be assigned.
CourseDeregisterNoShow: Record “no show” when deregistering CourseDeregisterNoShow: Record “no show” when deregistering
@ -53,18 +43,6 @@ CourseVisibleToTip: Other users will be able to see the course from "Visible Fro
CourseMaterialFree: Course material is publicly accessible CourseMaterialFree: Course material is publicly accessible
CourseFormSectionRegistration: Registration CourseFormSectionRegistration: Registration
CourseFormSectionAdministration: Administration CourseFormSectionAdministration: Administration
CourseApplicationInstructions: Instructions for application
CourseApplicationInstructionsTip: Will be shown to students if they decide to apply for this course
CourseApplicationRequired: Applications required
CourseApplicationRequiredTip: Should registrations for this course be provisional at first (without capacity constraint), until they are approved by a course administrator?
CourseApplicationTemplate: Application template
CourseApplicationTemplateTip: Students can download this template if they decide to apply for this course
CourseApplicationsText: Text application
CourseApplicationsTextTip: Should students submit plaintext with their application/registration (in addition to submitted files if applicable)?
CourseApplicationsFiles: Application files
CourseApplicationsFilesTip: Should students submit files with their application/registration (in addition to plaintext if applicable)?
CourseApplicationRatingsVisible: Feedback to applications
CourseApplicationRatingsVisibleTip: Should students be allowed to view rating and comments on their application after the rating period?
CourseCapacity: Capacity CourseCapacity: Capacity
CourseCapacityTip: Maximum permissable number of enrolments for this course; leave empty for unlimited capacity CourseCapacityTip: Maximum permissable number of enrolments for this course; leave empty for unlimited capacity
CourseSecretTip: Enrollment for this course will require the password, if set CourseSecretTip: Enrollment for this course will require the password, if set
@ -80,15 +58,9 @@ CourseDeregisterUntilTip: Participants may deregister from immediately after reg
CourseVisibilityEndMustBeAfterStart: The end of the visibility period must be after its start CourseVisibilityEndMustBeAfterStart: The end of the visibility period must be after its start
CourseRegistrationEndMustBeAfterStart: The end of the registration period must be after its start CourseRegistrationEndMustBeAfterStart: The end of the registration period must be after its start
CourseDeregistrationEndMustBeAfterStart: The end of the deregistration period must be after the start of the registration period CourseDeregistrationEndMustBeAfterStart: The end of the deregistration period must be after the start of the registration period
CourseAllocationRequiresCapacity: Course capacity needs to be specified if the course participates in a central allocation
CourseAllocationTermMustMatch: Course semester needs to match the semester of the central allocation
CourseUserMustBeLecturer: The current user needs to be a course administrator CourseUserMustBeLecturer: The current user needs to be a course administrator
CourseAllocationCapacityMayNotBeChanged: The capacity of a course that participates in a central allocation must not be altered
AllocationStaffRegisterToExpiredAllocation: The course registration period for the central allocation is over. Participation may not be changed.
AllocationStaffRegisterToExpiredMinCapacity: The course registration period for the central allocation is over. Minimum capacity may not be changed.
CourseShorthandTooLong: Long course shorthands may lead to display issues and might complicate communication with students. Please choose a more concise shorthand if possible. CourseShorthandTooLong: Long course shorthands may lead to display issues and might complicate communication with students. Please choose a more concise shorthand if possible.
CourseNotAlwaysVisibleDuringRegistration: To allow for students to register, the course should also be visible during the entire registration period (which is currently not the case). CourseNotAlwaysVisibleDuringRegistration: To allow for students to register, the course should also be visible during the entire registration period (which is currently not the case).
CourseApplicationInstructionsRecommended: Students can, as per the current course settings, submit files and/or texts with their applications/registrations. There are, however, no instructions for application or template files. You should either not require files/texts or clarify through instructions or templates what is expected of the students.
NoSuchTerm tid: Semester #{tid} does not exist. NoSuchTerm tid: Semester #{tid} does not exist.
NoSuchSchool ssh: Department #{ssh} does not exist. NoSuchSchool ssh: Department #{ssh} does not exist.
NoSuchCourseShorthand csh: There is no course with shorthand #{csh}. NoSuchCourseShorthand csh: There is no course with shorthand #{csh}.
@ -108,23 +80,31 @@ CourseParticipantEnlistDirectly: Enrol known users directly
CourseSubmissionGroup: Registered submission group CourseSubmissionGroup: Registered submission group
SubmissionGroupEmptyIsUnsetTip: Leave empty to remove users from their respective submission groups SubmissionGroupEmptyIsUnsetTip: Leave empty to remove users from their respective submission groups
CourseParticipantsRegisterHeading: Add course participants CourseParticipantsRegisterHeading: Add course participants
CourseParticipantsRegisterActionAddParticipants: Add course participants
CourseParticipantsRegisterActionAddTutorialMembers: Add course and tutorial participants
CourseParticipantsRegisterUsersField: Persons to register for course
CourseParticipantsRegisterUsersFieldTip: Please enter personal number. Please separate multiple entries with commas.
CourseParticipantsRegisterTutorialOption: Register course participants for tutorial?
CourseParticipantsRegisterTutorialField: Tutorial
CourseParticipantsRegisterTutorialFieldTip: If there is no tutorial with this name, a new one will be created. If there is a tutorial with this name, the course participants will be registered for it.
CourseParticipantsRegisterNoneGiven: No persons given to register!
CourseParticipantsRegisterNotFoundInAvs n: For #{n} #{pluralEN n "entry no corresponding person" "entries no corresponding persons"} could be found in AVS
CourseParticipantsRegisterUnnecessary: All requested registrations have already been saved. No actions have been performed.
CourseParticipantsInvited n: #{n} #{pluralEN n "invitation" "invitations"} sent via email CourseParticipantsInvited n: #{n} #{pluralEN n "invitation" "invitations"} sent via email
CourseParticipantsAlreadyRegistered n: #{n} #{pluralEN n "participant is" "participants are"} already enrolled CourseParticipantsAlreadyRegistered n: #{n} #{pluralEN n "participant is" "participants are"} already course #{pluralEN n "member" "members"}
CourseParticipantsRegistered n: Successfully registered #{n} #{pluralEN n "participant" "participants"} CourseParticipantsAlreadyTutorialMember n: #{n} #{pluralEN n "participant is" "participants are"} already registered for this tutorial
CourseApplicationText: Application text CourseParticipantsRegistered n: Successfully registered #{n} #{pluralEN n "participant" "participants"} for course
CourseApplicationFollowInstructions: Please follow the instructions for applications! CourseParticipantsRegisteredTutorial n: Successfully registered #{n} #{pluralEN n "participant" "participants"} for tutorial
CourseParticipantsRegisterConfirmationHeading: Register participants
CourseParticipantsRegisterConfirmInvalid: Invalid confirmation form!
CourseRegistrationText: Registration text CourseRegistrationText: Registration text
CourseRegistrationFollowInstructions: Please follow the instructions for registrations! CourseRegistrationFollowInstructions: Please follow the instructions for registrations!
CourseRegistrationFiles: Registration file(s) CourseRegistrationFiles: Registration file(s)
CourseApplicationNoFiles: No file(s)
CourseApplicationFilesNeedReupload: Application files need to be reuploaded every time the application is changed
CourseRegistrationFilesNeedReupload: Registration files need to be reuploaded every time the registration is changed CourseRegistrationFilesNeedReupload: Registration files need to be reuploaded every time the registration is changed
CourseApplicationFile: Application
CourseApplicationFiles: Application file(s)
CourseApplicationArchive: Zip archive of application files
CourseRegistrationFile: Registration file CourseRegistrationFile: Registration file
CourseRegistrationArchive: Zip archive of registration files CourseRegistrationArchive: Zip archive of registration files
CourseDeregistrationAllocationLog: Your enrollment in this course is due to a central allocation. If you leave the course, this will be permanently recorded and might affect you negatively in future central allocations. If you have good reasons why you should not be held accountable for leaving the course, please contact a course administrator. Course administrators can deregister you without incurring a permanent record.
CourseDeregistrationNoShow: If you deregister from this course “no show” will be recorded as your exam achievement for all exams associated with this course. If you have good reasons why you shold not be held accountable for leaving the course, please contact a course administrator. Course administrators can deregister you without incurring a permanent record. CourseDeregistrationNoShow: If you deregister from this course “no show” will be recorded as your exam achievement for all exams associated with this course. If you have good reasons why you shold not be held accountable for leaving the course, please contact a course administrator. Course administrators can deregister you without incurring a permanent record.
CourseDeregistrationFromInvisibleCourse: This course is only visible to enrolled participants and applicants. If you deregister now, you will not be able to access the course again! CourseDeregistrationFromInvisibleCourse: This course is only visible to enrolled participants and applicants. If you deregister now, you will not be able to access the course again!
CourseDeregistrationNoReRegistration: If you deregister from the course now, you will not be able to re-register yourself. CourseDeregistrationNoReRegistration: If you deregister from the course now, you will not be able to re-register yourself.
@ -134,15 +114,8 @@ CourseRegisterOk: Successfully enrolled for course
CourseDeregisterOk: Successfully left course CourseDeregisterOk: Successfully left course
CourseApplyOk: Successfully applied for course CourseApplyOk: Successfully applied for course
CourseRetractApplyOk: Successfully retracted application for course CourseRetractApplyOk: Successfully retracted application for course
CourseApplicationTemplateArchiveName tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-application-templates
CourseMemberOf: Participant of CourseMemberOf: Participant of
CourseAssociatedWith: associated with CourseAssociatedWith: associated with
CourseDeregistrationAllocationReason: Reason
CourseDeregistrationAllocationReasonTip: The specified reason will be permanently stored and might be the only information available during conflict resolution
CourseDeregistrationAllocationNoShow: Record as “no show”
CourseDeregistrationAllocationNoShowTip: Should, for all exams associated with this course, “no show” be recorded as the exam achievement automatically? This would be done once immediately (if no other achievement exists for the given exam) and automatically whenever a new exam is created.
CourseDeregistrationAllocationShouldLog: Self imposed
CourseDeregistrationAllocationShouldLogTip: If the participant was enrolled in this course due to a central allocation, it is intended that a permanent record be made that might affect the student negatively in future central allocations. As a course administrator you have the right to prevent this if the participant can present good reasons why them leaving the course is not self imposed.
CourseLastEdit: Latest edit CourseLastEdit: Latest edit
CourseUserNote: Note CourseUserNote: Note
CourseUserNoteTooltip: Only visible to administrators of this course CourseUserNoteTooltip: Only visible to administrators of this course
@ -201,16 +174,10 @@ CourseParticipantsCount n: #{n}
CourseParticipantsCountOf n m: #{n} of #{m} CourseParticipantsCountOf n m: #{n} of #{m}
CourseVisibility: Visibility CourseVisibility: Visibility
CourseInvisible: This course is currently only visible to lecturers, assistants, tutors, correctors, enrolled participants and applicants. CourseInvisible: This course is currently only visible to lecturers, assistants, tutors, correctors, enrolled participants and applicants.
CourseInvisibleOverridenByAllocation: Because the allocation this course participates in is currently open for application, the course is forced to be visible. After the application phase, the course will only be visible to lecturers, assistants, tutors, correctors, enrolled participants and applicants.
CourseRegistrationInterval: Enrolment CourseRegistrationInterval: Enrolment
CourseDirectRegistrationInterval: Direct enrolment CourseDirectRegistrationInterval: Direct enrolment
CourseDeregisterUntil time: Deregistration only until #{time} CourseDeregisterUntil time: Deregistration only until #{time}
CourseApplicationInstructionsApplication: Instructions for application
CourseApplicationInstructionsRegistration: Instructions for registration
CourseApplicationTemplateApplication: Application template(s)
CourseApplicationTemplateRegistration: Registration template(s)
NotRegistered: Note enrolled for this course NotRegistered: Note enrolled for this course
CourseApplicationDeleteToEdit: You need to withdraw your application and reapply to edit your application.
CourseMaterial: Material CourseMaterial: Material
CourseMaterialNotFree: Course material is only accessible to members of the course, e.g. for participants, tutors, correctors or administratiors. CourseMaterialNotFree: Course material is only accessible to members of the course, e.g. for participants, tutors, correctors or administratiors.
CourseMaterialsFoundHere: Material for this course is available here CourseMaterialsFoundHere: Material for this course is available here
@ -235,17 +202,8 @@ StudyTerms: Fields of study
NoStudyTermsKnown: No known features of study NoStudyTermsKnown: No known features of study
CourseMembersCountOf n mbNum: #{n} #{maybeToMessage "of " mbNum " "}participants CourseMembersCountOf n mbNum: #{n} #{maybeToMessage "of " mbNum " "}participants
#template course/application
CourseAllocationsBounds n: Expected number of alloctions due to #{pluralEN n "central allocation" "central allocations"}
CourseAllocationsBoundCoincide numFirstChoice: Est. #{numFirstChoice} #{pluralEN numFirstChoice "participant" "participants"}
CourseAllocationsBound numApps numFirstChoice: Est. between #{numFirstChoice} and #{numApps} #{pluralEN numApps "participant" "participants"}
CourseAllocationsBoundCapped: The numbers listed above were modified based on the currently configured course capacity.
CourseAllocationsBoundWarningOpen: The information listed above represents only the current state of applications and is subject to change.
CourseApplications: Applications
CourseLoginToApply: You need to login to Uni2work before you can apply for this course. CourseLoginToApply: You need to login to Uni2work before you can apply for this course.
CourseLoginToRegister: Your need to login to Uni2work before you can register for this course. CourseLoginToRegister: Your need to login to Uni2work before you can register for this course.
CourseAllApplicationsArchiveName tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-applications
CourseLecInviteHeading courseName: Invitation to be a course administrator for #{courseName} CourseLecInviteHeading courseName: Invitation to be a course administrator for #{courseName}
CourseLecInviteExplanation: You were invited to be a course administrator. CourseLecInviteExplanation: You were invited to be a course administrator.
CourseUserHasPersonalisedSheetFilesFilter: Participant has personalised sheet files for CourseUserHasPersonalisedSheetFilesFilter: Participant has personalised sheet files for
@ -260,13 +218,6 @@ CourseUserSetSubmissionGroup: Set group-submission
CourseUserReRegister: Reregister participant CourseUserReRegister: Reregister participant
CourseUserDownloadPersonalisedSheetFiles: Download personalised sheets CourseUserDownloadPersonalisedSheetFiles: Download personalised sheets
CourseApplicationsTableCsvSetVeto: Set veto
CourseApplicationsTableCsvSetRating: Set rating
CourseApplicationsTableCsvSetComment: Set comment
CourseApplicationsTableCsvExceptionNoMatchingUser: No matching user
CourseApplicationsTableCsvExceptionNoMatchingAllocation: No matching allocation
CourseApplicationsTableCsvExceptionNoMatchingStudyFeatures: no matching study features
CoursePersonalisedSheetFilesArchiveName tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-personalised_files CoursePersonalisedSheetFilesArchiveName tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-personalised_files
TutorialFreeCapacity: Free capacity TutorialFreeCapacity: Free capacity
@ -285,3 +236,7 @@ CourseExamRegistrationTime: Registered since
CourseParticipantStateIsActiveFilter: View CourseParticipantStateIsActiveFilter: View
CourseApply: Apply for course CourseApply: Apply for course
CourseAdministrator: Course administrator CourseAdministrator: Course administrator
CourseAvsRegisterTitle: Register participants
CourseAvsRegisterParticipants: Participants
CourseAvsRegisterParticipantsTip: Separate multiple participants with comma

View File

@ -44,4 +44,6 @@ TutorCorrectorInvitationAccepted shn@SheetName: Sie wurden als Korrektor:in für
TutorialUsersDeregistered count@Int64: #{show count} #{pluralDE count "-Tutorium-Teilnehmer:in" "Tutorium-Teilnehmer:innen" } abgemeldet TutorialUsersDeregistered count@Int64: #{show count} #{pluralDE count "-Tutorium-Teilnehmer:in" "Tutorium-Teilnehmer:innen" } abgemeldet
TutorialUserDeregister: Vom Tutorium Abmelden TutorialUserDeregister: Vom Tutorium Abmelden
TutorialUserSendMail: Mitteilung verschicken TutorialUserSendMail: Mitteilung verschicken
TutorialUserGrantQualification: Qualifikation vergeben
TutorialUserGrantedQualification n@Int: Qualifikation erfolgreich an #{tshow n} Tutoriums-#{pluralDE n "Teilnehmer:in" "Teilnehmer:innen"} vergeben
CommTutorial: Tutorium-Mitteilung CommTutorial: Tutorium-Mitteilung

View File

@ -45,4 +45,6 @@ TutorialUsersDeregistered count: Successfully deregistered #{show count} partici
TutorialUserDeregister: Deregister from tutorial TutorialUserDeregister: Deregister from tutorial
TutorialUserSendMail: Send mail TutorialUserSendMail: Send mail
TutorialUserGrantQualification: Grant Qualification
TutorialUserGrantedQualification n: Successfully granted qualification #{tshow n} tutorial #{pluralEN n "user" "users"}
CommTutorial: Tutorial message CommTutorial: Tutorial message

View File

@ -9,7 +9,6 @@ InfoLecturerCourses: Veranstaltungen
InfoLecturerExercises: Übungsbetrieb InfoLecturerExercises: Übungsbetrieb
InfoLecturerTutorials: Tutorien InfoLecturerTutorials: Tutorien
InfoLecturerExams: Prüfungen InfoLecturerExams: Prüfungen
InfoLecturerAllocations: Zentralanmeldungen
LecturerInfoTooltipNew: Neues Feature LecturerInfoTooltipNew: Neues Feature
LecturerInfoTooltipProblem: Feature mit bekannten Problemen LecturerInfoTooltipProblem: Feature mit bekannten Problemen
LecturerInfoTooltipPlanned: Geplantes Feature LecturerInfoTooltipPlanned: Geplantes Feature
@ -20,7 +19,6 @@ VersionHistory: Versionsgeschichte
KnownBugs: Bekannte Bugs KnownBugs: Bekannte Bugs
ImplementationDetails: Implementierung ImplementationDetails: Implementierung
Clone: Klonen Clone: Klonen
Applicant: Bewerber:in
Administrator: Administrator:in Administrator: Administrator:in
CommCourse: Kursmitteilung CommCourse: Kursmitteilung
Corrector: Korrektor:in Corrector: Korrektor:in

View File

@ -9,7 +9,6 @@ InfoLecturerCourses: Courses
InfoLecturerExercises: Course Exercises InfoLecturerExercises: Course Exercises
InfoLecturerTutorials: Tutorials InfoLecturerTutorials: Tutorials
InfoLecturerExams: Exams InfoLecturerExams: Exams
InfoLecturerAllocations: Central allocations
LecturerInfoTooltipNew: New feature LecturerInfoTooltipNew: New feature
LecturerInfoTooltipProblem: Feature with known issues LecturerInfoTooltipProblem: Feature with known issues
LecturerInfoTooltipPlanned: Planned feature LecturerInfoTooltipPlanned: Planned feature
@ -21,7 +20,6 @@ KnownBugs: Known bugs
ImplementationDetails: Implementation ImplementationDetails: Implementation
Clone: Cloning Clone: Cloning
Administrator: Administrator Administrator: Administrator
Applicant: Applicant
CommCourse: Course message CommCourse: Course message
Corrector: Corrector Corrector: Corrector
DefinitionCourseEvents: Course occurrences DefinitionCourseEvents: Course occurrences

View File

@ -15,7 +15,6 @@ ResetPassword: FRADrive-Passwort ändern bzw. setzen
MailSubjectChangeUserDisplayEmail: Diese E-Mail-Adresse in FRADrive veröffentlichen MailSubjectChangeUserDisplayEmail: Diese E-Mail-Adresse in FRADrive veröffentlichen
MailIntroChangeUserDisplayEmail displayEmail@UserEmail: Der oben genannte Benutzer/Die oben genannte Benutzerin möchte „#{displayEmail}“ als öffentliche Adresse, assoziiert mit sich selbst, angeben. Wenn Sie diese Aktion nicht selbst ausgelöst haben, ignorieren Sie diese Mitteilung bitte! MailIntroChangeUserDisplayEmail displayEmail@UserEmail: Der oben genannte Benutzer/Die oben genannte Benutzerin möchte „#{displayEmail}“ als öffentliche Adresse, assoziiert mit sich selbst, angeben. Wenn Sie diese Aktion nicht selbst ausgelöst haben, ignorieren Sie diese Mitteilung bitte!
MailTitleChangeUserDisplayEmail displayName@Text: #{displayName} möchte diese E-Mail-Adresse in FRADrive veröffentlichen MailTitleChangeUserDisplayEmail displayName@Text: #{displayName} möchte diese E-Mail-Adresse in FRADrive veröffentlichen
AllocationResultsStudentConsultFaq n@Int: Falls Sie Fragen oder Anmerkungen haben, beachten Sie bitte auch die Informationen auf #{pluralDE n "der" "den"} folgenden #{pluralDE n "Seite" "Seiten"}:
CommCourseSubject: Kursmitteilung CommCourseSubject: Kursmitteilung
InvitationAcceptDecline: Einladung annehmen/ablehnen InvitationAcceptDecline: Einladung annehmen/ablehnen
InvitationFromTip displayName@Text: Sie erhalten diese Einladung, weil #{displayName} ihren Versand in FRADrive ausgelöst hat. InvitationFromTip displayName@Text: Sie erhalten diese Einladung, weil #{displayName} ihren Versand in FRADrive ausgelöst hat.

View File

@ -15,7 +15,6 @@ ResetPassword: Reselt FRADrive password
MailSubjectChangeUserDisplayEmail: Publishing this email address in FRADrive MailSubjectChangeUserDisplayEmail: Publishing this email address in FRADrive
MailIntroChangeUserDisplayEmail displayEmail: The user mentioned above wants to publish “#{displayEmail}” as their own email address. If you have not caused this email to be sent, please ignore it! MailIntroChangeUserDisplayEmail displayEmail: The user mentioned above wants to publish “#{displayEmail}” as their own email address. If you have not caused this email to be sent, please ignore it!
MailTitleChangeUserDisplayEmail displayName: #{displayName} wants to publish this email address as their own in FRADrive MailTitleChangeUserDisplayEmail displayName: #{displayName} wants to publish this email address as their own in FRADrive
AllocationResultsStudentConsultFaq n@Int: If you have questions or remarks, please also take into account the information on the following #{pluralEN n "page" "pages"}:
CommCourseSubject: Course message CommCourseSubject: Course message
InvitationAcceptDecline: Accept/Decline invitation InvitationAcceptDecline: Accept/Decline invitation
InvitationFromTip displayName: You are receiving this invitation because #{displayName} has caused it to be sent from within FRADrive. InvitationFromTip displayName: You are receiving this invitation because #{displayName} has caused it to be sent from within FRADrive.

View File

@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
SystemMessageLastChangedAt time@Text: Zuletzt geändert: #{time} SystemMessageLastChangedAt time@Text: Zuletzt geändert: #{time}
NewsOpenAllocations: Offene Zentralanmeldungen
NewsUpcomingSheets: Anstehende Übungsblätter NewsUpcomingSheets: Anstehende Übungsblätter
NewsUpcomingExams: Bevorstehende Prüfungen NewsUpcomingExams: Bevorstehende Prüfungen
NewsHideHiddenSystemMessages: Versteckte Nachrichten nicht mehr anzeigen NewsHideHiddenSystemMessages: Versteckte Nachrichten nicht mehr anzeigen
@ -17,10 +16,4 @@ Done: Eingereicht
SubmissionNew: Abgabe anlegen SubmissionNew: Abgabe anlegen
NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter
NoUpcomingExams difftime@Text: In den nächsten #{difftime} gibt es keine Prüfungen oder ablaufende Prüfungsanmeldungen in Ihren Kursen NoUpcomingExams difftime@Text: In den nächsten #{difftime} gibt es keine Prüfungen oder ablaufende Prüfungsanmeldungen in Ihren Kursen
CourseParticipant: Teilnehmer:in CourseParticipant: Teilnehmer:in
NewsActiveAllocations: Daten zu aktiven Zentralanmeldungen für Dozenten
NewsActiveAllocationsPlaces: Plätze
NewsActiveAllocationsApplicants: Bewerber
NewsActiveAllocationsPlacementsMade: Zugeteilte Plätze
NewsActiveAllocationsApplicantsPlaced: Zugeteilte Bewerber

View File

@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
SystemMessageLastChangedAt time: Last changed: #{time} SystemMessageLastChangedAt time: Last changed: #{time}
NewsOpenAllocations: Active central allocations
NewsUpcomingSheets: Upcoming exercise sheets NewsUpcomingSheets: Upcoming exercise sheets
NewsUpcomingExams: Upcoming exams NewsUpcomingExams: Upcoming exams
NewsHideHiddenSystemMessages: Don't show hidden news items NewsHideHiddenSystemMessages: Don't show hidden news items
@ -18,9 +17,3 @@ SubmissionNew: Create submission
NoUpcomingSheetDeadlines: No upcoming sheets NoUpcomingSheetDeadlines: No upcoming sheets
NoUpcomingExams difftime: No exams for your courses occur or allow registration in the next #{difftime} NoUpcomingExams difftime: No exams for your courses occur or allow registration in the next #{difftime}
CourseParticipant: Participant CourseParticipant: Participant
NewsActiveAllocations: Information on active allocations for lecturers
NewsActiveAllocationsPlaces: Places
NewsActiveAllocationsApplicants: Applicants
NewsActiveAllocationsPlacementsMade: Placements made
NewsActiveAllocationsApplicantsPlaced: Applicants placed

View File

@ -80,4 +80,8 @@ MppPin !ident-ok: Pin
MppRecipient: Empfänger MppRecipient: Empfänger
MppAddress: Adresse MppAddress: Adresse
MppLang: Sprache MppLang: Sprache
MppBadLanguage: Sprache muss derzeit "de" oder "en" sein. MppBadLanguage: Sprache muss derzeit "de" oder "en" sein.
LmsAutomaticQueuing n@Natural: Die folgenden Funktionen werden normalerweise einmal pro Tag um #{show n} Uhr ausgeführt.
LmsManualQueuing: Die folgenden Funktionen sollten einmal pro Tag ausgeführt werden.
BtnLmsEnqueue: Nutzer mit ablaufenden Qualifikationen zum E-Learning anmelden und benachrichtigen
BtnLmsDequeue: Nutzer mit beendetem E-Learning ggf. benachrichtigen und aufräumen

View File

@ -80,4 +80,8 @@ MppPin: Pin
MppRecipient: Recipient MppRecipient: Recipient
MppAddress: Address MppAddress: Address
MppLang: Language MppLang: Language
MppBadLanguage: Language currently restricted to "en" or "de". MppBadLanguage: Language currently restricted to "en" or "de".
LmsAutomaticQueuing n@Natural: The following functions are executed daily at #{show n} o'clock.
LmsManualQueuing: The following functions should be executed daily.
BtnLmsEnqueue: Enqueue users with expiring qualifications for e-learning and notify them.
BtnLmsDequeue: Dequeue users with finished e-learning and notify, if appropriate.

View File

@ -27,7 +27,6 @@ SchoolExists ssh@SchoolId: Institut „#{ssh}“ existiert bereits
SchoolLecturer: Dozent:in SchoolLecturer: Dozent:in
SchoolEvaluation: Kursumfragenverwaltung SchoolEvaluation: Kursumfragenverwaltung
SchoolExamOffice: Prüfungsverwaltung SchoolExamOffice: Prüfungsverwaltung
SchoolAllocation: Zentralanmeldungs-Administration
SchoolAdmin !ident-ok: Admin SchoolAdmin !ident-ok: Admin
SchoolAuthorshipStatementSection: Eigenständigkeitserklärungen SchoolAuthorshipStatementSection: Eigenständigkeitserklärungen

View File

@ -28,7 +28,6 @@ SchoolAdmin: Admin
SchoolLecturer: Lecturer SchoolLecturer: Lecturer
SchoolEvaluation: Course evaluation SchoolEvaluation: Course evaluation
SchoolExamOffice: Exam office SchoolExamOffice: Exam office
SchoolAllocation: Administration of central allocations
SchoolAuthorshipStatementSection: Statements of Authorship SchoolAuthorshipStatementSection: Statements of Authorship
SchoolAuthorshipStatementSheetMode: Mode for exam-unrelated exercise sheet submissions SchoolAuthorshipStatementSheetMode: Mode for exam-unrelated exercise sheet submissions

View File

@ -2,61 +2,6 @@
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
#allocation.hs + templates in scope
MailSubjectAllocationStaffRegister allocationSchool@SchoolId allocation@AllocationName: Sie können nun Kurse für die Zentralameldung #{allocationSchool}: „#{allocation}“ registrieren
MailSubjectAllocationStaffRegisterMultiple n@Int: Sie können nun Kurse für #{n} Zentralameldungen registrieren
MailSubjectAllocationRegister allocationSchool@SchoolId allocation@AllocationName: Es kann sich nun für Kurse der Zentralameldung #{allocationSchool}: „#{allocation}“ beworben werden
MailSubjectAllocationRegisterMultiple n@Int: Es kann sich nun für Kurse für #{n} Zentralanmeldungen beworben werden
MailSubjectAllocationAllocation allocationSchool@SchoolId allocation@AllocationName: Sie können nun (optional) Bewerbungen für ihre Kurse in der Zentralanmeldung #{allocationSchool}: „#{allocation}“ bewerten
MailSubjectAllocationAllocationMultiple n@Int: Sie können nun (optional) Bewerbungen für ihre Kurse in #{n} Zentralanmeldungen bewerten
MailSubjectAllocationUnratedApplications allocationSchool@SchoolId allocation@AllocationName: Sie können nun (optional) Bewerbungen für ihre Kurse in der Zentralanmeldung #{allocationSchool}: „#{allocation}“ bewerten
MailSubjectAllocationUnratedApplicationsMultiple n@Int: Sie können nun (optional) Bewerbungen für ihre Kurse in #{n} Zentralanmeldungen bewerten
MailSubjectAllocationNewCourse allocation@AllocationName: Es wurde ein zusätzlicher Kurs zur Zentralanmeldung „#{allocation}” eingetragen
MailSubjectAllocationResults allocation@AllocationName: Plätze für Zentralanmeldung „#{allocation}“ wurden verteilt
AllocationResultLecturer csh@CourseShorthand count@Int64 count2@Int64: #{count} Teilnehmer:innen (von insgesamt #{count2}) für #{csh}
AllocationResultLecturerAll csh@CourseShorthand count@Int64: #{count} Teilnehmer:innen für #{csh}
AllocationResultLecturerNone csh@CourseShorthand: Keine Teilnehmer:innen für #{csh}
MailAllocationStaffRegisterIntroMultiple n@Int: Sie können nun Kurse für die folgenden #{n} Zentralameldungen registrieren:
MailAllocationStaffRegisterNewCourse: Sie können auf der unten aufgeführten Seite neue Kurse in FRADrive anlegen. Hierbei haben Sie die Möglichkeit anzugeben, dass der Kurs an einer Zentralanmeldung teilnimmt.
MailAllocationStaffRegisterDeadline n@Int deadline@Text: Bitte beachten Sie, dass alle Kurse, die an #{pluralDE n "dieser Zentralanmeldung" "diesen Zentralanmeldungen"} teilnehmen, bis #{deadline} eingetragen sein müssen.
MailAllocationStaffRegisterDeadlineMultiple: Bitte beachten Sie, dass alle Kurse, die an einer dieser Zentralanmeldungen teilnehmen, bis Ende der jeweiligen Regstrierungsphase (siehe unten) eingetragen sein müssen.
MailAllocationStaffRegisterDeadlineSingle deadline@Text: Registrierungsphase endet #{deadline}
MailAllocationStaffRegisterDeadlineSingleNothing: Aktuell kein Ende der Registrierungsphase festgelegt
MailAllocationSchoolAndName allocationSchool@SchoolId allocation@AllocationName: #{allocationSchool}: „#{allocation}“
CourseNew: Neuen Kurs anlegen
MailAllocationRegisterIntroMultiple n@Int: Es kann sich nun für Kurse für die folgenden #{n} Zentralanmeldungen beworben werden:
MailAllocationRegister n@Int: Es kann sich nun, auf #{pluralDE n "der unten aufgeführten Seite" "den unten aufgeführten Seiten"}, für alle Kurse der #{pluralDE n "Zentralanmeldung" "Zentralanmeldungen"} jeweils einzeln beworben werden.
MailAllocationRegisterDeadline deadline@Text: Bitte beachten Sie, dass alle Bewerbungen bis #{deadline} eingegangen sein müssen.
MailAllocationRegisterDeadlineMultiple: Bitte beachten Sie, dass alle Bewerbungen bis Ende der jeweiligen Bewerbungsphase (siehe unten) eingegangen sein müssen.
MailAllocationRegisterDeadlineSingle deadline@Text: Bewerbungsphase endet #{deadline}
MailAllocationRegisterDeadlineSingleNothing: Aktuell kein Ende der Bewerbungsphase festgelegt
MailAllocationAllocationIntroMultiple n@Int: Sie können nun (optional) Bewerbungen für ihre Kurse in #{n} Zentralanmeldungen bewerten:
MailAllocationAllocation n@Int: Sie können nun auf den unten aufgeführten Seiten Bewerbungen, die im Rahmen der #{pluralDE n "Zentralanmeldung" "Zentralanmeldungen"} an ihre Kurse gestellt wurden, bewerten. Die Bewertungen werden bei der Vergabe der Plätze berücksichtigt.
MailAllocationApplicationsMayChange deadline@Text: Bitte beachten Sie, dass Studierende noch bis #{deadline} Bewerbungen stellen, verändern und zurückziehen können. Bewerbungen, die sich nach ihrer Bewertung noch verändern, müssen neu bewertet werden.
MailAllocationApplicationsRegisterDeadline deadline@Text: Bewerbungsphase endet #{deadline}
MailAllocationApplicationsRegisterDeadlineNothing: Aktuell kein Ende der Bewerbungsphase festgelegt
MailAllocationApplicationsMayChangeMultiple: Bitte beachten Sie, dass Studierende noch bis Ende der Bewerbungsphase (siehe unten) der jeweiligen Zentralanmeldung Bewerbungen stellen, verändern und zurückziehen können. Bewerbungen, die sich nach ihrer Bewertung noch verändern, müssen neu bewertet werden.
MailAllocationAllocationDeadline deadline@Text: Bitte beachten Sie, dass alle Bewertungen bis #{deadline} erfolgt sein müssen.
MailAllocationApplicationsAllocationDeadline deadline@Text: Bewertungsphase endet #{deadline}
MailAllocationApplicationsAllocationDeadlineNothing: Aktuell keine Ende der Bewertungsphase festgelegt
MailAllocationAllocationDeadlineMultiple: Bitte beachten Sie, dass alle Bewertungen bis Ende der Bewertungsphase (siehe unten) erfolgt sein müssen.
MailAllocationUnratedApplicationsIntroMultiple n@Int: Sie können nun (optional) Bewerbungen für ihre Kurse in #{n} Zentralanmeldungen bewerten:
MailAllocationUnratedApplications n@Int: Für die unten aufgeführten Kurse liegen Bewerbungen vor, die im Rahmen der #{pluralDE n "Zentralanmeldung" "Zentralanmeldungen"} an den jeweiligen Kurs gestellt wurden, die entweder noch nicht bewertet wurden oder die nach der Bewertung noch verändert wurden, sodass die vorhandenen Bewertungen nun nicht mehr gültig sind.
MailAllocationUnratedApplicationsRatingIsOptional: Es steht Ihnen frei so viele oder so wenige Bewerbungen zu bewerten, wie Sie möchten (Sie können auch garkeine Bewerbungen bewerten).
MailAllocationUnratedApplicationsCount i@Natural: #{i} #{pluralDE i "Bewerbung" "Bewerbungen"}
AllocationResultsLecturer: Im Rahmen der oben genannten Zentralanmeldung wurden Plätze zugewiesen, wie folgt:
AllocationResultsLecturerSubstituteCoursesWarning: Bitte konfigurieren Sie so bald wie möglich einen Zeitrahmen in dem Sie bereit sind etwaige Nachrücker in den folgenden Kursen zu akzeptieren:
AllocationResultsStudent: Sie haben Plätze erhalten in:
AllocationNoResultsStudent: Sie haben leider keine Plätze erhalten.
AllocationResultStudent csh@CourseShorthand: Sie haben einen Platz in #{csh} erhalten.
AllocationResultsTip: Die folgenden Informationen entsprechen dem aktuellen Stand der Zentralanmeldung und können sich, z.B. durch die Verteilung von Plätzen an Nachrücker, noch ändern. Über zukünftige Änderungen, die Sie betreffen, werden Sie gesondert informiert.
AllocationResultsStudentTip: Unten aufgeführt sind alle Plätze, die Sie im Rahmen der genannten Zentralanmeldung erhalten haben und von denen Sie seit dem weder abgemeldet wurden, noch sich selbst abgemeldet haben. Plätze, über die Sie ggf. bereits informiert wurden, können also erneut aufgeführt sein.
AllocationResultStudentRegistrationTip: Sie sind zu oben genanntem Kurs in FRADrive angemeldet.
AllocationResultsStudentRegistrationTip: Sie sind zu den oben genannten Kursen in FRADrive angemeldet.
MailAllocationNewCourseTip: Es wurde der folgende Kurs zur Zentralanmeldung eingetragen:
MailAllocationNewCourseEditApplicationsHere: Sie können Ihre Bewerbung(en) hier anpassen:
MailAllocationNewCourseApplyHere: Sie können sich hier bewerben:
#correctionsAssigned.hs + templates #correctionsAssigned.hs + templates
MailSubjectCorrectionsAssigned csh@CourseShorthand sheetName@SheetName: Ihnen wurden Korrekturen zu #{sheetName} in #{csh} zugeteilt MailSubjectCorrectionsAssigned csh@CourseShorthand sheetName@SheetName: Ihnen wurden Korrekturen zu #{sheetName} in #{csh} zugeteilt
MailCorrectionsAssignedIntro courseName@Text termDesc@Text sheetName@SheetName n@Int: #{n} #{pluralDE n "Abgabe wurde" "Abgaben wurden"} Ihnen zur Korrektur für #{sheetName} im Kurs #{courseName} (#{termDesc}) zugeteilt. MailCorrectionsAssignedIntro courseName@Text termDesc@Text sheetName@SheetName n@Int: #{n} #{pluralDE n "Abgabe wurde" "Abgaben wurden"} Ihnen zur Korrektur für #{sheetName} im Kurs #{courseName} (#{termDesc}) zugeteilt.

View File

@ -2,61 +2,6 @@
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
#allocation.hs + templates in scope
MailSubjectAllocationStaffRegister allocationSchool allocation: You can now register courses for the central allocation #{allocationSchool}: “#{allocation}”
MailSubjectAllocationStaffRegisterMultiple n: You can now register courses for #{n} central allocations
MailSubjectAllocationRegister allocationSchool allocation: Applications can now be made for courses of the central allocation #{allocationSchool}: “#{allocation}”
MailSubjectAllocationRegisterMultiple n: Applications can now be made for courses of #{n} central allocations
MailSubjectAllocationAllocation allocationSchool allocation: You can now (optionally) rate applications for your courses that participate in the central allocation #{allocationSchool}: “#{allocation}”
MailSubjectAllocationAllocationMultiple n: You can now (optionally) rate applications for your courses that participate in #{n} central allocations
MailSubjectAllocationUnratedApplications allocationSchool allocation: You can now (optionally) rate applications for your courses that participate in the central allocation #{allocationSchool}: “#{allocation}”
MailSubjectAllocationUnratedApplicationsMultiple n: You can now (optionally) rate applications for your courses that participate in #{n} central allocations
MailSubjectAllocationNewCourse allocation: A new course was added to the central allocation “#{allocation}”
AllocationResultLecturer csh count count2: #{count} #{pluralEN count "participant" "participants"} (of #{count2}) for #{csh}
AllocationResultLecturerAll csh count: #{count} #{pluralEN count "participant" "participants"} for #{csh}
AllocationResultLecturerNone csh: No participants for #{csh}
MailAllocationStaffRegisterIntroMultiple n: You can now register courses for the following #{n} central allocations:
MailAllocationStaffRegisterNewCourse: You can create new courses in FRADrive on the site listed below. While doing so you can specify that the course should participate in a central allocation.
MailAllocationStaffRegisterDeadline n deadline: Please consider that all courses, that are to participate in #{pluralEN n "this central allocation" "these central allocations"}, must be registered before #{deadline}.
MailAllocationStaffRegisterDeadlineMultiple: Please consider that alle courses, that are to participate in these central allocations, must be registered before the ends of their respective course registration periods (see below).
MailAllocationStaffRegisterDeadlineSingle deadline: Course Registration period ends on #{deadline}
MailAllocationStaffRegisterDeadlineSingleNothing: Currently no end of course registration period configured
MailAllocationSchoolAndName allocationSchool allocation: #{allocationSchool}: “#{allocation}”
CourseNew: Create new course
MailAllocationRegisterIntroMultiple n: Applications can now be made for courses of the following #{n} central allocations:
MailAllocationRegister n: Applications can now be made for each of the courses participating in the central #{pluralEN n "allocation" "allocations"} on the #{pluralEN n "page" "pages"} listed below.
MailAllocationRegisterDeadline deadline: Please consider that all applications have to be made before #{deadline}.
MailAllocationRegisterDeadlineMultiple: Please consider that all applications for courses participating in central allocations have to be made before the ends of their respective application periods (see below).
MailAllocationRegisterDeadlineSingle deadline: Application periods ends on #{deadline}
MailAllocationRegisterDeadlineSingleNothing: Currently no end of application period configured
MailAllocationAllocationIntroMultiple n: You can now (optionally) rate applications for your courses that participate in #{n} central allocations:
MailAllocationAllocation n: You can now rate applications made in the context of the central #{pluralEN n "allocation" "allocations"} for your courses on the pages listed below. Ratings made will have an effect on the allocation.
MailAllocationApplicationsMayChange deadline: Please consider that applicants may change or delete their applications until #{deadline}. If an application was rated before it was changed it needs to be rated again.
MailAllocationApplicationsRegisterDeadline deadline: Application period ends on #{deadline}
MailAllocationApplicationsRegisterDeadlineNothing: Currently no end of application period configured
MailAllocationApplicationsMayChangeMultiple: Please consider that applicants may change or delete their applications until the end of the respective central allocation's application period. If an application was rated before it was changed it needs to be rated again.
MailAllocationAllocationDeadline deadline: Please consider that all ratings have to be made before #{deadline}.
MailAllocationApplicationsAllocationDeadline deadline: Rating period ends on #{deadline}
MailAllocationApplicationsAllocationDeadlineNothing: Currently no end of rating period configured
MailAllocationAllocationDeadlineMultiple: Please consider that all ratings have to be made before the end of the respective rating period (see below).
MailAllocationUnratedApplicationsIntroMultiple n: You can now (optionally) rate applications for your courses that participate in #{n} central allocations:
MailAllocationUnratedApplications n: For there courses listed below, there exist applications made in the context of #{pluralEN n "the central allocation" "one of the central allocations"} which have either not yet been rated or which have changed since they were rated.
MailAllocationUnratedApplicationsRatingIsOptional: You are free to rate as many or as few applications as you want to (you may also rate none of them).
MailAllocationUnratedApplicationsCount i: #{i} #{pluralDE i "application" "applications"}
MailSubjectAllocationResults allocation: Placements have been made for the central allocation “#{allocation}”
AllocationResultsLecturer: In the course of the central allocations placements have been made as follows:
AllocationResultsLecturerSubstituteCoursesWarning: Please configure a deadline up to which you are able to accept substitute registrations for the following courses as soon as possible:
AllocationResultsStudent: You have been placed in:
AllocationNoResultsStudent: Unfortunately you were not placed in any courses.
AllocationResultStudent csh: You were placed in #{csh}.
AllocationResultsTip: The following information reflect the current state of the allocation and are subject to change (e.g. when handling succession). You will be informed separately if any future changes concern you.
AllocationResultsStudentTip: Listed below are placements in courses which you have received due to the mentioned central allocation and for which you have not left the respective course or have been deregistered. Thus placements you have been informed of already may be listed again.
AllocationResultStudentRegistrationTip: You were enrolled in the course mentioned above in FRADrive.
AllocationResultsStudentRegistrationTip: You were enrolled in the courses mentioned above in FRADrive.
MailAllocationNewCourseTip: The following course was added to the central allocation:
MailAllocationNewCourseEditApplicationsHere: You can modify your application here:
MailAllocationNewCourseApplyHere: You can apply here:
#correctionsAssigned.hs + templates #correctionsAssigned.hs + templates
MailSubjectCorrectionsAssigned csh sheetName: You were assigned corrections for #{sheetName} of #{csh} MailSubjectCorrectionsAssigned csh sheetName: You were assigned corrections for #{sheetName} of #{csh}
MailCorrectionsAssignedIntro courseName termDesc sheetName n: You were assigned #{n} #{pluralEN n "correction" "corrections"} for #{sheetName} of #{courseName} (#{termDesc}). MailCorrectionsAssignedIntro courseName termDesc sheetName n: You were assigned #{n} #{pluralEN n "correction" "corrections"} for #{sheetName} of #{courseName} (#{termDesc}).

View File

@ -15,7 +15,6 @@ AuthTagExamOffice: Nutzer:in ist mit Prüfungsverwaltung beauftragt
AuthTagSystemExamOffice: Nutzer:in ist mit systemweiter Prüfungsverwaltung beauftragt AuthTagSystemExamOffice: Nutzer:in ist mit systemweiter Prüfungsverwaltung beauftragt
AuthTagSystemPrinter: Nutzer:in ist mit systemweiten Druck von Briefen beauftragt AuthTagSystemPrinter: Nutzer:in ist mit systemweiten Druck von Briefen beauftragt
AuthTagEvaluation: Nutzer:in ist mit Kursumfragenverwaltung beauftragt AuthTagEvaluation: Nutzer:in ist mit Kursumfragenverwaltung beauftragt
AuthTagAllocationAdmin: Nutzer:in ist mit der Administration von Zentralanmeldungen beauftragt
AuthTagToken: Nutzer:in präsentiert Authorisierungs-Token AuthTagToken: Nutzer:in präsentiert Authorisierungs-Token
AuthTagNoEscalation: Nutzer-Rechte werden nicht auf fremde Institute ausgeweitet AuthTagNoEscalation: Nutzer-Rechte werden nicht auf fremde Institute ausgeweitet
AuthTagDeprecated: Seite ist nicht überholt AuthTagDeprecated: Seite ist nicht überholt
@ -27,17 +26,14 @@ AuthTagTutor: Nutzer:in ist Tutor:in
AuthTagTutorControl: Tutorierende haben Kontrolle über ihre Tutorium AuthTagTutorControl: Tutorierende haben Kontrolle über ihre Tutorium
AuthTagTime: Zeitliche Einschränkungen sind erfüllt AuthTagTime: Zeitliche Einschränkungen sind erfüllt
AuthTagStaffTime: Zeitliche Einschränkungen für Lehrbeteiligte sind erfüllt AuthTagStaffTime: Zeitliche Einschränkungen für Lehrbeteiligte sind erfüllt
AuthTagAllocationTime: Zeitliche Einschränkungen durch Zentralanmeldung sind erfüllt
AuthTagCourseTime: Zeitliche Einschränkungen für Kurssichtbarkeit sind erfüllt AuthTagCourseTime: Zeitliche Einschränkungen für Kurssichtbarkeit sind erfüllt
AuthTagCourseRegistered: Nutzer:in ist Kursteilnehmer:in AuthTagCourseRegistered: Nutzer:in ist Kursteilnehmer:in
AuthTagAllocationRegistered: Nutzer:in nimmt an der Zentralanmeldung teil
AuthTagTutorialRegistered: Nutzer:in ist Tutoriumsteilnehmer:in AuthTagTutorialRegistered: Nutzer:in ist Tutoriumsteilnehmer:in
AuthTagExamRegistered: Nutzer:in ist Prüfungsteilnehmer:in AuthTagExamRegistered: Nutzer:in ist Prüfungsteilnehmer:in
AuthTagExamResult: Nutzer:in hat Prüfungsergebnisse AuthTagExamResult: Nutzer:in hat Prüfungsergebnisse
AuthTagExamOccurrenceRegistered: Nutzer:in ist für Prüfungsraum/-termin angemeldet AuthTagExamOccurrenceRegistered: Nutzer:in ist für Prüfungsraum/-termin angemeldet
AuthTagExamOccurrenceRegistration: Anmeldung zur Prüfung erfolgt inkl. Raum/Termin AuthTagExamOccurrenceRegistration: Anmeldung zur Prüfung erfolgt inkl. Raum/Termin
AuthTagParticipant: Nutzer:in ist mit Kurs assoziiert AuthTagParticipant: Nutzer:in ist mit Kurs assoziiert
AuthTagApplicant: Nutzer:in ist mit Bewerber zum Kurs
AuthTagRegisterGroup: Nutzer:in ist nicht Mitglied eines anderen Tutoriums mit der selben Registrierungs-Gruppe AuthTagRegisterGroup: Nutzer:in ist nicht Mitglied eines anderen Tutoriums mit der selben Registrierungs-Gruppe
AuthTagCapacity: Kapazität ist ausreichend AuthTagCapacity: Kapazität ist ausreichend
AuthTagEmpty: Ressource ist „leer“ AuthTagEmpty: Ressource ist „leer“

View File

@ -15,7 +15,6 @@ AuthTagExamOffice: User is part of an exam office
AuthTagSystemExamOffice: User is charged with system wide exam administration AuthTagSystemExamOffice: User is charged with system wide exam administration
AuthTagSystemPrinter: User is responsible for system wide letter printing AuthTagSystemPrinter: User is responsible for system wide letter printing
AuthTagEvaluation: User is charged with course evaluation AuthTagEvaluation: User is charged with course evaluation
AuthTagAllocationAdmin: User is charged with administration of central allocations
AuthTagToken: User is presenting an authorisation-token AuthTagToken: User is presenting an authorisation-token
AuthTagNoEscalation: User permissions are not being expanded to other departments AuthTagNoEscalation: User permissions are not being expanded to other departments
AuthTagDeprecated: Page is not deprecated AuthTagDeprecated: Page is not deprecated
@ -27,17 +26,14 @@ AuthTagTutor: User is tutor
AuthTagTutorControl: Tutors have control over their tutorial AuthTagTutorControl: Tutors have control over their tutorial
AuthTagTime: Time restrictions are fulfilled AuthTagTime: Time restrictions are fulfilled
AuthTagStaffTime: Time restrictions for teaching staff are fulfilled AuthTagStaffTime: Time restrictions for teaching staff are fulfilled
AuthTagAllocationTime: Time restrictions due to a central allocation are fulfilled
AuthTagCourseTime: Time restrictions for course visibility are fulfilled AuthTagCourseTime: Time restrictions for course visibility are fulfilled
AuthTagCourseRegistered: User is enrolled in course AuthTagCourseRegistered: User is enrolled in course
AuthTagAllocationRegistered: User participates in central allocation
AuthTagTutorialRegistered: User is tutorial participant AuthTagTutorialRegistered: User is tutorial participant
AuthTagExamRegistered: User is exam participant AuthTagExamRegistered: User is exam participant
AuthTagExamResult: User has an exam result AuthTagExamResult: User has an exam result
AuthTagExamOccurrenceRegistered: User is registered for exam occurrence/room AuthTagExamOccurrenceRegistered: User is registered for exam occurrence/room
AuthTagExamOccurrenceRegistration: Registration for exam is done including occurrence/room AuthTagExamOccurrenceRegistration: Registration for exam is done including occurrence/room
AuthTagParticipant: User participates in course AuthTagParticipant: User participates in course
AuthTagApplicant: User is applicant for course
AuthTagRegisterGroup: User is not participant in any tutorial of the same registration group AuthTagRegisterGroup: User is not participant in any tutorial of the same registration group
AuthTagCapacity: Capacity is sufficient AuthTagCapacity: Capacity is sufficient
AuthTagEmpty: Resource is “empty” AuthTagEmpty: Resource is “empty”

View File

@ -57,10 +57,7 @@ NotificationTriggerKindCourseLecturer: Für Kursverwalter:innen
NotificationTriggerKindAdmin: Für Administrator:innen NotificationTriggerKindAdmin: Für Administrator:innen
NotificationTriggerKindExamOffice: Für Prüfungsverwalter:innen NotificationTriggerKindExamOffice: Für Prüfungsverwalter:innen
NotificationTriggerKindEvaluation: Für Vorlesungsumfragen NotificationTriggerKindEvaluation: Für Vorlesungsumfragen
NotificationTriggerKindAllocationStaff: Für Zentralanmeldungen (Dozierende)
NotificationTriggerKindAllocationParticipant: Für Zentralanmeldungen
NotificationTriggerKindSubmissionUser: Für Mitabgebende einer Übungsblatt-Abgabe NotificationTriggerKindSubmissionUser: Für Mitabgebende einer Übungsblatt-Abgabe
NotificationTriggerKindAllocationAdmin: Für Administrator:innen von Zentralanmeldungen
NotificationTriggerSubmissionRatedGraded: Meine Abgabe in einem gewerteten Übungsblatt wurde korrigiert NotificationTriggerSubmissionRatedGraded: Meine Abgabe in einem gewerteten Übungsblatt wurde korrigiert
NotificationTriggerSubmissionRated: Meine Abgabe wurde korrigiert NotificationTriggerSubmissionRated: Meine Abgabe wurde korrigiert
@ -77,20 +74,12 @@ NotificationTriggerExamRegistrationActive: Ich kann mich für eine Prüfung anme
NotificationTriggerExamRegistrationSoonInactive: Ich kann mich bald nicht mehr für eine Prüfung anmelden NotificationTriggerExamRegistrationSoonInactive: Ich kann mich bald nicht mehr für eine Prüfung anmelden
NotificationTriggerExamDeregistrationSoonInactive: Ich kann mich bald nicht mehr von einer Prüfung abmelden NotificationTriggerExamDeregistrationSoonInactive: Ich kann mich bald nicht mehr von einer Prüfung abmelden
NotificationTriggerExamResult: Ich kann ein neues Prüfungsergebnis einsehen NotificationTriggerExamResult: Ich kann ein neues Prüfungsergebnis einsehen
NotificationTriggerAllocationStaffRegister: Ich kann Kurse bei einer neuen Zentralanmeldung eintragen
NotificationTriggerAllocationAllocation: Ich kann Zentralanmeldungs-Bewerbungen für einen meiner Kurse bewerten
NotificationTriggerAllocationRegister: Ich kann mich bei einer neuen Zentralanmeldung bewerben
NotificationTriggerAllocationOutdatedRatings: Zentralanmeldungs-Bewerbungen für einen meiner Kurse wurden verändert, nachdem sie bewertet wurden
NotificationTriggerAllocationUnratedApplications: Bewertungen zu Zentralanmeldungs-Bewerbungen für einen meiner Kurse stehen aus
NotificationTriggerAllocationResults: Plätze wurden für eine meiner Zentralanmeldungen verteilt
NotificationTriggerExamOfficeExamResults: Ich kann neue Prüfungsergebnisse einsehen NotificationTriggerExamOfficeExamResults: Ich kann neue Prüfungsergebnisse einsehen
NotificationTriggerExamOfficeExamResultsChanged: Prüfungsergebnisse wurden verändert NotificationTriggerExamOfficeExamResultsChanged: Prüfungsergebnisse wurden verändert
NotificationTriggerCourseRegistered: Ein:e Kursverwalter:in hat mich zu einem Kurs angemeldet NotificationTriggerCourseRegistered: Ein:e Kursverwalter:in hat mich zu einem Kurs angemeldet
NotificationTriggerSubmissionUserCreated: Ich wurde als Mitabgebender zu einer Übungsblatt-Abgabe hinzugefügt NotificationTriggerSubmissionUserCreated: Ich wurde als Mitabgebender zu einer Übungsblatt-Abgabe hinzugefügt
NotificationTriggerSubmissionEdited: Eine meiner Übungsblatt-Abgaben wurde verändert NotificationTriggerSubmissionEdited: Eine meiner Übungsblatt-Abgaben wurde verändert
NotificationTriggerSubmissionUserDeleted: Ich wurde als Mitabgebender von einer Übungsblatt-Abgabe entfernt NotificationTriggerSubmissionUserDeleted: Ich wurde als Mitabgebender von einer Übungsblatt-Abgabe entfernt
NotificationTriggerAllocationNewCourse: Es wurde ein neuer Kurs eingetragen zu einer Zentralanmeldungen, zu der ich meine Teilnahme registriert habe
NotificationTriggerAllocationNewCourseTip: Kann pro Zentralanmeldung überschrieben werden
NotificationTriggerQualification: Eine meiner Qualifikationen läuft ab NotificationTriggerQualification: Eine meiner Qualifikationen läuft ab
UserDisplayNameRules: Vorgaben für den angezeigten Namen UserDisplayNameRules: Vorgaben für den angezeigten Namen
@ -113,8 +102,6 @@ FavouritesPlaceholder: Anzahl Favoriten
FavouritesNotNatural: Anzahl der Favoriten muss eine natürliche Zahl sein! FavouritesNotNatural: Anzahl der Favoriten muss eine natürliche Zahl sein!
FavouritesSemestersPlaceholder: Anzahl Semester FavouritesSemestersPlaceholder: Anzahl Semester
FavouritesSemestersNotNatural: Anzahl der Favoriten-Semester muss eine natürliche Zahl sein! FavouritesSemestersNotNatural: Anzahl der Favoriten-Semester muss eine natürliche Zahl sein!
FormAllocationNotifications: Benachrichtigungen für neue Zentralanmeldungskurse
FormAllocationNotificationsTip: Wollen Sie eine Benachrichtigung per E-Mail erhalten wenn ein neuer Kurs zur Zentralanmeldung eingetragen wird? „Ja“ und „Nein“ überschreiben die entsprechende systemweite Einstellung unter "Benachrichtigungen"
SettingsUpdate: Einstellungen erfolgreich gespeichert SettingsUpdate: Einstellungen erfolgreich gespeichert
TokensResetSuccess: Authorisierungs-Tokens invalidiert TokensResetSuccess: Authorisierungs-Tokens invalidiert
ProfileTitle: Benutzereinstellungen ProfileTitle: Benutzereinstellungen
@ -125,9 +112,6 @@ LastEditByUser: Ihre letzte Bearbeitung
SubmissionGroupName: Gruppenname SubmissionGroupName: Gruppenname
TitleChangeUserDisplayEmail: Öffentliche E-Mail-Adresse setzen TitleChangeUserDisplayEmail: Öffentliche E-Mail-Adresse setzen
LanguageChanged: Sprache erfolgreich geändert LanguageChanged: Sprache erfolgreich geändert
AllocNotifyNewCourseDefault: Systemweite Einstellung
AllocNotifyNewCourseForceOff: Nein
AllocNotifyNewCourseForceOn: Ja
Settings: Individuelle Benutzereinstellungen Settings: Individuelle Benutzereinstellungen
FormExamOffice: Prüfungsverwaltung FormExamOffice: Prüfungsverwaltung

View File

@ -57,10 +57,7 @@ NotificationTriggerKindCourseLecturer: For course administrators
NotificationTriggerKindAdmin: For administrators NotificationTriggerKindAdmin: For administrators
NotificationTriggerKindExamOffice: For the exam office NotificationTriggerKindExamOffice: For the exam office
NotificationTriggerKindEvaluation: For course evaluations NotificationTriggerKindEvaluation: For course evaluations
NotificationTriggerKindAllocationStaff: For central allocations (lecturers)
NotificationTriggerKindAllocationParticipant: For central allocations
NotificationTriggerKindSubmissionUser: For participants in an exercise sheet submission NotificationTriggerKindSubmissionUser: For participants in an exercise sheet submission
NotificationTriggerKindAllocationAdmin: For administrators of central allocations
NotificationTriggerSubmissionRatedGraded: My submission for an exercise sheet was marked (not purely informational) NotificationTriggerSubmissionRatedGraded: My submission for an exercise sheet was marked (not purely informational)
NotificationTriggerSubmissionRated: My submission for an exercise sheet was marked NotificationTriggerSubmissionRated: My submission for an exercise sheet was marked
@ -77,20 +74,12 @@ NotificationTriggerExamRegistrationActive: I can now register for an exam
NotificationTriggerExamRegistrationSoonInactive: I will soon no longer be able to register for an exam NotificationTriggerExamRegistrationSoonInactive: I will soon no longer be able to register for an exam
NotificationTriggerExamDeregistrationSoonInactive: I will soon no longer be able to deregister from an exam NotificationTriggerExamDeregistrationSoonInactive: I will soon no longer be able to deregister from an exam
NotificationTriggerExamResult: An exam result is available NotificationTriggerExamResult: An exam result is available
NotificationTriggerAllocationStaffRegister: I can now register a course for central allocation
NotificationTriggerAllocationAllocation: I can now grade applications to a central alloction for one of my courses
NotificationTriggerAllocationRegister: I can now apply to a new central allocation
NotificationTriggerAllocationOutdatedRatings: Applications to a central allocation for one of my courses have changed since they were graded
NotificationTriggerAllocationUnratedApplications: Grades are pending for applications to a central allocation for one of my courses
NotificationTriggerAllocationResults: Participants have been placed by one of my central allocations
NotificationTriggerExamOfficeExamResults: New exam results are available NotificationTriggerExamOfficeExamResults: New exam results are available
NotificationTriggerExamOfficeExamResultsChanged: Exam results have changed NotificationTriggerExamOfficeExamResultsChanged: Exam results have changed
NotificationTriggerCourseRegistered: A course administrator has enrolled me in a course NotificationTriggerCourseRegistered: A course administrator has enrolled me in a course
NotificationTriggerSubmissionUserCreated: I was added to an exercise sheet submission NotificationTriggerSubmissionUserCreated: I was added to an exercise sheet submission
NotificationTriggerSubmissionEdited: One of my exercise sheet submissions was changed NotificationTriggerSubmissionEdited: One of my exercise sheet submissions was changed
NotificationTriggerSubmissionUserDeleted: I was removed from one of my exercise sheet submissions NotificationTriggerSubmissionUserDeleted: I was removed from one of my exercise sheet submissions
NotificationTriggerAllocationNewCourse: A new course was added to a central allocation for which I have registered my participation
NotificationTriggerAllocationNewCourseTip: Can be overridden per central allocation
NotificationTriggerQualification: My Qualifications are about to expire NotificationTriggerQualification: My Qualifications are about to expire
UserDisplayNameRules: Specification for display names UserDisplayNameRules: Specification for display names
@ -113,8 +102,6 @@ FavouritesPlaceholder: Number of favourites
FavouritesNotNatural: Number of favourites must be a natural number! FavouritesNotNatural: Number of favourites must be a natural number!
FavouritesSemestersPlaceholder: Number of semesters FavouritesSemestersPlaceholder: Number of semesters
FavouritesSemestersNotNatural: Maximum number of semesters in favourites bar must be a natural number! FavouritesSemestersNotNatural: Maximum number of semesters in favourites bar must be a natural number!
FormAllocationNotifications: Notifications for new central allocation courses
FormAllocationNotificationsTip: Do you want to receive a notification if a new course is added to the central allocation? “Yes” and “No” override the system wide setting under “Notifications”
SettingsUpdate: Successfully updated settings SettingsUpdate: Successfully updated settings
TokensResetSuccess: Successfully invalidated all authorisation tokens TokensResetSuccess: Successfully invalidated all authorisation tokens
ProfileTitle: Settings ProfileTitle: Settings
@ -125,10 +112,6 @@ LastEditByUser: Your last edit
SubmissionGroupName: Group name SubmissionGroupName: Group name
TitleChangeUserDisplayEmail: Set display email TitleChangeUserDisplayEmail: Set display email
LanguageChanged: Language changed successfully LanguageChanged: Language changed successfully
AllocNotifyNewCourseDefault: System-wide setting
AllocNotifyNewCourseForceOff: No
AllocNotifyNewCourseForceOn: Yes
Settings: Settings Settings: Settings
FormExamOffice: Exam Office FormExamOffice: Exam Office

View File

@ -28,7 +28,7 @@ TermLectureStartTooltip: Muss am oder nach dem Beginn liegen
TermLectureEndTooltip: Muss am oder vor dem Ende liegen TermLectureEndTooltip: Muss am oder vor dem Ende liegen
TermActive: Aktiv TermActive: Aktiv
TermActiveTooltip: Zeitraum in dem Lehrende Kurse anlegen dürfen; kann auf angegebene Lehrende eingeschränkt werden TermActiveTooltip: Zeitraum in dem Lehrende Kurse anlegen dürfen; kann auf angegebene Lehrende eingeschränkt werden
TermActiveForPlaceholder: Email (optional) TermActiveForPlaceholder: E-Mail (optional)
NumCourses num@Int64: #{num} #{pluralDE num "Kurs" "Kurse"} NumCourses num@Int64: #{num} #{pluralDE num "Kurs" "Kurse"}
TermsHeading: Semesterübersicht TermsHeading: Semesterübersicht
TermEditHeading: Semester editieren/anlegen TermEditHeading: Semester editieren/anlegen

View File

@ -28,7 +28,7 @@ TermLectureStartTooltip: Must be on or after starting day
TermLectureEndTooltip: Must be before or on ending day TermLectureEndTooltip: Must be before or on ending day
TermActive: Active TermActive: Active
TermActiveTooltip: Timeframe when lecturers may add courses; maybe restricted for specified lecturers TermActiveTooltip: Timeframe when lecturers may add courses; maybe restricted for specified lecturers
TermActiveForPlaceholder: E-Mail (optional) TermActiveForPlaceholder: Email (optional)
NumCourses num: #{num} #{pluralEN num "course" "courses"} NumCourses num: #{num} #{pluralEN num "course" "courses"}
TermsHeading: Semesters TermsHeading: Semesters
TermEditHeading: Edit semester TermEditHeading: Edit semester

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de> # SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
@ -9,8 +9,6 @@ BtnRegister: Anmelden
BtnDeregister: Abmelden BtnDeregister: Abmelden
BtnCourseRegister: Zum Kurs anmelden BtnCourseRegister: Zum Kurs anmelden
BtnCourseDeregister: Vom Kurs abmelden BtnCourseDeregister: Vom Kurs abmelden
BtnCourseApply: Zum Kurs bewerben
BtnCourseRetractApplication: Bewerbung zum Kurs zurückziehen
BtnExamRegister: Anmelden zur Prüfung BtnExamRegister: Anmelden zur Prüfung
BtnExamRegisterOccurrence: Anmelden zum Prüfungstermin/-raum BtnExamRegisterOccurrence: Anmelden zum Prüfungstermin/-raum
BtnExamSwitchOccurrence: Zu Prüfungstermin/-raum wechseln BtnExamSwitchOccurrence: Zu Prüfungstermin/-raum wechseln
@ -32,20 +30,10 @@ BtnCorrInvAccept: Annehmen
BtnCorrInvDecline: Ablehnen BtnCorrInvDecline: Ablehnen
BtnSubmissionsAssign: Abgaben automatisch zuteilen BtnSubmissionsAssign: Abgaben automatisch zuteilen
BtnSubmissionsAssignAll: Abgaben automatisch zuteilen BtnSubmissionsAssignAll: Abgaben automatisch zuteilen
BtnAllocationCompute: Vergabe berechnen
BtnAllocationAccept: Vergabe akzeptieren
BtnAllocationRegister: Teilnahme registrieren
BtnAllocationRegistrationEdit: Teilnahme anpassen
BtnAllocationApply: Bewerben
BtnAllocationApplicationEdit: Bewerbung ersetzen
BtnAllocationApplicationRetract: Bewerbung zurückziehen
BtnAllocationApplicationRate: Bewerbung bewerten
BtnSystemMessageHide: Verstecken BtnSystemMessageHide: Verstecken
BtnSystemMessageUnhide: Nicht mehr verstecken BtnSystemMessageUnhide: Nicht mehr verstecken
BtnCommunicationSend: Senden BtnCommunicationSend: Senden
BtnCommunicationTest: Test-Nachricht verschicken BtnCommunicationTest: Test-Nachricht verschicken
BtnAcceptApplications: Bewerbungen akzeptieren
BtnAcceptApplicationsTip: Mit dem untigen Knopf können Sie den Kurs (höchstens bis zur angegeben Maximalkapazität, falls eingestellt) mit Bewerbern auffüllen. Die Bewertungen der Bewerbungen werden dabei berücksichtigt (Unbewertet wird behandelt wie eine Note zwischen 2.3 und 2.7). Bewerber mit Veto oder 5.0 werden nicht angemeldet.
BtnExamAutoOccurrenceCalculate: Verteilungstabelle berechnen BtnExamAutoOccurrenceCalculate: Verteilungstabelle berechnen
BtnExamAutoOccurrenceAccept: Verteilung akzeptieren BtnExamAutoOccurrenceAccept: Verteilung akzeptieren
BtnExamAutoOccurrenceNudgeUp !ident-ok: + BtnExamAutoOccurrenceNudgeUp !ident-ok: +
@ -65,4 +53,7 @@ BtnNotifyNewCourseForceOff: Nicht benachrichtigen
BtnUserAssimilate: Assimilieren BtnUserAssimilate: Assimilieren
BtnCloseExam: Prüfung abschließen BtnCloseExam: Prüfung abschließen
BtnFinishExam: Prüfungsergebnisse sichtbar schalten BtnFinishExam: Prüfungsergebnisse sichtbar schalten
BtnConfirm: Bestätigen BtnConfirm: Bestätigen
BtnCourseRegisterAdd: Personen suchen
BtnCourseRegisterConfirm: Ausgewählte Personen anmelden
BtnCourseRegisterAbort: Abbrechen

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022 Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de> # SPDX-FileCopyrightText: 2022 Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
@ -9,8 +9,6 @@ BtnRegister: Register
BtnDeregister: Deregister BtnDeregister: Deregister
BtnCourseRegister: Enrol for course BtnCourseRegister: Enrol for course
BtnCourseDeregister: Leave course BtnCourseDeregister: Leave course
BtnCourseApply: Apply for course
BtnCourseRetractApplication: Retract application
BtnExamRegister: Enrol for exam BtnExamRegister: Enrol for exam
BtnExamRegisterOccurrence: Enrol for exam occurrence/room BtnExamRegisterOccurrence: Enrol for exam occurrence/room
BtnExamSwitchOccurrence: Switch to exam occurrence/room BtnExamSwitchOccurrence: Switch to exam occurrence/room
@ -32,20 +30,10 @@ BtnCorrInvAccept: Accept
BtnCorrInvDecline: Decline BtnCorrInvDecline: Decline
BtnSubmissionsAssign: Assign submissions automatically BtnSubmissionsAssign: Assign submissions automatically
BtnSubmissionsAssignAll: Automatically distribute corrections BtnSubmissionsAssignAll: Automatically distribute corrections
BtnAllocationCompute: Compute allocation
BtnAllocationAccept: Accept allocation
BtnAllocationRegister: Register participation
BtnAllocationRegistrationEdit: Edit registration
BtnAllocationApply: Apply
BtnAllocationApplicationEdit: Edit application
BtnAllocationApplicationRetract: Retract application
BtnAllocationApplicationRate: Grade application
BtnSystemMessageHide: Hide BtnSystemMessageHide: Hide
BtnSystemMessageUnhide: Unhide BtnSystemMessageUnhide: Unhide
BtnCommunicationSend: Send BtnCommunicationSend: Send
BtnCommunicationTest: Send test message BtnCommunicationTest: Send test message
BtnAcceptApplications: Accept applications
BtnAcceptApplicationsTip: By clicking the button below you may fill the course with applicants (only up to the maximum capacity if configured). Grading of applications will be considered (no grading is treated as if graded between 2.3 and 2.7). Vetoed applicants and applications graded 5.0 will not be enrolled.
BtnInviteAccept: Accept invitation BtnInviteAccept: Accept invitation
BtnInviteDecline: Decline invitation BtnInviteDecline: Decline invitation
BtnExamAutoOccurrenceCalculate: Calculate assignment rules BtnExamAutoOccurrenceCalculate: Calculate assignment rules
@ -65,4 +53,7 @@ BtnNotifyNewCourseForceOff: Do not notify me
BtnUserAssimilate: Assimilate BtnUserAssimilate: Assimilate
BtnCloseExam: Close exam BtnCloseExam: Close exam
BtnFinishExam: Make results visible BtnFinishExam: Make results visible
BtnConfirm: Confirm BtnConfirm: Confirm
BtnCourseRegisterAdd: Search persons
BtnCourseRegisterConfirm: Register selected persons
BtnCourseRegisterAbort: Abort

View File

@ -6,24 +6,15 @@ BreadcrumbCsvOptions: CSV-Optionen
BreadcrumbSubmissionFile: Datei BreadcrumbSubmissionFile: Datei
BreadcrumbSubmissionUserInvite: Einladung zur Abgabe BreadcrumbSubmissionUserInvite: Einladung zur Abgabe
BreadcrumbCryptoIDDispatch: CryptoID-Weiterleitung BreadcrumbCryptoIDDispatch: CryptoID-Weiterleitung
BreadcrumbCourseAppsFiles: Bewerbungsdateien
BreadcrumbCourseNotes: Kursnotizen BreadcrumbCourseNotes: Kursnotizen
BreadcrumbHiWis: Korrektor:innen BreadcrumbHiWis: Korrektor:innen
BreadcrumbMaterial !ident-ok: Material BreadcrumbMaterial !ident-ok: Material
BreadcrumbSheet: Übungsblatt BreadcrumbSheet: Übungsblatt
BreadcrumbTutorial: Tutorium BreadcrumbTutorial: Tutorium
BreadcrumbExam: Prüfung BreadcrumbExam: Prüfung
BreadcrumbApplicant: Bewerber:in
BreadcrumbCourseRegister: Anmelden BreadcrumbCourseRegister: Anmelden
BreadcrumbCourseRegisterTemplate: Bewerbungsvorlagen
BreadcrumbCourseFavourite: Favorisieren BreadcrumbCourseFavourite: Favorisieren
BreadcrumbCourse: Kurs BreadcrumbCourse: Kurs
BreadcrumbAllocationRegister: Teilnahme registrieren
BreadcrumbAllocation: Zentralanmeldung
BreadcrumbAllocationNew: Neue Zentralanmeldung
BreadcrumbAllocationEdit: Bearbeiten
BreadcrumbAllocationMatchings: Verteilungen
BreadcrumbAllocationMatchingLog: Verteilungs-Protokoll
BreadcrumbTerm !ident-ok: Semester BreadcrumbTerm !ident-ok: Semester
BreadcrumbSchool: Institut BreadcrumbSchool: Institut
BreadcrumbUser: Benutzer:in BreadcrumbUser: Benutzer:in
@ -41,7 +32,6 @@ BreadcrumbCourseNews: Kursnachricht
BreadcrumbCourseNewsDelete: Kursnachricht löschen BreadcrumbCourseNewsDelete: Kursnachricht löschen
BreadcrumbCourseEventDelete: Kurstermin löschen BreadcrumbCourseEventDelete: Kurstermin löschen
BreadcrumbProfile: Einstellungen BreadcrumbProfile: Einstellungen
BreadcrumbAllocationInfo: Ablauf einer Zentralanmeldung
BreadcrumbCourseParticipantInvitation: Einladung als Kursteilnehmer:in BreadcrumbCourseParticipantInvitation: Einladung als Kursteilnehmer:in
BreadcrumbMaterialArchive: Archiv BreadcrumbMaterialArchive: Archiv
BreadcrumbMaterialFile: Datei BreadcrumbMaterialFile: Datei
@ -56,7 +46,6 @@ BreadcrumbTutorInvite: Einladung als Tutor:in
BreadcrumbExamCorrectorInvite: Einladung als Prüfungskorrektor:in BreadcrumbExamCorrectorInvite: Einladung als Prüfungskorrektor:in
BreadcrumbExamParticipantInvite: Einladung als Prüfungsteilnehmer:in BreadcrumbExamParticipantInvite: Einladung als Prüfungsteilnehmer:in
BreadcrumbExamRegister: Anmelden BreadcrumbExamRegister: Anmelden
BreadcrumbApplicationFiles: Bewerbungsdateien
BreadcrumbCourseNewsArchive: Archiv BreadcrumbCourseNewsArchive: Archiv
BreadcrumbCourseNewsFile: Datei BreadcrumbCourseNewsFile: Datei
BreadcrumbExternalExam: Externe Prüfung BreadcrumbExternalExam: Externe Prüfung
@ -72,13 +61,6 @@ BreadcrumbParticipantsList: Kursteilnehmerlisten
BreadcrumbParticipants: Kursteilnehmerliste BreadcrumbParticipants: Kursteilnehmerliste
BreadcrumbExamAutoOccurrence: Automatische Termin-/Raumverteilung BreadcrumbExamAutoOccurrence: Automatische Termin-/Raumverteilung
BreadcrumbStorageKey: Lokalen Schlüssel generieren BreadcrumbStorageKey: Lokalen Schlüssel generieren
BreadcrumbAllocationUsers: Bewerber:innen
BreadcrumbAllocationPriorities: Zentrale Dringlichkeiten
BreadcrumbAllocationCompute: Platzvergabe berechnen
BreadcrumbAllocationAccept: Platzvergabe akzeptieren
BreadcrumbAllocationAddUser: Bewerber:in hinzufügen
BreadcrumbAllocationEditUser: Bewerber:in bearbeiten
BreadcrumbAllocationDelUser: Bewerber:in entfernen
BreadcrumbMessageHide: Verstecken BreadcrumbMessageHide: Verstecken
BreadcrumbFaq !ident-ok: FAQ BreadcrumbFaq !ident-ok: FAQ
BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen
@ -113,7 +95,6 @@ BreadcrumbTermShow: Semester
BreadcrumbTermCreate: Neues Semester anlegen BreadcrumbTermCreate: Neues Semester anlegen
BreadcrumbTermEdit: Semester editieren BreadcrumbTermEdit: Semester editieren
BreadcrumbTermCurrent: Aktuelles Semester BreadcrumbTermCurrent: Aktuelles Semester
BreadcrumbAllocationListTitle: Zentralanmeldungen
BreadcrumbParticipantsIntersect: Überschneidung von Kursteilnehmer:innen BreadcrumbParticipantsIntersect: Überschneidung von Kursteilnehmer:innen
BreadcrumbCourseList: Kurse BreadcrumbCourseList: Kurse
BreadcrumbCourseNew: Neuen Kurs anlegen BreadcrumbCourseNew: Neuen Kurs anlegen
@ -136,7 +117,6 @@ BreadcrumbCourseEventNew: Neuer Kurstermin
BreadcrumbCourseEventEdit: Kurstermin bearbeiten BreadcrumbCourseEventEdit: Kurstermin bearbeiten
BreadcrumbExamList: Prüfungen BreadcrumbExamList: Prüfungen
BreadcrumbExamNew: Neue Prüfung anlegen BreadcrumbExamNew: Neue Prüfung anlegen
BreadcrumbCourseApplications: Bewerbungen
BreadcrumbExamEdit: Prüfung bearbeiten BreadcrumbExamEdit: Prüfung bearbeiten
BreadcrumbExamUsers: Teilnehmer:innen BreadcrumbExamUsers: Teilnehmer:innen
BreadcrumbExamGrades: Prüfungsleistungen BreadcrumbExamGrades: Prüfungsleistungen

View File

@ -6,24 +6,15 @@ BreadcrumbCsvOptions: csv-options
BreadcrumbSubmissionFile: File BreadcrumbSubmissionFile: File
BreadcrumbSubmissionUserInvite: Invitation to participate in a submission BreadcrumbSubmissionUserInvite: Invitation to participate in a submission
BreadcrumbCryptoIDDispatch: CryptoID-redirect BreadcrumbCryptoIDDispatch: CryptoID-redirect
BreadcrumbCourseAppsFiles: Application files
BreadcrumbCourseNotes: Course notes BreadcrumbCourseNotes: Course notes
BreadcrumbHiWis: Correctors BreadcrumbHiWis: Correctors
BreadcrumbMaterial: Material BreadcrumbMaterial: Material
BreadcrumbSheet: Exercise sheet BreadcrumbSheet: Exercise sheet
BreadcrumbTutorial: Tutorial BreadcrumbTutorial: Tutorial
BreadcrumbExam: Exam BreadcrumbExam: Exam
BreadcrumbApplicant: Applicant
BreadcrumbCourseRegister: Register BreadcrumbCourseRegister: Register
BreadcrumbCourseRegisterTemplate: Application template
BreadcrumbCourseFavourite: Favourite BreadcrumbCourseFavourite: Favourite
BreadcrumbCourse: Course BreadcrumbCourse: Course
BreadcrumbAllocationRegister: Register participation
BreadcrumbAllocation: Central allocation
BreadcrumbAllocationNew: New Allocation
BreadcrumbAllocationEdit: Edit
BreadcrumbAllocationMatchings: Matchings
BreadcrumbAllocationMatchingLog: Matching log
BreadcrumbTerm: Semester BreadcrumbTerm: Semester
BreadcrumbSchool: Department BreadcrumbSchool: Department
BreadcrumbUser: User BreadcrumbUser: User
@ -41,7 +32,6 @@ BreadcrumbCourseNews: Course news
BreadcrumbCourseNewsDelete: Delete course news BreadcrumbCourseNewsDelete: Delete course news
BreadcrumbCourseEventDelete: Delete course occurrence BreadcrumbCourseEventDelete: Delete course occurrence
BreadcrumbProfile: Settings BreadcrumbProfile: Settings
BreadcrumbAllocationInfo: On central allocations
BreadcrumbCourseParticipantInvitation: Invitation to be a course participant BreadcrumbCourseParticipantInvitation: Invitation to be a course participant
BreadcrumbMaterialArchive: Archive BreadcrumbMaterialArchive: Archive
BreadcrumbMaterialFile: File BreadcrumbMaterialFile: File
@ -56,7 +46,6 @@ BreadcrumbTutorInvite: Invitation to be a tutor
BreadcrumbExamCorrectorInvite: Invitation to be an exam corrector BreadcrumbExamCorrectorInvite: Invitation to be an exam corrector
BreadcrumbExamParticipantInvite: Invitation to be an exam participant BreadcrumbExamParticipantInvite: Invitation to be an exam participant
BreadcrumbExamRegister: Register BreadcrumbExamRegister: Register
BreadcrumbApplicationFiles: Application files
BreadcrumbCourseNewsArchive: Archive BreadcrumbCourseNewsArchive: Archive
BreadcrumbCourseNewsFile: File BreadcrumbCourseNewsFile: File
BreadcrumbExternalExam: External exam BreadcrumbExternalExam: External exam
@ -72,13 +61,6 @@ BreadcrumbParticipantsList: Lists of course participants
BreadcrumbParticipants: Course participants BreadcrumbParticipants: Course participants
BreadcrumbExamAutoOccurrence: Automatic occurrence/room distribution BreadcrumbExamAutoOccurrence: Automatic occurrence/room distribution
BreadcrumbStorageKey: Generate storage key BreadcrumbStorageKey: Generate storage key
BreadcrumbAllocationUsers: Applicants
BreadcrumbAllocationPriorities: Central priorities
BreadcrumbAllocationCompute: Compute allocation
BreadcrumbAllocationAccept: Accept allocation
BreadcrumbAllocationAddUser: Add applicant
BreadcrumbAllocationEditUser: Edit applicant
BreadcrumbAllocationDelUser: Remove participant
BreadcrumbMessageHide: Hide BreadcrumbMessageHide: Hide
BreadcrumbFaq: FAQ BreadcrumbFaq: FAQ
BreadcrumbSheetPersonalisedFiles: Download personalised sheet files BreadcrumbSheetPersonalisedFiles: Download personalised sheet files
@ -113,7 +95,6 @@ BreadcrumbTermShow: Semesters
BreadcrumbTermCreate: Create new semester BreadcrumbTermCreate: Create new semester
BreadcrumbTermEdit: Edit semester BreadcrumbTermEdit: Edit semester
BreadcrumbTermCurrent: Current semester BreadcrumbTermCurrent: Current semester
BreadcrumbAllocationListTitle: Central allocations
BreadcrumbParticipantsIntersect: Common course participants BreadcrumbParticipantsIntersect: Common course participants
BreadcrumbCourseList: Courses BreadcrumbCourseList: Courses
BreadcrumbCourseNew: Create new course BreadcrumbCourseNew: Create new course
@ -133,7 +114,6 @@ BreadcrumbCourseEventNew: New course occurrence
BreadcrumbCourseEventEdit: Edit course occurrence BreadcrumbCourseEventEdit: Edit course occurrence
BreadcrumbExamList: Exams BreadcrumbExamList: Exams
BreadcrumbExamNew: Create new exam BreadcrumbExamNew: Create new exam
BreadcrumbCourseApplications: Applications
BreadcrumbExamEdit: Edit exam BreadcrumbExamEdit: Edit exam
BreadcrumbExamUsers: Participants BreadcrumbExamUsers: Participants
BreadcrumbExamGrades: Exam results BreadcrumbExamGrades: Exam results

View File

@ -9,11 +9,9 @@ MenuInfoLecturerCourses: Veranstaltungen
MenuInfoLecturerExercises: Übungsbetrieb MenuInfoLecturerExercises: Übungsbetrieb
MenuInfoLecturerTutorials: Tutorien MenuInfoLecturerTutorials: Tutorien
MenuInfoLecturerExams: Prüfungen MenuInfoLecturerExams: Prüfungen
MenuInfoLecturerAllocations: Zentralanmeldungen
MenuCsvOptions: CSV-Optionen MenuCsvOptions: CSV-Optionen
MenuCorrectorAssignTitle: Korrektor:in zuweisen MenuCorrectorAssignTitle: Korrektor:in zuweisen
MenuOpenCourses: Kurse mit offener Registrierung MenuOpenCourses: Kurse mit offener Registrierung
MenuOpenAllocations: Aktive Zentralanmeldungen
MenuNews: Aktuell MenuNews: Aktuell
MenuInformation: Informationen MenuInformation: Informationen
MenuLegal: Rechtliche Informationen MenuLegal: Rechtliche Informationen
@ -29,15 +27,10 @@ MenuHelp: Hilfe
MenuProfile: Anpassen MenuProfile: Anpassen
MenuLogin !ident-ok: Login MenuLogin !ident-ok: Login
MenuLogout !ident-ok: Logout MenuLogout !ident-ok: Logout
MenuAllocationList: Zentralanmeldungen
MenuAllocationNew: Neue Zentralanmeldung anlegen
MenuAllocationEdit: Zentralanmeldung bearbeiten
MenuAllocationMatchings: Verteilungen
MenuCourseList: Kurse MenuCourseList: Kurse
MenuCourseMembers: Kursteilnehmer:innen MenuCourseMembers: Kursteilnehmer:innen
MenuCourseAddMembers: KursteilnehmerInnen hinzufügen MenuCourseAddMembers: Kursteilnehmer:innen hinzufügen
MenuCourseCommunication: Kursmitteilung (E-Mail) MenuCourseCommunication: Kursmitteilung (E-Mail)
MenuCourseApplications: Bewerbungen
MenuCourseExamOffice: Prüfungsbeauftragte MenuCourseExamOffice: Prüfungsbeauftragte
MenuTermShow: Semester MenuTermShow: Semester
MenuSubmissionDelete: Abgabe löschen MenuSubmissionDelete: Abgabe löschen
@ -98,8 +91,6 @@ MenuExamOfficeExams: Prüfungen
MenuExamOfficeFields: Fächer MenuExamOfficeFields: Fächer
MenuExamOfficeUsers: Benutzer:innen MenuExamOfficeUsers: Benutzer:innen
MenuLecturerInvite: Funktionäre hinzufügen MenuLecturerInvite: Funktionäre hinzufügen
MenuAllocationInfo: Hinweise zum Ablauf einer Zentralanmeldung
MenuCourseApplicationsFiles: Dateien aller Bewerbungen
MenuSchoolList: Institute MenuSchoolList: Institute
MenuSchoolNew: Neues Institut anlegen MenuSchoolNew: Neues Institut anlegen
MenuExternalExamGrades: Prüfungsleistungen MenuExternalExamGrades: Prüfungsleistungen
@ -110,12 +101,6 @@ MenuExternalExamList: Externe Prüfungen
MenuExternalExamCorrect: Prüfungsleistungen eintragen MenuExternalExamCorrect: Prüfungsleistungen eintragen
MenuParticipantsList: Kursteilnehmerlisten MenuParticipantsList: Kursteilnehmerlisten
MenuParticipantsIntersect: Überschneidung von Kursteilnehmer:innen MenuParticipantsIntersect: Überschneidung von Kursteilnehmer:innen
MenuAllocationUsers: Bewerber:innen
MenuAllocationPriorities: Zentrale Dringlichkeiten
MenuAllocationCompute: Platzvergabe berechnen
MenuAllocationAddUser: Bewerber:in hinzufügen
MenuAllocationDelUser: Bewerber:in entfernen
MenuFaq !ident-ok: FAQ MenuFaq !ident-ok: FAQ
MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen
MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen

View File

@ -9,11 +9,9 @@ MenuInfoLecturerCourses: Courses
MenuInfoLecturerExercises: Course Exercises MenuInfoLecturerExercises: Course Exercises
MenuInfoLecturerTutorials: Tutorials MenuInfoLecturerTutorials: Tutorials
MenuInfoLecturerExams: Exams MenuInfoLecturerExams: Exams
MenuInfoLecturerAllocations: Central allocations
MenuCsvOptions: CSV-options MenuCsvOptions: CSV-options
MenuCorrectorAssignTitle: Assign corrector MenuCorrectorAssignTitle: Assign corrector
MenuOpenCourses: Courses with open registration MenuOpenCourses: Courses with open registration
MenuOpenAllocations: Active central allocations
MenuNews: News MenuNews: News
MenuInformation: Information MenuInformation: Information
MenuLegal: Legal MenuLegal: Legal
@ -29,15 +27,10 @@ MenuHelp: Support
MenuProfile: Settings MenuProfile: Settings
MenuLogin: Login MenuLogin: Login
MenuLogout: Logout MenuLogout: Logout
MenuAllocationList: Central allocations
MenuAllocationNew: Create new allocation
MenuAllocationEdit: Edit allocation
MenuAllocationMatchings: Matchings
MenuCourseList: Courses MenuCourseList: Courses
MenuCourseMembers: Participants MenuCourseMembers: Participants
MenuCourseAddMembers: Add participants MenuCourseAddMembers: Add participants
MenuCourseCommunication: Course message (email) MenuCourseCommunication: Course message (email)
MenuCourseApplications: Applications
MenuCourseExamOffice: Exam offices MenuCourseExamOffice: Exam offices
MenuTermShow: Semesters MenuTermShow: Semesters
MenuSubmissionDelete: Delete submission MenuSubmissionDelete: Delete submission
@ -99,8 +92,6 @@ MenuExamOfficeExams: Exams
MenuExamOfficeFields: Fields of study MenuExamOfficeFields: Fields of study
MenuExamOfficeUsers: Users MenuExamOfficeUsers: Users
MenuLecturerInvite: Add functionaries MenuLecturerInvite: Add functionaries
MenuAllocationInfo: Information regarding central allocations
MenuCourseApplicationsFiles: Files of all applications
MenuSchoolList: Departments MenuSchoolList: Departments
MenuSchoolNew: Create new department MenuSchoolNew: Create new department
MenuExternalExamGrades: Exam results MenuExternalExamGrades: Exam results
@ -111,12 +102,6 @@ MenuExternalExamList: External exams
MenuExternalExamCorrect: Enter exam results MenuExternalExamCorrect: Enter exam results
MenuParticipantsList: Lists of course participants MenuParticipantsList: Lists of course participants
MenuParticipantsIntersect: Common course participants MenuParticipantsIntersect: Common course participants
MenuAllocationUsers: Applicants
MenuAllocationPriorities: Central priorities
MenuAllocationCompute: Compute allocation
MenuAllocationAddUser: Add applicant
MenuAllocationDelUser: Remove participant
MenuFaq: FAQ MenuFaq: FAQ
MenuSheetPersonalisedFiles: Download personalised sheet files MenuSheetPersonalisedFiles: Download personalised sheet files
MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files

View File

@ -71,3 +71,6 @@ TableDiffDaysTooltip: Zeitspanne nach ISO 8601. Beispiel: "P2Y3M4D" ist eine Zei
TableExamOfficeLabel: Label-Name TableExamOfficeLabel: Label-Name
TableExamOfficeLabelStatus: Label-Farbe TableExamOfficeLabelStatus: Label-Farbe
TableExamOfficeLabelPriority: Label-Priorität TableExamOfficeLabelPriority: Label-Priorität
TableQualifications: Qualifikationen
TableCompany: Firma
TableSupervisor: Ansprechpartner

View File

@ -71,3 +71,6 @@ TableDiffDaysTooltip: Duration given according to ISO 8601. Example: "P2Y3M4D" i
TableExamOfficeLabel: Label name TableExamOfficeLabel: Label name
TableExamOfficeLabelStatus: Label colour TableExamOfficeLabelStatus: Label colour
TableExamOfficeLabelPriority: Label priority TableExamOfficeLabelPriority: Label priority
TableQualifications: Qualifications
TableCompany: Company
TableSupervisor: Supervisor

View File

@ -1,70 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
Allocation -- attributes with prefix staff- affect lecturers only, but are invisble to students
term TermId
school SchoolId -- school that manages this central allocation, not necessarily school of courses
shorthand AllocationShorthand -- practical shorthand
name AllocationName
legacyShorthands [AllocationShorthand] default='[]' -- just for association to previous allocations
description StoredMarkup Maybe -- description for prospective students
staffDescription StoredMarkup Maybe -- description seen by prospective lecturers only
staffRegisterFrom UTCTime Maybe -- lectureres may register courses
staffRegisterTo UTCTime Maybe -- course registration stops
-- staffDeregisterUntil not needed: staff may make arbitrary changes until staffRegisterTo, always frozen afterwards
staffAllocationFrom UTCTime Maybe -- lecturers may rate applicants from this day onwwards or prohibited
staffAllocationTo UTCTime Maybe --
-- Student register for this allocation
-- canRegisterNow = maybe False (<= currentTime) registerFrom && maybe True (>= currentTime) registerTo
registerFrom UTCTime Maybe -- student applications allowed from a given day onwwards or prohibited
registerTo UTCTime Maybe -- student applications may be prohibited from a given date onwards
-- deregisterUntil not needed: students may withdraw applicants until registerTo, but never after. Also see overrideDeregister
-- overrides
registerByStaffFrom UTCTime Maybe -- lecturers may directly enrol/disenrol students after a given date or prohibited
registerByStaffTo UTCTime Maybe
registerByCourse UTCTime Maybe -- course registration dates are ignored until this day has passed or always prohibited
overrideDeregister UTCTime Maybe -- deregister prohibited after this time or always allowed (defaulting to course settings)
-- overrideVisible not needed, since courses are always visible
matchingSeed ByteString default='\x'::bytea
TermSchoolAllocationShort term school shorthand -- shorthand must be unique within school and semester
TermSchoolAllocationName term school name -- name must be unique within school and semester
deriving Show Eq Ord Generic
AllocationMatching
allocation AllocationId
fingerprint AllocationFingerprint
time UTCTime
log FileContentReference
deriving Generic
AllocationCourse
allocation AllocationId
course CourseId
minCapacity Int -- if the course would get assigned fewer than this many applicants, restart the assignment process without the course
acceptSubstitutes UTCTime Maybe
overrideSumCapacity Int Maybe -- mark course as outlier (ridiculously large capacity) and use this capacity instead for computing overall capacity of allocation
UniqueAllocationCourse course
deriving Generic
AllocationUser
allocation AllocationId
user UserId
totalCourses Word64 -- number of total allocated courses for this user must be <= than this number
priority AllocationPriority Maybe
UniqueAllocationUser allocation user
deriving Eq Ord Show Generic
AllocationDeregister -- self-inflicted user-deregistrations from an allocated course
user UserId
course CourseId Maybe OnDeleteSetNull OnUpdateCascade
time UTCTime
reason Text Maybe -- if this deregistration was done by proxy (e.g. the lecturer pressed the button)
deriving Eq Ord Show Generic
AllocationNotificationSetting
user UserId
allocation AllocationId
isOptOut Bool
UniqueAllocationNotificationSetting user allocation
deriving Generic

View File

@ -1,4 +1,4 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de> -- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
-- --
-- SPDX-License-Identifier: AGPL-3.0-or-later -- SPDX-License-Identifier: AGPL-3.0-or-later
@ -22,14 +22,8 @@ Course -- Information about a single course; contained info is always visible
registerFrom UTCTime Maybe -- enrolement allowed from a given day onwwards or prohibited registerFrom UTCTime Maybe -- enrolement allowed from a given day onwwards or prohibited
registerTo UTCTime Maybe -- enrolement may be prohibited from a given date onwards registerTo UTCTime Maybe -- enrolement may be prohibited from a given date onwards
deregisterUntil UTCTime Maybe -- unenrolement may be prohibited from a given date onwards deregisterUntil UTCTime Maybe -- unenrolement may be prohibited from a given date onwards
deregisterNoShow Bool default=false
registerSecret Text Maybe -- enrolement maybe protected by a simple common passphrase registerSecret Text Maybe -- enrolement maybe protected by a simple common passphrase
materialFree Bool -- False: only enrolled users may see course materials not stored in this table materialFree Bool -- False: only enrolled users may see course materials not stored in this table
applicationsRequired Bool default=false
applicationsInstructions StoredMarkup Maybe
applicationsText Bool default=false
applicationsFiles UploadMode "default='{\"mode\": \"no-upload\"}'::jsonb"
applicationsRatingsVisible Bool default=false
TermSchoolCourseShort term school shorthand -- shorthand must be unique within school and semester TermSchoolCourseShort term school shorthand -- shorthand must be unique within school and semester
TermSchoolCourseName term school name -- name must be unique within school and semester TermSchoolCourseName term school name -- name must be unique within school and semester
deriving Generic deriving Generic
@ -67,7 +61,6 @@ CourseParticipant -- course enrolement
user UserId user UserId
registration UTCTime -- time of last enrolement for this course registration UTCTime -- time of last enrolement for this course
field StudyFeaturesId Maybe MigrationOnly field StudyFeaturesId Maybe MigrationOnly
allocated AllocationId Maybe -- participant was centrally allocated
state CourseParticipantState state CourseParticipantState
UniqueParticipant user course UniqueParticipant user course
deriving Eq Ord Show Generic deriving Eq Ord Show Generic

View File

@ -1,25 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
CourseApplication
course CourseId
user UserId
field StudyFeaturesId Maybe MigrationOnly
text Text Maybe -- free text entered by user
ratingVeto Bool default=false
ratingPoints ExamGrade Maybe
ratingComment Text Maybe
allocation AllocationId Maybe
allocationPriority Word64 Maybe
time UTCTime default=now()
ratingTime UTCTime Maybe
deriving Generic
CourseApplicationFile
application CourseApplicationId OnDeleteCascade OnUpdateCascade
title FilePath
content FileContentReference Maybe
modified UTCTime
UniqueCourseApplicationFile application title
deriving Generic

View File

@ -15,8 +15,8 @@ Qualification
-- elearningOnly Bool -- successful E-learing automatically increases validity. NO! -- elearningOnly Bool -- successful E-learing automatically increases validity. NO!
-- refreshInvitation StoredMarkup -- hard-coded I18N-MSGs used instead, but displayed on qualification page NO! -- refreshInvitation StoredMarkup -- hard-coded I18N-MSGs used instead, but displayed on qualification page NO!
-- expiryNotification StoredMarkup Maybe -- configurable user-profile-notifcations are used instead NO! -- expiryNotification StoredMarkup Maybe -- configurable user-profile-notifcations are used instead NO!
avsLicence AvsLicence Maybe -- if set, is synchronized to Avs as a driving licence avsLicence AvsLicence Maybe -- if set, valid QualificationUsers are synchronized to AVS as a driving licence
sapId Text Maybe -- if set, all QualificationUsers with userCompanyPersonalNumber are transmitted via SAP interface under this id sapId Text Maybe -- if set, valid QualificationUsers with userCompanyPersonalNumber are transmitted via SAP interface under this id
SchoolQualificationShort school shorthand -- must be unique per school and shorthand SchoolQualificationShort school shorthand -- must be unique per school and shorthand
SchoolQualificationName school name -- must be unique per school and name SchoolQualificationName school name -- must be unique per school and name
-- across all schools, only one qualification may be a driving licence: -- across all schools, only one qualification may be a driving licence:
@ -134,13 +134,3 @@ LmsResult
timestamp UTCTime default=now() timestamp UTCTime default=now()
UniqueLmsResult qualification ident -- required by DBTable UniqueLmsResult qualification ident -- required by DBTable
deriving Generic deriving Generic
-- Logs all processed rows from LmsUserlist and LmsResult
LmsAudit
qualification QualificationId OnDeleteCascade OnUpdateCascade
ident LmsIdent
notificationType LmsStatus -- LmsBlocked Day | LmsSuccess Day
note Text Maybe
received UTCTime -- timestamp from LmsUserlist/LmsResult
processed UTCTime default=now()
deriving Generic

View File

@ -1,3 +1,3 @@
{ {
"version": "26.6.5" "version": "27.0.0"
} }

View File

@ -1,3 +1,3 @@
{ {
"version": "26.6.5" "version": "27.0.0"
} }

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "uni2work", "name": "uni2work",
"version": "26.6.5", "version": "27.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "uni2work", "name": "uni2work",
"version": "26.6.5", "version": "27.0.0",
"description": "", "description": "",
"keywords": [], "keywords": [],
"author": "", "author": "",

View File

@ -1,5 +1,5 @@
name: uniworx name: uniworx
version: 26.6.5 version: 27.0.0
dependencies: dependencies:
- base - base
- yesod - yesod

53
routes
View File

@ -67,7 +67,12 @@
/admin/tokens AdminTokensR GET POST /admin/tokens AdminTokensR GET POST
/admin/crontab AdminCrontabR GET /admin/crontab AdminCrontabR GET
/admin/avs AdminAvsR GET POST /admin/avs AdminAvsR GET POST
/admin/ldap AdminLdapR GET POST /admin/ldap AdminLdapR GET POST
/admin/problems AdminProblemsR GET
/admin/problems/no-contact ProblemUnreachableR GET
/admin/problems/no-avs-id ProblemWithoutAvsId GET
/admin/problems/r-without-f ProblemFbutNoR GET
/admin/problems/avs ProblemAvsSynchR GET POST
/print PrintCenterR GET POST !system-printer /print PrintCenterR GET POST !system-printer
/print/acknowledge/#Day/#Int/#Int PrintAckR GET POST !system-printer /print/acknowledge/#Day/#Int/#Int PrintAckR GET POST !system-printer
@ -81,7 +86,6 @@
/info/lecturer InfoLecturerR GET !free /info/lecturer InfoLecturerR GET !free
/info/supervisor InfoSupervisorR GET !free /info/supervisor InfoSupervisorR GET !free
/info/legal LegalR GET !free /info/legal LegalR GET !free
/info/allocation InfoAllocationR GET !free
/info/glossary GlossaryR GET !free /info/glossary GlossaryR GET !free
/info/faq FaqR GET !free /info/faq FaqR GET !free
/info/terms-of-use TermsOfUseR GET !free /info/terms-of-use TermsOfUseR GET !free
@ -138,24 +142,6 @@
/ SchoolEditR GET POST / SchoolEditR GET POST
/allocation/ AllocationListR GET !free
!/allocation/new AllocationNewR GET POST !allocation-admin
/allocation/#TermId/#SchoolId/#AllocationShorthand AllocationR:
/ AShowR GET POST !free
/edit AEditR GET POST !allocation-admin
/register ARegisterR POST !time
/course/#CryptoUUIDCourse/apply AApplyR POST !timeANDallocation-registered
/users AUsersR GET POST !allocation-admin
/users/#CryptoUUIDUser AEditUserR GET POST !allocation-admin
/users/#CryptoUUIDUser/delete ADelUserR GET POST !allocation-admin
!/users/add AAddUserR GET POST !allocation-admin
/priorities APriosR GET POST !allocation-admin
/compute AComputeR GET POST !allocation-admin
/accept AAcceptR GET POST !allocation-admin
/matchings AMatchingListR GET
/matchings/#CryptoUUIDAllocationMatching AMatchingR:
/log AMLogR GET
/participants ParticipantsListR GET !evaluation /participants ParticipantsListR GET !evaluation
/participants/#TermId/#SchoolId ParticipantsR GET !evaluation /participants/#TermId/#SchoolId ParticipantsR GET !evaluation
/participants/intersect ParticipantsIntersectR GET POST !evaluation /participants/intersect ParticipantsIntersectR GET POST !evaluation
@ -165,17 +151,15 @@
/course/ CourseListR GET !free /course/ CourseListR GET !free
!/course/new CourseNewR GET POST !lecturer !/course/new CourseNewR GET POST !lecturer
/course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer: /course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer:
/ CShowR GET !tutor !corrector !exam-corrector !course-registered !course-time !evaluation !exam-office !allocation-admin / CShowR GET !tutor !corrector !exam-corrector !course-registered !course-time !evaluation !exam-office
/favourite CFavouriteR GET POST !free /favourite CFavouriteR GET POST !free
/register CRegisterR GET POST !timeANDcapacityANDallocation-timeAND¬course-registeredANDcourse-time !timeANDallocation-timeAND¬exam-resultANDcourse-registered !lecturerANDallocation-time /register CRegisterR GET POST !timeANDcapacityAND¬course-registeredANDcourse-time !timeAND¬exam-resultANDcourse-registered !lecturer
/register-template CRegisterTemplateR GET !course-time
/edit CEditR GET POST /edit CEditR GET POST
/lecturer-invite CLecInviteR GET POST /lecturer-invite CLecInviteR GET POST
/delete CDeleteR GET POST !lecturerANDemptyANDallocation-time /delete CDeleteR GET POST !lecturerANDempty
/users CUsersR GET POST /users CUsersR GET POST
!/users/new CAddUserR GET POST !lecturerANDallocation-time !/users/new CAddUserR GET POST !lecturer
!/users/invite CInviteR GET POST /users/#CryptoUUIDUser CUserR GET POST !lecturerANDparticipant !lecturer
/users/#CryptoUUIDUser CUserR GET POST !lecturerANDparticipant !lecturerANDapplicant
/correctors CHiWisR GET /correctors CHiWisR GET
/communication CCommR GET POST /communication CCommR GET POST
/notes CNotesR GET POST !corrector -- THIS route is used to check for overall course corrector access! /notes CNotesR GET POST !corrector -- THIS route is used to check for overall course corrector access!
@ -242,11 +226,6 @@
/grades EGradesR GET POST !exam-office /grades EGradesR GET POST !exam-office
/assign-occurrences EAutoOccurrenceR POST /assign-occurrences EAutoOccurrenceR POST
/correct ECorrectR GET POST !exam-correctorANDtime /correct ECorrectR GET POST !exam-correctorANDtime
/apps CApplicationsR GET POST
!/apps/files CAppsFilesR GET
/apps/#CryptoFileNameCourseApplication CourseApplicationR:
/ CAEditR GET POST !timeANDself !lecturerANDstaff-time !selfANDread
/files CAFilesR GET !self !lecturerANDstaff-time
!/news/add CNewsNewR GET POST !/news/add CNewsNewR GET POST
/news/#CryptoUUIDCourseNews CourseNewsR: /news/#CryptoUUIDCourseNews CourseNewsR:
/ CNShowR GET !timeANDparticipant / CNShowR GET !timeANDparticipant
@ -289,16 +268,16 @@
-- OSIS CSV Export Demo -- OSIS CSV Export Demo
/lms LmsAllR GET POST !free -- TODO verify that this is ok /lms LmsAllR GET POST !free -- TODO verify that this is ok
/lms/#SchoolId LmsSchoolR GET !free -- TODO verify that this is ok /lms/#SchoolId LmsSchoolR GET !free -- TODO verify that this is ok
/lms/#SchoolId/#QualificationShorthand LmsR GET POST !free -- TODO Filtering does not work! /lms/#SchoolId/#QualificationShorthand LmsR GET POST !free --
/lms/#SchoolId/#QualificationShorthand/edit LmsEditR GET POST /lms/#SchoolId/#QualificationShorthand/edit LmsEditR GET POST
/lms/#SchoolId/#QualificationShorthand/users LmsUsersR GET /lms/#SchoolId/#QualificationShorthand/users LmsUsersR GET
/lms/#SchoolId/#QualificationShorthand/users/direct LmsUsersDirectR GET -- development /lms/#SchoolId/#QualificationShorthand/users/direct LmsUsersDirectR GET -- development only
/lms/#SchoolId/#QualificationShorthand/userlist LmsUserlistR GET POST /lms/#SchoolId/#QualificationShorthand/userlist LmsUserlistR GET POST
/lms/#SchoolId/#QualificationShorthand/userlist/upload LmsUserlistUploadR GET POST -- development /lms/#SchoolId/#QualificationShorthand/userlist/upload LmsUserlistUploadR GET POST -- development only
/lms/#SchoolId/#QualificationShorthand/userlist/direct LmsUserlistDirectR POST !token /lms/#SchoolId/#QualificationShorthand/userlist/direct LmsUserlistDirectR POST !token
/lms/#SchoolId/#QualificationShorthand/fake LmsFakeR GET POST !development -- TODO: delete this testing URL /lms/#SchoolId/#QualificationShorthand/fake LmsFakeR GET POST -- TODO: delete this testing URL
/lms/#SchoolId/#QualificationShorthand/result LmsResultR GET POST /lms/#SchoolId/#QualificationShorthand/result LmsResultR GET POST
/lms/#SchoolId/#QualificationShorthand/result/upload LmsResultUploadR GET POST -- development /lms/#SchoolId/#QualificationShorthand/result/upload LmsResultUploadR GET POST -- development only
/lms/#SchoolId/#QualificationShorthand/result/direct LmsResultDirectR POST !token /lms/#SchoolId/#QualificationShorthand/result/direct LmsResultDirectR POST !token
/api ApiDocsR GET !free /api ApiDocsR GET !free

View File

@ -145,7 +145,6 @@ import Handler.CryptoIDDispatch
import Handler.SystemMessage import Handler.SystemMessage
import Handler.Health import Handler.Health
import Handler.Exam import Handler.Exam
import Handler.Allocation
import Handler.ExamOffice import Handler.ExamOffice
import Handler.Metrics import Handler.Metrics
import Handler.ExternalExam import Handler.ExternalExam

View File

@ -64,17 +64,6 @@ data Transaction
, transactionUser :: UserId , transactionUser :: UserId
} }
| TransactionCourseApplicationEdit
{ transactionCourse :: CourseId
, transactionUser :: UserId
, transactionCourseApplication :: CourseApplicationId
}
| TransactionCourseApplicationDeleted
{ transactionCourse :: CourseId
, transactionUser :: UserId
, transactionCourseApplication :: CourseApplicationId
}
| TransactionSubmissionEdit | TransactionSubmissionEdit
{ transactionSubmission :: SubmissionId { transactionSubmission :: SubmissionId
, transactionSheet :: SheetId , transactionSheet :: SheetId
@ -123,6 +112,17 @@ data Transaction
{ transactionTutorial :: TutorialId { transactionTutorial :: TutorialId
} }
| TransactionTutorialParticipantEdit
{ transactionTutorial :: TutorialId
, transactionTutorialParticipant :: TutorialParticipantId
, transactionUser :: UserId
}
| TransactionTutorialParticipantDelete
{ transactionTutorial :: TutorialId
, transactionTutorialParticipant :: TutorialParticipantId
, transactionUser :: UserId
}
| TransactionExternalExamEdit | TransactionExternalExamEdit
{ transactionExternalExam :: ExternalExamId { transactionExternalExam :: ExternalExamId
} }
@ -181,15 +181,35 @@ data Transaction
, transactionNewUserIdent :: UserIdent , transactionNewUserIdent :: UserIdent
} }
| TransactionAllocationUserEdited | TransactionLmsBlocked
{ transactionUser :: UserId { transactionQualification :: QualificationId
, transactionAllocation :: AllocationId , transactionLmsIdent :: LmsIdent
, transactionLmsDay :: Day
, transactionLmsUser :: Maybe UserId
, transactionNote :: Maybe Text
, transactionReceived :: UTCTime -- when was the csv file received?
} }
| TransactionAllocationUserDeleted | TransactionLmsSuccess
{ transactionUser :: UserId { transactionQualification :: QualificationId
, transactionAllocation :: AllocationId , transactionLmsIdent :: LmsIdent
, transactionLmsDay :: Day
, transactionLmsUser :: Maybe UserId
, transactionNote :: Maybe Text
, transactionReceived :: UTCTime -- when was the csv file received?
} }
| TransactionQualificationUserEdit
{ transactionQualificationUser :: QualificationUserId
, transactionQualification :: QualificationId
, transactionUser :: UserId
, transactionQualificationValidUntil :: Day
}
| TransactionQualificationUserDelete
{ transactionQualificationUser :: QualificationUserId
, transactionQualification :: QualificationId
, transactionUser :: UserId
}
deriving (Eq, Ord, Read, Show, Generic, Typeable) deriving (Eq, Ord, Read, Show, Generic, Typeable)
deriveJSON defaultOptions deriveJSON defaultOptions

View File

@ -50,8 +50,6 @@ decCryptoIDs [ ''SubmissionId
, ''ExamId , ''ExamId
, ''ExamOccurrenceId , ''ExamOccurrenceId
, ''ExamPartId , ''ExamPartId
, ''AllocationId
, ''CourseApplicationId
, ''CourseId , ''CourseId
, ''CourseNewsId , ''CourseNewsId
, ''CourseEventId , ''CourseEventId
@ -59,8 +57,8 @@ decCryptoIDs [ ''SubmissionId
, ''ExternalApiId , ''ExternalApiId
, ''ExternalExamId , ''ExternalExamId
, ''MaterialFileId , ''MaterialFileId
, ''AllocationMatchingId
, ''PrintJobId , ''PrintJobId
, ''QualificationId
] ]
decCryptoIDKeySize decCryptoIDKeySize

View File

@ -7,7 +7,7 @@
module Database.Esqueleto.Utils module Database.Esqueleto.Utils
( true, false ( true, false
, justVal, justValList, toValues , vals, justVal, justValList, toValues
, isJust, alt , isJust, alt
, isInfixOf, hasInfix , isInfixOf, hasInfix
, strConcat, substring , strConcat, substring
@ -98,6 +98,9 @@ false = E.val False
-- infinity :: E.SqlExpr (E.Value UTCTime) -- infinity :: E.SqlExpr (E.Value UTCTime)
-- infinity = unsafeSqlValue "'infinity'" -- infinity = unsafeSqlValue "'infinity'"
vals :: (MonoFoldable mono, PersistField (Element mono)) => mono -> E.SqlExpr (E.ValueList (Element mono))
vals = E.valList . toList
justVal :: PersistField typ => typ -> E.SqlExpr (E.Value (Maybe typ)) justVal :: PersistField typ => typ -> E.SqlExpr (E.Value (Maybe typ))
-- justVal = E.val . Just -- justVal = E.val . Just
justVal = E.just . E.val justVal = E.just . E.val
@ -327,9 +330,9 @@ allFilter fltrs needle criterias = F.foldr aux true fltrs
orderByList :: PersistField a => [a] -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Int) orderByList :: PersistField a => [a] -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Int)
orderByList vals orderByList valus
= let sortUni = zip [1..] vals -- memoize this, might not work due to polymorphism = let sortUni = zip [1..] valus -- memoize this, might not work due to polymorphism
in \x -> E.case_ [ (x E.==. E.val u, E.val i) | (i,u) <- sortUni ] (E.val . succ $ List.length vals) in \x -> E.case_ [ (x E.==. E.val u, E.val i) | (i,u) <- sortUni ] (E.val . succ $ List.length valus)
orderByOrd :: (Ord a, Finite a, PersistField a) => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Int) orderByOrd :: (Ord a, Finite a, PersistField a) => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Int)
orderByOrd = orderByList $ List.sort universeF orderByOrd = orderByList $ List.sort universeF

View File

@ -510,7 +510,6 @@ tagAccessPredicate AuthAdmin = cacheAPSchoolFunction SchoolAdmin (Just $ Right d
| maybe True (`Set.notMember` adminList) mAuthId' -> Right $ case route' of | maybe True (`Set.notMember` adminList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired _ | is _Nothing mAuthId' -> return AuthenticationRequired
CourseR{} -> unauthorizedI MsgUnauthorizedSchoolAdmin CourseR{} -> unauthorizedI MsgUnauthorizedSchoolAdmin
AllocationR{} -> unauthorizedI MsgUnauthorizedSchoolAdmin
SchoolR _ _ -> unauthorizedI MsgUnauthorizedSchoolAdmin SchoolR _ _ -> unauthorizedI MsgUnauthorizedSchoolAdmin
_other -> unauthorizedI MsgUnauthorizedSiteAdmin _other -> unauthorizedI MsgUnauthorizedSiteAdmin
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of | otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
@ -526,18 +525,6 @@ tagAccessPredicate AuthAdmin = cacheAPSchoolFunction SchoolAdmin (Just $ Right d
E.&&. course E.^. CourseShorthand E.==. E.val csh E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT isAdmin $ unauthorizedI MsgUnauthorizedSchoolAdmin guardMExceptT isAdmin $ unauthorizedI MsgUnauthorizedSchoolAdmin
return Authorized return Authorized
-- Allocations: access only to school admins
AllocationR tid ssh ash _ -> $cachedHereBinary (mAuthId, tid, ssh, ash) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isAdmin <- lift . E.selectExists . E.from $ \(allocation `E.InnerJoin` userAdmin) -> do
E.on $ allocation E.^. AllocationSchool E.==. userAdmin E.^. UserFunctionSchool
E.where_ $ userAdmin E.^. UserFunctionUser E.==. E.val authId
E.&&. userAdmin E.^. UserFunctionFunction E.==. E.val SchoolAdmin
E.&&. allocation E.^. AllocationTerm E.==. E.val tid
E.&&. allocation E.^. AllocationSchool E.==. E.val ssh
E.&&. allocation E.^. AllocationShorthand E.==. E.val ash
guardMExceptT isAdmin (unauthorizedI MsgUnauthorizedSchoolAdmin)
return Authorized
-- Schools: access only to school admins -- Schools: access only to school admins
SchoolR ssh _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do SchoolR ssh _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId authId <- maybeExceptT AuthenticationRequired $ return mAuthId
@ -661,26 +648,6 @@ tagAccessPredicate AuthEvaluation = cacheAPSchoolFunction SchoolEvaluation (Just
isEvaluation <- lift $ exists [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolEvaluation] isEvaluation <- lift $ exists [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolEvaluation]
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedEvaluation guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedEvaluation
return Authorized return Authorized
tagAccessPredicate AuthAllocationAdmin = cacheAPSchoolFunction SchoolAllocation (Just $ Right diffHour) $ \mAuthId' _ _ allocationList -> if
| maybe True (`Set.notMember` allocationList) mAuthId' -> Right $ if
| is _Nothing mAuthId' -> return AuthenticationRequired
| otherwise -> unauthorizedI MsgUnauthorizedAllocationAdmin
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
AllocationR _ ssh _ _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift . existsBy $ UniqueUserFunction authId ssh SchoolAllocation
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedAllocationAdmin
return Authorized
CourseR _ ssh _ _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift . existsBy $ UniqueUserFunction authId ssh SchoolAllocation
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedAllocationAdmin
return Authorized
_other -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift $ exists [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolAllocation]
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedAllocationAdmin
return Authorized
tagAccessPredicate AuthToken = APDB $ \_ _ mAuthId route isWrite -> exceptT return return $ tagAccessPredicate AuthToken = APDB $ \_ _ mAuthId route isWrite -> exceptT return return $
lift . validateBearer mAuthId route isWrite =<< askBearerUnsafe lift . validateBearer mAuthId route isWrite =<< askBearerUnsafe
tagAccessPredicate AuthNoEscalation = APDB $ \_ _ mAuthId route _ -> case route of tagAccessPredicate AuthNoEscalation = APDB $ \_ _ mAuthId route _ -> case route of
@ -710,7 +677,6 @@ tagAccessPredicate AuthLecturer = cacheAPDB' (Just $ Right diffMinute) mkLecture
, maybe True (`Set.notMember` lecturerList) mAuthId' -> Right $ case route' of , maybe True (`Set.notMember` lecturerList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired _ | is _Nothing mAuthId' -> return AuthenticationRequired
CourseR{} -> unauthorizedI MsgUnauthorizedLecturer CourseR{} -> unauthorizedI MsgUnauthorizedLecturer
AllocationR{} -> unauthorizedI MsgUnauthorizedAllocationLecturer
EExamR{} -> unauthorizedI MsgUnauthorizedExternalExamLecturer EExamR{} -> unauthorizedI MsgUnauthorizedExternalExamLecturer
_other -> unauthorizedI MsgUnauthorizedSchoolLecturer _other -> unauthorizedI MsgUnauthorizedSchoolLecturer
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of | otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
@ -724,18 +690,6 @@ tagAccessPredicate AuthLecturer = cacheAPDB' (Just $ Right diffMinute) mkLecture
E.&&. course E.^. CourseShorthand E.==. E.val csh E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT isLecturer (unauthorizedI MsgUnauthorizedLecturer) guardMExceptT isLecturer (unauthorizedI MsgUnauthorizedLecturer)
return Authorized return Authorized
AllocationR tid ssh ash _ -> $cachedHereBinary (mAuthId, tid, ssh, ash) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isLecturer <- lift . E.selectExists . E.from $ \(allocation `E.InnerJoin` allocationCourse `E.InnerJoin` course `E.InnerJoin` lecturer) -> do
E.on $ course E.^. CourseId E.==. lecturer E.^. LecturerCourse
E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse
E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation
E.where_ $ lecturer E.^. LecturerUser E.==. E.val authId
E.&&. allocation E.^. AllocationTerm E.==. E.val tid
E.&&. allocation E.^. AllocationSchool E.==. E.val ssh
E.&&. allocation E.^. AllocationShorthand E.==. E.val ash
guardMExceptT isLecturer $ unauthorizedI MsgUnauthorizedAllocationLecturer
return Authorized
EExamR tid ssh coursen examn _ -> $cachedHereBinary (mAuthId, tid, ssh, coursen, examn) . exceptT return return $ do EExamR tid ssh coursen examn _ -> $cachedHereBinary (mAuthId, tid, ssh, coursen, examn) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isLecturer <- lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` staff) -> do isLecturer <- lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` staff) -> do
@ -755,7 +709,6 @@ tagAccessPredicate AuthLecturer = cacheAPDB' (Just $ Right diffMinute) mkLecture
where where
mkLecturerList _ route _ = case route of mkLecturerList _ route _ = case route of
CourseR{} -> cacheLecturerList CourseR{} -> cacheLecturerList
AllocationR{} -> cacheLecturerList
EExamR{} -> Just EExamR{} -> Just
( AuthCacheExternalExamStaffList ( AuthCacheExternalExamStaffList
, fmap (setOf $ folded . _Value) . E.select . E.from $ return . (E.^. ExternalExamStaffUser) , fmap (setOf $ folded . _Value) . E.select . E.from $ return . (E.^. ExternalExamStaffUser)
@ -1049,31 +1002,6 @@ tagAccessPredicate AuthTime = APDB $ \_ (runTACont -> cont) mAuthId route isWrit
guard $ NTop (Just now) <= NTop deregUntil guard $ NTop (Just now) <= NTop deregUntil
return Authorized return Authorized
_other -> unauthorizedI MsgUnauthorizedCourseRegistrationTime _other -> unauthorizedI MsgUnauthorizedCourseRegistrationTime
CApplicationR tid ssh csh _ _ -> maybeT (unauthorizedI MsgUnauthorizedApplicationTime) $ do
Entity course Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
allocationCourse <- $cachedHereBinary course . lift . getBy $ UniqueAllocationCourse course
allocation <- for allocationCourse $ \(Entity _ AllocationCourse{..}) -> $cachedHereBinary allocationCourseAllocation . MaybeT $ get allocationCourseAllocation
case allocation of
Nothing -> do
cTime <- liftIO getCurrentTime
guard $ maybe False (cTime >=) courseRegisterFrom
guard $ maybe True (cTime <=) courseRegisterTo
Just Allocation{..} -> do
cTime <- liftIO getCurrentTime
guard $ NTop allocationRegisterFrom <= NTop (Just cTime)
guard $ NTop (Just cTime) <= NTop allocationRegisterTo
return Authorized
AllocationR tid ssh ash _ -> maybeT (unauthorizedI MsgUnauthorizedAllocationRegisterTime) $ do
-- Checks `registerFrom` and `registerTo`, override as further routes become available
now <- liftIO getCurrentTime
Entity _ Allocation{..} <- MaybeT . $cachedHereBinary (tid, ssh, ash) . getBy $ TermSchoolAllocationShort tid ssh ash
guard $ NTop allocationRegisterFrom <= NTop (Just now)
guard $ NTop (Just now) <= NTop allocationRegisterTo
return Authorized
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do
smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
@ -1111,75 +1039,6 @@ tagAccessPredicate AuthTime = APDB $ \_ (runTACont -> cont) mAuthId route isWrit
return Authorized return Authorized
r -> $unsupportedAuthPredicate AuthTime r r -> $unsupportedAuthPredicate AuthTime r
tagAccessPredicate AuthStaffTime = APDB $ \_ _ _ route isWrite -> case route of
CApplicationR tid ssh csh _ _ -> maybeT (unauthorizedI MsgUnauthorizedApplicationTime) $ do
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
allocationCourse <- $cachedHereBinary course . lift . getBy $ UniqueAllocationCourse course
allocation <- for allocationCourse $ \(Entity _ AllocationCourse{..}) -> $cachedHereBinary allocationCourseAllocation . MaybeT $ get allocationCourseAllocation
case allocation of
Nothing -> return ()
Just Allocation{..} -> do
cTime <- liftIO getCurrentTime
guard $ NTop allocationStaffAllocationFrom <= NTop (Just cTime)
when isWrite $
guard $ NTop (Just cTime) <= NTop allocationStaffAllocationTo
return Authorized
AllocationR tid ssh ash _ -> maybeT (unauthorizedI MsgUnauthorizedAllocationRegisterTime) $ do
-- Checks `registerFrom` and `registerTo`, override as further routes become available
now <- liftIO getCurrentTime
Entity _ Allocation{..} <- MaybeT . $cachedHereBinary (tid, ssh, ash) . getBy $ TermSchoolAllocationShort tid ssh ash
guard $ NTop allocationStaffAllocationFrom <= NTop (Just now)
guard $ NTop (Just now) <= NTop allocationStaffAllocationTo
return Authorized
r -> $unsupportedAuthPredicate AuthStaffTime r
tagAccessPredicate AuthAllocationTime = APDB $ \_ (runTACont -> cont) mAuthId route isWrite -> case route of
CourseR tid ssh csh CRegisterR -> do
now <- liftIO getCurrentTime
mba <- mbAllocation tid ssh csh
case mba of
Nothing -> return Authorized
Just (_, Allocation{..}) -> do
registered <- cont (predDNFSingleton $ PLVariable AuthCourseRegistered) mAuthId route isWrite
if
| not registered
, NTop allocationRegisterByCourse >= NTop (Just now)
-> unauthorizedI MsgUnauthorizedAllocatedCourseRegister
| registered
, NTop (Just now) >= NTop allocationOverrideDeregister
-> unauthorizedI MsgUnauthorizedAllocatedCourseDeregister
| otherwise
-> return Authorized
CourseR tid ssh csh CAddUserR -> do
now <- liftIO getCurrentTime
mba <- mbAllocation tid ssh csh
case mba of
Just (_, Allocation{..})
| NTop allocationRegisterByStaffTo <= NTop (Just now)
|| NTop allocationRegisterByStaffFrom >= NTop (Just now)
-> unauthorizedI MsgUnauthorizedAllocatedCourseRegister
_other -> return Authorized
CourseR tid ssh csh CDeleteR -> do
now <- liftIO getCurrentTime
mba <- mbAllocation tid ssh csh
case mba of
Just (_, Allocation{..})
| NTop allocationRegisterByStaffTo <= NTop (Just now)
|| NTop allocationRegisterByStaffFrom >= NTop (Just now)
-> unauthorizedI MsgUnauthorizedAllocatedCourseDelete
_other -> return Authorized
r -> $unsupportedAuthPredicate AuthAllocationTime r
where
mbAllocation tid ssh csh = $cachedHereBinary (tid, ssh, csh) . runMaybeT $ do
cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity _ AllocationCourse{..} <- MaybeT . getBy $ UniqueAllocationCourse cid
(cid,) <$> MaybeT (get allocationCourseAllocation)
tagAccessPredicate AuthCourseTime = APDB $ \_ _ _mAuthId route _ -> case route of tagAccessPredicate AuthCourseTime = APDB $ \_ _ _mAuthId route _ -> case route of
CourseR tid ssh csh _ -> exceptT return return $ do CourseR tid ssh csh _ -> exceptT return return $ do
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
@ -1187,7 +1046,7 @@ tagAccessPredicate AuthCourseTime = APDB $ \_ _ _mAuthId route _ -> case route o
E.where_ $ course E.^. CourseTerm E.==. E.val tid E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. courseIsVisible now course E.nothing E.&&. courseIsVisible now course
guardMExceptT courseVisible (unauthorizedI MsgUnauthorizedCourseTime) guardMExceptT courseVisible (unauthorizedI MsgUnauthorizedCourseTime)
return Authorized return Authorized
r -> $unsupportedAuthPredicate AuthCourseTime r r -> $unsupportedAuthPredicate AuthCourseTime r
@ -1406,13 +1265,6 @@ tagAccessPredicate AuthExamResult = APDB $ \_ _ mAuthId route _ -> case route of
guardMExceptT (hasResult || hasPartResult) (unauthorizedI MsgUnauthorizedExamResult) guardMExceptT (hasResult || hasPartResult) (unauthorizedI MsgUnauthorizedExamResult)
return Authorized return Authorized
r -> $unsupportedAuthPredicate AuthExamRegistered r r -> $unsupportedAuthPredicate AuthExamRegistered r
tagAccessPredicate AuthAllocationRegistered = APDB $ \_ _ mAuthId route _ -> case route of
AllocationR tid ssh ash _ -> maybeT (unauthorizedI MsgUnauthorizedAllocationRegistered) $ do
uid <- hoistMaybe mAuthId
aId <- MaybeT . $cachedHereBinary (tid, ssh, ash) . getKeyBy $ TermSchoolAllocationShort tid ssh ash
void . MaybeT . $cachedHereBinary (uid, aId) . getKeyBy $ UniqueAllocationUser aId uid
return Authorized
r -> $unsupportedAuthPredicate AuthAllocationRegistered r
tagAccessPredicate AuthParticipant = APDB $ \_ _ mAuthId route _ -> case route of tagAccessPredicate AuthParticipant = APDB $ \_ _ mAuthId route _ -> case route of
CNewsR tid ssh csh cID _ -> maybeT (unauthorizedI MsgUnauthorizedParticipantSelf) $ do CNewsR tid ssh csh cID _ -> maybeT (unauthorizedI MsgUnauthorizedParticipantSelf) $ do
nId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID nId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
@ -1521,27 +1373,6 @@ tagAccessPredicate AuthParticipant = APDB $ \_ _ mAuthId route _ -> case route o
E.&&. course E.^. CourseTerm E.==. E.val tid E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh E.&&. course E.^. CourseShorthand E.==. E.val csh
tagAccessPredicate AuthApplicant = APDB $ \_ _ mAuthId route _ -> case route of
CourseR tid ssh csh (CUserR cID) -> maybeT (unauthorizedI MsgUnauthorizedApplicant) $ do
uid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
isApplicant <- isCourseApplicant tid ssh csh uid
guard isApplicant
return Authorized
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnauthorizedApplicantSelf) $ do
uid <- hoistMaybe mAuthId
isApplicant <- isCourseApplicant tid ssh csh uid
guard isApplicant
return Authorized
r -> $unsupportedAuthPredicate AuthApplicant r
where
isCourseApplicant tid ssh csh uid = lift . $cachedHereBinary (uid, tid, ssh, csh) . E.selectExists . E.from $ \(course `E.InnerJoin` courseApplication) -> do
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. E.val uid
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
tagAccessPredicate AuthCapacity = APDB $ \_ _ _ route _ -> case route of tagAccessPredicate AuthCapacity = APDB $ \_ _ _ route _ -> case route of
CExamR tid ssh csh examn (ERegisterOccR occn) -> maybeT (unauthorizedI MsgExamOccurrenceNoCapacity) $ do CExamR tid ssh csh examn (ERegisterOccR occn) -> maybeT (unauthorizedI MsgExamOccurrenceNoCapacity) $ do
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
@ -1667,10 +1498,6 @@ tagAccessPredicate AuthSelf = APDB $ \_ _ mAuthId route _ -> exceptT return retu
CourseR _ _ _ (CUserR cID) -> return $ Left cID CourseR _ _ _ (CUserR cID) -> return $ Left cID
ForProfileR cID -> return $ Left cID ForProfileR cID -> return $ Left cID
ForProfileDataR cID -> return $ Left cID ForProfileDataR cID -> return $ Left cID
CApplicationR _ _ _ cID _ -> do
appId <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSelf) (const True :: CryptoIDError -> Bool) $ decrypt cID
CourseApplication{..} <- maybeMExceptT (unauthorizedI MsgUnauthorizedSelf) . $cachedHereBinary appId $ get appId
return $ Right courseApplicationUser
_other -> throwError =<< $unsupportedAuthPredicate AuthSelf route _other -> throwError =<< $unsupportedAuthPredicate AuthSelf route
referencedUser <- case referencedUser' of referencedUser <- case referencedUser' of
Right uid -> return uid Right uid -> return uid
@ -1762,7 +1589,6 @@ routeAuthTags = fmap predDNFEntail . ofoldM parse defaultAuthDNF . routeAttrs
broadenRoute :: AuthTag -> Route UniWorX -> Route UniWorX broadenRoute :: AuthTag -> Route UniWorX -> Route UniWorX
broadenRoute aTag route = case (aTag, route) of broadenRoute aTag route = case (aTag, route) of
(AuthAdmin, CourseR tid ssh csh _) -> CourseR tid ssh csh CShowR (AuthAdmin, CourseR tid ssh csh _) -> CourseR tid ssh csh CShowR
(AuthAdmin, AllocationR tid ssh ash _) -> AllocationR tid ssh ash AShowR
(AuthAdmin, SchoolR ssh _) -> SchoolR ssh SchoolEditR (AuthAdmin, SchoolR ssh _) -> SchoolR ssh SchoolEditR
(AuthAdmin, _) -> NewsR (AuthAdmin, _) -> NewsR
@ -1775,7 +1601,6 @@ broadenRoute aTag route = case (aTag, route) of
(AuthExamOffice, _) -> NewsR (AuthExamOffice, _) -> NewsR
(AuthLecturer, CourseR tid ssh csh _) -> CourseR tid ssh csh CShowR (AuthLecturer, CourseR tid ssh csh _) -> CourseR tid ssh csh CShowR
(AuthLecturer, AllocationR tid ssh ash _) -> AllocationR tid ssh ash AShowR
(AuthLecturer, EExamR tid ssh coursen examn _) -> EExamR tid ssh coursen examn EEShowR (AuthLecturer, EExamR tid ssh coursen examn _) -> EExamR tid ssh coursen examn EEShowR
(AuthLecturer, _) -> NewsR (AuthLecturer, _) -> NewsR
@ -1920,7 +1745,4 @@ authoritiveApproot = \case
CourseR _ _ _ (SheetR _ (SubmissionR _ (SubArchiveR _))) -> ApprootUserGenerated CourseR _ _ _ (SheetR _ (SubmissionR _ (SubArchiveR _))) -> ApprootUserGenerated
CourseR _ _ _ (CourseNewsR _ (CNFileR _)) -> ApprootUserGenerated CourseR _ _ _ (CourseNewsR _ (CNFileR _)) -> ApprootUserGenerated
CourseR _ _ _ (CourseNewsR _ CNArchiveR) -> ApprootUserGenerated CourseR _ _ _ (CourseNewsR _ CNArchiveR) -> ApprootUserGenerated
CourseR _ _ _ CRegisterTemplateR -> ApprootUserGenerated
CourseR _ _ _ CAppsFilesR -> ApprootUserGenerated
CourseR _ _ _ (CourseApplicationR _ CAFilesR) -> ApprootUserGenerated
_other -> ApprootDefault _other -> ApprootDefault

View File

@ -9,7 +9,7 @@ module Foundation.I18n
( appLanguages, appLanguagesOpts ( appLanguages, appLanguagesOpts
, UniWorXMessage(..), UniWorXTestMessage(..), UniWorXSettingsMessage(..) , UniWorXMessage(..), UniWorXTestMessage(..), UniWorXSettingsMessage(..)
, UniWorXHelpMessage(..), UniWorXNavigationMessage(..) , UniWorXHelpMessage(..), UniWorXNavigationMessage(..)
, UniWorXCourseMessage(..), UniWorXAllocationMessage(..), UniWorXExamMessage(..) , UniWorXCourseMessage(..), UniWorXExamMessage(..)
, UniWorXSheetMessage(..), UniWorXAdminMessage(..), UniWorXSubmissionMessage(..) , UniWorXSheetMessage(..), UniWorXAdminMessage(..), UniWorXSubmissionMessage(..)
, UniWorXTutorialMessage(..), UniWorXUserMessage(..), UniWorXButtonMessage(..) , UniWorXTutorialMessage(..), UniWorXUserMessage(..), UniWorXButtonMessage(..)
, UniWorXFormMessage(..), UniWorXRatingMessage(..), UniWorXTableColumnMessage(..) , UniWorXFormMessage(..), UniWorXRatingMessage(..), UniWorXTableColumnMessage(..)
@ -135,10 +135,10 @@ noneOneMoreEN num noneText singularForm pluralForm
-- | num == 0 = noneText -- | num == 0 = noneText
-- | otherwise = someText -- | otherwise = someText
ordinalEN :: ToMessage a _ordinalEN :: ToMessage a
=> a => a
-> Text -> Text
ordinalEN (toMessage -> numStr) = case lastChar of _ordinalEN (toMessage -> numStr) = case lastChar of
Just '1' -> [st|#{numStr}st|] Just '1' -> [st|#{numStr}st|]
Just '2' -> [st|#{numStr}nd|] Just '2' -> [st|#{numStr}nd|]
Just '3' -> [st|#{numStr}rd|] Just '3' -> [st|#{numStr}rd|]
@ -189,7 +189,6 @@ mkMessageAddition ''UniWorX "Settings" "messages/uniworx/categories/settings" "d
mkMessageAddition ''UniWorX "Help" "messages/uniworx/categories/help" "de-de-formal" mkMessageAddition ''UniWorX "Help" "messages/uniworx/categories/help" "de-de-formal"
mkMessageAddition ''UniWorX "Navigation" "messages/uniworx/utils/navigation" "de-de-formal" mkMessageAddition ''UniWorX "Navigation" "messages/uniworx/utils/navigation" "de-de-formal"
mkMessageAddition ''UniWorX "Course" "messages/uniworx/categories/courses/courses" "de-de-formal" mkMessageAddition ''UniWorX "Course" "messages/uniworx/categories/courses/courses" "de-de-formal"
mkMessageAddition ''UniWorX "Allocation" "messages/uniworx/categories/courses/allocation" "de-de-formal"
mkMessageAddition ''UniWorX "Exam" "messages/uniworx/categories/courses/exam" "de-de-formal" mkMessageAddition ''UniWorX "Exam" "messages/uniworx/categories/courses/exam" "de-de-formal"
mkMessageAddition ''UniWorX "Sheet" "messages/uniworx/categories/courses/sheet" "de-de-formal" mkMessageAddition ''UniWorX "Sheet" "messages/uniworx/categories/courses/sheet" "de-de-formal"
mkMessageAddition ''UniWorX "Admin" "messages/uniworx/categories/admin" "de-de-formal" mkMessageAddition ''UniWorX "Admin" "messages/uniworx/categories/admin" "de-de-formal"

View File

@ -106,14 +106,19 @@ breadcrumb (UserPasswordR cID) = useRunDB $ do
breadcrumb AdminNewFunctionaryInviteR = i18nCrumb MsgMenuLecturerInvite $ Just UsersR breadcrumb AdminNewFunctionaryInviteR = i18nCrumb MsgMenuLecturerInvite $ Just UsersR
breadcrumb AdminFunctionaryInviteR = i18nCrumb MsgBreadcrumbFunctionaryInvite Nothing breadcrumb AdminFunctionaryInviteR = i18nCrumb MsgBreadcrumbFunctionaryInvite Nothing
breadcrumb AdminR = i18nCrumb MsgAdminHeading Nothing breadcrumb AdminR = i18nCrumb MsgAdminHeading Nothing
breadcrumb AdminTestR = i18nCrumb MsgMenuAdminTest $ Just AdminR breadcrumb AdminTestR = i18nCrumb MsgMenuAdminTest $ Just AdminR
breadcrumb AdminTestPdfR = i18nCrumb MsgMenuAdminTest $ Just AdminTestR breadcrumb AdminTestPdfR = i18nCrumb MsgMenuAdminTest $ Just AdminTestR
breadcrumb AdminErrMsgR = i18nCrumb MsgMenuAdminErrMsg $ Just AdminR breadcrumb AdminErrMsgR = i18nCrumb MsgMenuAdminErrMsg $ Just AdminR
breadcrumb AdminTokensR = i18nCrumb MsgMenuAdminTokens $ Just AdminR breadcrumb AdminTokensR = i18nCrumb MsgMenuAdminTokens $ Just AdminR
breadcrumb AdminCrontabR = i18nCrumb MsgBreadcrumbAdminCrontab $ Just AdminR breadcrumb AdminCrontabR = i18nCrumb MsgBreadcrumbAdminCrontab $ Just AdminR
breadcrumb AdminAvsR = i18nCrumb MsgMenuAvs $ Just AdminR breadcrumb AdminAvsR = i18nCrumb MsgMenuAvs $ Just AdminR
breadcrumb AdminLdapR = i18nCrumb MsgMenuLdap $ Just AdminR breadcrumb AdminLdapR = i18nCrumb MsgMenuLdap $ Just AdminR
breadcrumb AdminProblemsR = i18nCrumb MsgProblemsHeading $ Just AdminR
breadcrumb ProblemUnreachableR = i18nCrumb MsgProblemsUnreachableHeading $ Just AdminProblemsR
breadcrumb ProblemWithoutAvsId = i18nCrumb MsgProblemsNoAvsIdHeading $ Just AdminProblemsR
breadcrumb ProblemFbutNoR = i18nCrumb MsgProblemsRWithoutFHeading $ Just AdminProblemsR
breadcrumb ProblemAvsSynchR = i18nCrumb MsgProblemsAvsSynchHeading $ Just AdminProblemsR
breadcrumb PrintCenterR = i18nCrumb MsgMenuApc Nothing breadcrumb PrintCenterR = i18nCrumb MsgMenuApc Nothing
breadcrumb PrintSendR = i18nCrumb MsgMenuPrintSend $ Just PrintCenterR breadcrumb PrintSendR = i18nCrumb MsgMenuPrintSend $ Just PrintCenterR
@ -143,7 +148,6 @@ breadcrumb DataProtectionR = i18nCrumb MsgMenuDataProt $ Just Legal
breadcrumb TermsOfUseR = i18nCrumb MsgMenuTermsUse $ Just LegalR breadcrumb TermsOfUseR = i18nCrumb MsgMenuTermsUse $ Just LegalR
breadcrumb PaymentsR = i18nCrumb MsgMenuPayments $ Just LegalR breadcrumb PaymentsR = i18nCrumb MsgMenuPayments $ Just LegalR
breadcrumb InfoAllocationR = i18nCrumb MsgBreadcrumbAllocationInfo $ Just InfoR
breadcrumb VersionR = i18nCrumb MsgMenuVersion $ Just InfoR breadcrumb VersionR = i18nCrumb MsgMenuVersion $ Just InfoR
breadcrumb FaqR = i18nCrumb MsgBreadcrumbFaq $ Just InfoR breadcrumb FaqR = i18nCrumb MsgBreadcrumbFaq $ Just InfoR
@ -207,36 +211,6 @@ breadcrumb (TermSchoolCourseListR tid ssh) = useRunDB . maybeT (i18nCrumb MsgBre
<*> fmap isJust (get tid) <*> fmap isJust (get tid)
return (CI.original $ unSchoolKey ssh, Just $ TermCourseListR tid) return (CI.original $ unSchoolKey ssh, Just $ TermCourseListR tid)
breadcrumb AllocationListR = i18nCrumb MsgAllocationListTitle $ Just NewsR
breadcrumb AllocationNewR = i18nCrumb MsgBreadcrumbAllocationNew $ Just AllocationListR
breadcrumb (AllocationR tid ssh ash sRoute) = case sRoute of
AShowR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbAllocation $ Just AllocationListR) $ do
mr <- getMessageRender
Entity _ Allocation{allocationName} <- MaybeT . getBy $ TermSchoolAllocationShort tid ssh ash
return ([st|#{allocationName} (#{mr (ShortTermIdentifier (unTermKey tid))}, #{CI.original (unSchoolKey ssh)})|], Just AllocationListR)
AEditR -> i18nCrumb MsgBreadcrumbAllocationEdit . Just $ AllocationR tid ssh ash AShowR
AMatchingListR -> i18nCrumb MsgBreadcrumbAllocationMatchings . Just $ AllocationR tid ssh ash AShowR
AMatchingR _ AMLogR -> i18nCrumb MsgBreadcrumbAllocationMatchingLog . Just $ AllocationR tid ssh ash AMatchingListR
ARegisterR -> i18nCrumb MsgBreadcrumbAllocationRegister . Just $ AllocationR tid ssh ash AShowR
AApplyR cID -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbCourse . Just $ AllocationR tid ssh ash AShowR) $ do
cid <- decrypt cID
Course{..} <- do
aid <- MaybeT . getKeyBy $ TermSchoolAllocationShort tid ssh ash
guardM . lift $ exists [ AllocationCourseAllocation ==. aid, AllocationCourseCourse ==. cid ]
MaybeT $ get cid
return (CI.original courseName, Just $ AllocationR tid ssh ash AShowR)
AUsersR -> i18nCrumb MsgBreadcrumbAllocationUsers . Just $ AllocationR tid ssh ash AShowR
APriosR -> i18nCrumb MsgBreadcrumbAllocationPriorities . Just $ AllocationR tid ssh ash AUsersR
AComputeR -> i18nCrumb MsgBreadcrumbAllocationCompute . Just $ AllocationR tid ssh ash AUsersR
AAcceptR -> i18nCrumb MsgBreadcrumbAllocationAccept . Just $ AllocationR tid ssh ash AUsersR
AAddUserR -> i18nCrumb MsgBreadcrumbAllocationAddUser . Just $ AllocationR tid ssh ash AUsersR
AEditUserR cID -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbAllocationEditUser . Just $ AllocationR tid ssh ash AUsersR) $ do
guardM . lift . hasReadAccessTo . AllocationR tid ssh ash $ AEditUserR cID
uid <- decrypt cID
User{..} <- MaybeT $ get uid
return (userDisplayName, Just $ AllocationR tid ssh ash AUsersR)
ADelUserR cID -> i18nCrumb MsgBreadcrumbAllocationDelUser . Just $ AllocationR tid ssh ash (AEditUserR cID)
breadcrumb ParticipantsListR = i18nCrumb MsgBreadcrumbParticipantsList $ Just CourseListR breadcrumb ParticipantsListR = i18nCrumb MsgBreadcrumbParticipantsList $ Just CourseListR
breadcrumb (ParticipantsR _ _) = i18nCrumb MsgBreadcrumbParticipants $ Just ParticipantsListR breadcrumb (ParticipantsR _ _) = i18nCrumb MsgBreadcrumbParticipants $ Just ParticipantsListR
breadcrumb ParticipantsIntersectR = i18nCrumb MsgMenuParticipantsIntersect $ Just ParticipantsListR breadcrumb ParticipantsIntersectR = i18nCrumb MsgMenuParticipantsIntersect $ Just ParticipantsListR
@ -249,7 +223,6 @@ breadcrumb (CourseR tid ssh csh CShowR) = useRunDB . maybeT (i18nCrumb MsgBreadc
breadcrumb (CourseR tid ssh csh CEditR) = i18nCrumb MsgMenuCourseEdit . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CEditR) = i18nCrumb MsgMenuCourseEdit . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CUsersR) = i18nCrumb MsgMenuCourseMembers . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CUsersR) = i18nCrumb MsgMenuCourseMembers . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CAddUserR) = i18nCrumb MsgMenuCourseAddMembers . Just $ CourseR tid ssh csh CUsersR breadcrumb (CourseR tid ssh csh CAddUserR) = i18nCrumb MsgMenuCourseAddMembers . Just $ CourseR tid ssh csh CUsersR
breadcrumb (CourseR tid ssh csh CInviteR) = i18nCrumb MsgBreadcrumbCourseParticipantInvitation . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CExamOfficeR) = i18nCrumb MsgMenuCourseExamOffice . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CExamOfficeR) = i18nCrumb MsgMenuCourseExamOffice . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh (CUserR cID)) = useRunDB . maybeT (i18nCrumb MsgBreadcrumbUser . Just $ CourseR tid ssh csh CUsersR) $ do breadcrumb (CourseR tid ssh csh (CUserR cID)) = useRunDB . maybeT (i18nCrumb MsgBreadcrumbUser . Just $ CourseR tid ssh csh CUsersR) $ do
guardM . lift . hasReadAccessTo . CourseR tid ssh csh $ CUserR cID guardM . lift . hasReadAccessTo . CourseR tid ssh csh $ CUserR cID
@ -267,7 +240,6 @@ breadcrumb (CourseR tid ssh csh CTutorialListR) = i18nCrumb MsgMenuTutorialList
breadcrumb (CourseR tid ssh csh CTutorialNewR) = i18nCrumb MsgMenuTutorialNew . Just $ CourseR tid ssh csh CTutorialListR breadcrumb (CourseR tid ssh csh CTutorialNewR) = i18nCrumb MsgMenuTutorialNew . Just $ CourseR tid ssh csh CTutorialListR
breadcrumb (CourseR tid ssh csh CFavouriteR) = i18nCrumb MsgBreadcrumbCourseFavourite . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CFavouriteR) = i18nCrumb MsgBreadcrumbCourseFavourite . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CRegisterR) = i18nCrumb MsgBreadcrumbCourseRegister . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CRegisterR) = i18nCrumb MsgBreadcrumbCourseRegister . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CRegisterTemplateR) = i18nCrumb MsgBreadcrumbCourseRegisterTemplate . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CLecInviteR) = i18nCrumb MsgBreadcrumbLecturerInvite . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CLecInviteR) = i18nCrumb MsgBreadcrumbLecturerInvite . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CDeleteR) = i18nCrumb MsgMenuCourseDelete . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CDeleteR) = i18nCrumb MsgMenuCourseDelete . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CHiWisR) = i18nCrumb MsgBreadcrumbHiWis . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CHiWisR) = i18nCrumb MsgBreadcrumbHiWis . Just $ CourseR tid ssh csh CShowR
@ -289,17 +261,6 @@ breadcrumb (CourseR tid ssh csh (CourseEventR _cID sRoute)) = case sRoute of
breadcrumb (CourseR tid ssh csh CExamListR) = i18nCrumb MsgMenuExamList . Just $ CourseR tid ssh csh CShowR breadcrumb (CourseR tid ssh csh CExamListR) = i18nCrumb MsgMenuExamList . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CExamNewR) = i18nCrumb MsgMenuExamNew . Just $ CourseR tid ssh csh CExamListR breadcrumb (CourseR tid ssh csh CExamNewR) = i18nCrumb MsgMenuExamNew . Just $ CourseR tid ssh csh CExamListR
breadcrumb (CourseR tid ssh csh CApplicationsR) = i18nCrumb MsgMenuCourseApplications . Just $ CourseR tid ssh csh CShowR
breadcrumb (CourseR tid ssh csh CAppsFilesR) = i18nCrumb MsgBreadcrumbCourseAppsFiles . Just $ CourseR tid ssh csh CApplicationsR
breadcrumb (CourseR tid ssh csh (CourseApplicationR cID sRoute)) = case sRoute of
CAEditR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbApplicant . Just $ CourseR tid ssh csh CApplicationsR) $ do
guardM . lift . hasReadAccessTo $ CApplicationR tid ssh csh cID CAEditR
appId <- decrypt cID
User{..} <- MaybeT (get appId) >>= MaybeT . get . courseApplicationUser
return (userDisplayName, Just $ CourseR tid ssh csh CApplicationsR)
CAFilesR -> i18nCrumb MsgBreadcrumbApplicationFiles . Just $ CApplicationR tid ssh csh cID CAEditR
breadcrumb (CourseR tid ssh csh (ExamR examn sRoute)) = case sRoute of breadcrumb (CourseR tid ssh csh (ExamR examn sRoute)) = case sRoute of
EShowR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbExam . Just $ CourseR tid ssh csh CExamListR) $ do EShowR -> useRunDB . maybeT (i18nCrumb MsgBreadcrumbExam . Just $ CourseR tid ssh csh CExamListR) $ do
guardM . lift . hasReadAccessTo $ CExamR tid ssh csh examn EShowR guardM . lift . hasReadAccessTo $ CExamR tid ssh csh examn EShowR
@ -776,6 +737,14 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the
, navIcon = IconMenuAdmin , navIcon = IconMenuAdmin
, navChildren = , navChildren =
[ NavLink [ NavLink
{ navLabel = MsgProblemsHeading
, navRoute = AdminR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, NavLink
{ navLabel = MsgMenuUsers { navLabel = MsgMenuUsers
, navRoute = UsersR , navRoute = UsersR
, navAccess' = NavAccessTrue , navAccess' = NavAccessTrue
@ -878,14 +847,6 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the
, navQuick' = mempty , navQuick' = mempty
, navForceActive = False , navForceActive = False
} }
, NavLink
{ navLabel = MsgMenuAllocationList
, navRoute = AllocationListR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, NavLink , NavLink
{ navLabel = MsgMenuInfoLecturerTitle { navLabel = MsgMenuInfoLecturerTitle
, navRoute = InfoLecturerR , navRoute = InfoLecturerR
@ -918,17 +879,6 @@ pageActions NewsR = return
} }
, navChildren = [] , navChildren = []
} }
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuOpenAllocations
, navRoute = (AllocationListR, [("allocations-active", toPathPiece True)])
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
] ]
pageActions (CourseR tid ssh csh CShowR) = do pageActions (CourseR tid ssh csh CShowR) = do
materialListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh MaterialListR materialListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh MaterialListR
@ -1355,7 +1305,6 @@ pageActions HelpR = return
, ("exercises", MsgMenuInfoLecturerExercises) , ("exercises", MsgMenuInfoLecturerExercises)
, ("tutorials", MsgMenuInfoLecturerTutorials) , ("tutorials", MsgMenuInfoLecturerTutorials)
, ("exams", MsgMenuInfoLecturerExams) , ("exams", MsgMenuInfoLecturerExams)
, ("allocations", MsgMenuInfoLecturerAllocations)
] :: [(Text, UniWorXNavigationMessage)] ] :: [(Text, UniWorXNavigationMessage)]
return NavLink return NavLink
{ navLabel { navLabel
@ -1452,121 +1401,6 @@ pageActions TermShowR = do
, navChildren = participantsSecondary , navChildren = participantsSecondary
} }
] ]
pageActions AllocationListR = return
[ NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationNew
, navRoute = AllocationNewR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
]
pageActions (AllocationR tid ssh ash AShowR) = return
[ NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationInfo
, navRoute = InfoAllocationR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = True }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationUsers
, navRoute = AllocationR tid ssh ash AUsersR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationCompute
, navRoute = AllocationR tid ssh ash AComputeR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
, NavPageActionSecondary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationEdit
, navRoute = AllocationR tid ssh ash AEditR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
}
, NavPageActionSecondary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationMatchings
, navRoute = AllocationR tid ssh ash AMatchingListR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = True }
, navQuick' = mempty
, navForceActive = False
}
}
]
pageActions (AllocationR tid ssh ash AUsersR) = return
[ NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationPriorities
, navRoute = AllocationR tid ssh ash APriosR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationCompute
, navRoute = AllocationR tid ssh ash AComputeR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationAddUser
, navRoute = AllocationR tid ssh ash AAddUserR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
]
pageActions (AllocationR tid ssh ash (AEditUserR cID)) = return
[ NavPageActionSecondary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationDelUser
, navRoute = AllocationR tid ssh ash $ ADelUserR cID
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = True }
, navQuick' = mempty
, navForceActive = False
}
}
]
pageActions CourseListR = do pageActions CourseListR = do
participantsSecondary <- pageQuickActions NavQuickViewPageActionSecondary ParticipantsListR participantsSecondary <- pageQuickActions NavQuickViewPageActionSecondary ParticipantsListR
return return
@ -1581,17 +1415,6 @@ pageActions CourseListR = do
} }
, navChildren = [] , navChildren = []
} }
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuAllocationList
, navRoute = AllocationListR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
, NavPageActionPrimary , NavPageActionPrimary
{ navLink = NavLink { navLink = NavLink
{ navLabel = MsgMenuParticipantsList { navLabel = MsgMenuParticipantsList
@ -1727,31 +1550,6 @@ pageActions (CourseR tid ssh csh CUsersR) = return
} }
, navChildren = [] , navChildren = []
} }
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuCourseApplications
, navRoute = CourseR tid ssh csh CApplicationsR
, navAccess' = NavAccessDB $
let courseWhere course = course <$ do
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
existsApplications = E.selectExists . E.from $ \(course `E.InnerJoin` courseApplication) -> do
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
void $ courseWhere course
courseApplications = fmap (any E.unValue) . E.select . E.from $ \course -> do
void $ courseWhere course
return $ course E.^. CourseApplicationsRequired
courseAllocation = E.selectExists . E.from $ \(course `E.InnerJoin` allocationCourse) -> do
E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse
void $ courseWhere course
in courseAllocation `or2M` courseApplications `or2M` existsApplications
, navType = NavTypeLink { navModal = False }
, navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite
, navForceActive = False
}
, navChildren = []
}
] ]
pageActions (CourseR tid ssh csh MaterialListR) = return pageActions (CourseR tid ssh csh MaterialListR) = return
[ NavPageActionPrimary [ NavPageActionPrimary
@ -1814,40 +1612,53 @@ pageActions (CTutorialR tid ssh csh tutn TEditR) = return
} }
} }
] ]
pageActions (CTutorialR tid ssh csh tutn TUsersR) = return pageActions (CTutorialR tid ssh csh tutn TUsersR) = do
[ NavPageActionPrimary membersSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh CUsersR
{ navLink = NavLink return
{ navLabel = MsgMenuTutorialComm [ NavPageActionPrimary
, navRoute = CTutorialR tid ssh csh tutn TCommR { navLink = NavLink
, navAccess' = NavAccessTrue { navLabel = MsgMenuCourseMembers
, navType = NavTypeLink { navModal = False } , navRoute = CourseR tid ssh csh CUsersR
, navQuick' = mempty , navAccess' = NavAccessTrue
, navForceActive = False , navType = NavTypeLink { navModal = False }
, navQuick' = navQuick NavQuickViewPageActionSecondary
, navForceActive = False
}
, navChildren = membersSecondary
} }
, navChildren = [] , NavPageActionPrimary
} { navLink = NavLink
, NavPageActionPrimary { navLabel = MsgMenuTutorialComm
{ navLink = NavLink , navRoute = CTutorialR tid ssh csh tutn TCommR
{ navLabel = MsgMenuTutorialEdit , navAccess' = NavAccessTrue
, navRoute = CTutorialR tid ssh csh tutn TEditR , navType = NavTypeLink { navModal = False }
, navAccess' = NavAccessTrue , navQuick' = mempty
, navType = NavTypeLink { navModal = False } , navForceActive = False
, navQuick' = mempty }
, navForceActive = False , navChildren = []
} }
, navChildren = [] , NavPageActionPrimary
} { navLink = NavLink
, NavPageActionSecondary { navLabel = MsgMenuTutorialEdit
{ navLink = NavLink , navRoute = CTutorialR tid ssh csh tutn TEditR
{ navLabel = MsgMenuTutorialDelete , navAccess' = NavAccessTrue
, navRoute = CTutorialR tid ssh csh tutn TDeleteR , navType = NavTypeLink { navModal = False }
, navAccess' = NavAccessTrue , navQuick' = mempty
, navType = NavTypeLink { navModal = False } , navForceActive = False
, navQuick' = mempty }
, navForceActive = False , navChildren = []
} }
} , NavPageActionSecondary
] { navLink = NavLink
{ navLabel = MsgMenuTutorialDelete
, navRoute = CTutorialR tid ssh csh tutn TDeleteR
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
}
]
pageActions (CourseR tid ssh csh CExamListR) = return pageActions (CourseR tid ssh csh CExamListR) = return
[ NavPageActionPrimary [ NavPageActionPrimary
{ navLink = NavLink { navLink = NavLink
@ -2200,44 +2011,6 @@ pageActions (CSubmissionR tid ssh csh shn cid CorrectionR) = return
} }
} }
] ]
pageActions (CourseR tid ssh csh CApplicationsR) = return
[ NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuCourseApplicationsFiles
, navRoute = CourseR tid ssh csh CAppsFilesR
, navAccess' = NavAccessDB $
let appAccess (E.Value appId) = do
cID <- encrypt appId
hasReadAccessTo $ CApplicationR tid ssh csh cID CAFilesR
appSource = E.selectSource . E.from $ \(course `E.InnerJoin` courseApplication) -> do
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.where_ . E.exists . E.from $ \courseApplicationFile ->
E.where_ $ courseApplicationFile E.^. CourseApplicationFileApplication E.==. courseApplication E.^. CourseApplicationId
return $ courseApplication E.^. CourseApplicationId
in runConduit $ appSource .| anyMC appAccess
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
, NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuCourseMembers
, navRoute = CourseR tid ssh csh CUsersR
, navAccess' = NavAccessDB $ do
cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
exists [ CourseParticipantCourse ==. cid ]
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
]
pageActions CorrectionsR = return pageActions CorrectionsR = return
[ NavPageActionPrimary [ NavPageActionPrimary
{ navLink = NavLink { navLink = NavLink

View File

@ -40,13 +40,10 @@ deriving instance Generic MaterialR
deriving instance Generic TutorialR deriving instance Generic TutorialR
deriving instance Generic ExamR deriving instance Generic ExamR
deriving instance Generic EExamR deriving instance Generic EExamR
deriving instance Generic CourseApplicationR
deriving instance Generic AllocationR
deriving instance Generic SchoolR deriving instance Generic SchoolR
deriving instance Generic ExamOfficeR deriving instance Generic ExamOfficeR
deriving instance Generic CourseNewsR deriving instance Generic CourseNewsR
deriving instance Generic CourseEventR deriving instance Generic CourseEventR
deriving instance Generic AMatchingR
deriving instance Generic (Route UniWorX) deriving instance Generic (Route UniWorX)
@ -57,19 +54,16 @@ instance Hashable MaterialR
instance Hashable TutorialR instance Hashable TutorialR
instance Hashable ExamR instance Hashable ExamR
instance Hashable EExamR instance Hashable EExamR
instance Hashable CourseApplicationR
instance Hashable AllocationR
instance Hashable SchoolR instance Hashable SchoolR
instance Hashable ExamOfficeR instance Hashable ExamOfficeR
instance Hashable CourseNewsR instance Hashable CourseNewsR
instance Hashable CourseEventR instance Hashable CourseEventR
instance Hashable AMatchingR
instance Hashable (Route UniWorX) instance Hashable (Route UniWorX)
instance Hashable (Route EmbeddedStatic) where instance Hashable (Route EmbeddedStatic) where
hashWithSalt s = hashWithSalt s . renderRoute hashWithSalt s = hashWithSalt s . renderRoute
instance Hashable (Route Auth) where instance Hashable (Route Auth) where
hashWithSalt s = hashWithSalt s . renderRoute hashWithSalt s = hashWithSalt s . renderRoute
instance Ord (Route Auth) where instance Ord (Route Auth) where
compare = compare `on` renderRoute compare = compare `on` renderRoute
instance Ord (Route EmbeddedStatic) where instance Ord (Route EmbeddedStatic) where
@ -82,13 +76,10 @@ deriving instance Ord MaterialR
deriving instance Ord TutorialR deriving instance Ord TutorialR
deriving instance Ord ExamR deriving instance Ord ExamR
deriving instance Ord EExamR deriving instance Ord EExamR
deriving instance Ord CourseApplicationR
deriving instance Ord AllocationR
deriving instance Ord SchoolR deriving instance Ord SchoolR
deriving instance Ord ExamOfficeR deriving instance Ord ExamOfficeR
deriving instance Ord CourseNewsR deriving instance Ord CourseNewsR
deriving instance Ord CourseEventR deriving instance Ord CourseEventR
deriving instance Ord AMatchingR
deriving instance Ord (Route UniWorX) deriving instance Ord (Route UniWorX)
data RouteChildren data RouteChildren
@ -124,10 +115,6 @@ pattern CSubmissionR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Cr
pattern CSubmissionR tid ssh csh shn cid ptn pattern CSubmissionR tid ssh csh shn cid ptn
= CSheetR tid ssh csh shn (SubmissionR cid ptn) = CSheetR tid ssh csh shn (SubmissionR cid ptn)
pattern CApplicationR :: TermId -> SchoolId -> CourseShorthand -> CryptoFileNameCourseApplication -> CourseApplicationR -> Route UniWorX
pattern CApplicationR tid ssh csh appId ptn
= CourseR tid ssh csh (CourseApplicationR appId ptn)
pattern CNewsR :: TermId -> SchoolId -> CourseShorthand -> CryptoUUIDCourseNews -> CourseNewsR -> Route UniWorX pattern CNewsR :: TermId -> SchoolId -> CourseShorthand -> CryptoUUIDCourseNews -> CourseNewsR -> Route UniWorX
pattern CNewsR tid ssh csh nId ptn pattern CNewsR tid ssh csh nId ptn
= CourseR tid ssh csh (CourseNewsR nId ptn) = CourseR tid ssh csh (CourseNewsR nId ptn)

View File

@ -164,12 +164,9 @@ siteLayout' overrideHeading widget = do
(favourites', (title, parents), nav', contentHeadline, mmsgs, maxFavouriteTerms, currentTheme, storedReasonAndToggleRoute) <- do (favourites', (title, parents), nav', contentHeadline, mmsgs, maxFavouriteTerms, currentTheme, storedReasonAndToggleRoute) <- do
(favCourses, breadcrumbs'', nav', contentHeadline, mmsgs, storedReasonAndToggleRoute) <- runDB $ do (favCourses, breadcrumbs'', nav', contentHeadline, mmsgs, storedReasonAndToggleRoute) <- runDB $ do
favCourses'' <- withReaderT (projectBackend @SqlReadBackend) . E.select . E.from $ \(course `E.LeftOuterJoin` allocation `E.LeftOuterJoin` courseFavourite) -> do favCourses'' <- withReaderT (projectBackend @SqlReadBackend) . E.select . E.from $ \(course `E.LeftOuterJoin` courseFavourite) -> do
E.on $ E.just (course E.^. CourseId) E.==. courseFavourite E.?. CourseFavouriteCourse E.on $ E.just (course E.^. CourseId) E.==. courseFavourite E.?. CourseFavouriteCourse
E.&&. courseFavourite E.?. CourseFavouriteUser E.==. E.val (view _1 <$> muid) E.&&. courseFavourite E.?. CourseFavouriteUser E.==. E.val (view _1 <$> muid)
E.on . E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. E.just (allocationCourse E.^. AllocationCourseAllocation) E.==. allocation E.?. AllocationId
let isFavourite = E.not_ . E.isNothing $ courseFavourite E.?. CourseFavouriteId let isFavourite = E.not_ . E.isNothing $ courseFavourite E.?. CourseFavouriteId
isCurrent isCurrent
@ -198,7 +195,7 @@ siteLayout' overrideHeading widget = do
E.&&. tutorial E.^. TutorialCourse E.==. course E.^. CourseId E.&&. tutorial E.^. TutorialCourse E.==. course E.^. CourseId
E.where_ $ E.just (tutor E.^. TutorUser) E.==. E.val (view _1 <$> muid) E.where_ $ E.just (tutor E.^. TutorUser) E.==. E.val (view _1 <$> muid)
isAssociated = isParticipant E.||. isLecturer E.||. isCorrector E.||. isTutor isAssociated = isParticipant E.||. isLecturer E.||. isCorrector E.||. isTutor
courseVisible = courseIsVisible now course $ allocation E.?. AllocationId courseVisible = courseIsVisible now course
reason = E.case_ reason = E.case_
[ E.when_ isCurrent E.then_ . E.just $ E.val FavouriteCurrent [ E.when_ isCurrent E.then_ . E.just $ E.val FavouriteCurrent

View File

@ -291,7 +291,7 @@ decodeUser now UserDefaultConf{..} upsertMode ldapData = do
, userMatrikelnummer = Nothing -- not known from LDAP, must be derived from REST interface to AVS TODO , userMatrikelnummer = Nothing -- not known from LDAP, must be derived from REST interface to AVS TODO
, userPostAddress = Nothing -- not known from LDAP, must be derived from REST interface to AVS TODO , userPostAddress = Nothing -- not known from LDAP, must be derived from REST interface to AVS TODO
, userPinPassword = Nothing -- must be derived via AVS , userPinPassword = Nothing -- must be derived via AVS
, userPrefersPostal = False , userPrefersPostal = userDefaultPrefersPostal
, .. , ..
} }
userUpdate = [ userUpdate = [

View File

@ -1,4 +1,4 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Wolfgang Witt <Wolfgang.Witt@campus.lmu.de> -- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>
-- --
-- SPDX-License-Identifier: AGPL-3.0-or-later -- SPDX-License-Identifier: AGPL-3.0-or-later
@ -210,7 +210,6 @@ routeNormalizers :: forall m backend.
routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .) routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .)
[ normalizeRender [ normalizeRender
, ncSchool , ncSchool
, ncAllocation
, ncCourse , ncCourse
, ncSheet , ncSheet
, ncMaterial , ncMaterial
@ -218,10 +217,8 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
, ncExam , ncExam
, ncExternalExam , ncExternalExam
, verifySubmission , verifySubmission
, verifyCourseApplication
, verifyCourseNews , verifyCourseNews
, verifyMaterialVideo , verifyMaterialVideo
, verifyAllocationMatchingLog
] ]
where where
normalizeRender :: Route UniWorX -> WriterT Any (ReaderT SqlReadBackend (HandlerFor UniWorX)) (Route UniWorX) normalizeRender :: Route UniWorX -> WriterT Any (ReaderT SqlReadBackend (HandlerFor UniWorX)) (Route UniWorX)
@ -253,11 +250,6 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
Entity ssh' _ <- MaybeT . lift . getBy $ UniqueSchoolShorthand schoolShort Entity ssh' _ <- MaybeT . lift . getBy $ UniqueSchoolShorthand schoolShort
(caseChanged `on` unSchoolKey) ssh ssh' (caseChanged `on` unSchoolKey) ssh ssh'
return ssh' return ssh'
ncAllocation = maybeOrig $ \route -> do
AllocationR tid ssh ash _ <- return route
Entity _ Allocation{..} <- MaybeT . $cachedHereBinary (tid, ssh, ash) . lift . getBy $ TermSchoolAllocationShort tid ssh ash
caseChanged ash allocationShorthand
return $ route & typesUsing @RouteChildren @AllocationShorthand . filtered (== ash) .~ allocationShorthand
ncCourse = maybeOrig $ \route -> do ncCourse = maybeOrig $ \route -> do
CourseR tid ssh csh _ <- return route CourseR tid ssh csh _ <- return route
Entity _ Course{..} <- MaybeT . $cachedHereBinary (tid, ssh, csh) . lift . getBy $ TermSchoolCourseShort tid ssh csh Entity _ Course{..} <- MaybeT . $cachedHereBinary (tid, ssh, csh) . lift . getBy $ TermSchoolCourseShort tid ssh csh
@ -304,14 +296,6 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
let newRoute = CSubmissionR courseTerm courseSchool courseShorthand sheetName cID sr let newRoute = CSubmissionR courseTerm courseSchool courseShorthand sheetName cID sr
tell . Any $ route /= newRoute tell . Any $ route /= newRoute
return newRoute return newRoute
verifyCourseApplication = maybeOrig $ \route -> do
CApplicationR _tid _ssh _csh cID sr <- return route
aId <- decrypt cID
CourseApplication{courseApplicationCourse} <- lift . lift $ get404 aId
Course{courseTerm, courseSchool, courseShorthand} <- lift . lift $ get404 courseApplicationCourse
let newRoute = CApplicationR courseTerm courseSchool courseShorthand cID sr
tell . Any $ route /= newRoute
return newRoute
verifyCourseNews = maybeOrig $ \route -> do verifyCourseNews = maybeOrig $ \route -> do
CNewsR _tid _ssh _csh cID sr <- return route CNewsR _tid _ssh _csh cID sr <- return route
aId <- decrypt cID aId <- decrypt cID
@ -329,11 +313,3 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
let newRoute = CMaterialR courseTerm courseSchool courseShorthand materialName $ MVideoR cID let newRoute = CMaterialR courseTerm courseSchool courseShorthand materialName $ MVideoR cID
tell . Any $ route /= newRoute tell . Any $ route /= newRoute
return newRoute return newRoute
verifyAllocationMatchingLog = maybeOrig $ \route -> do
AllocationR _tid _ssh _ash (AMatchingR cID AMLogR) <- return route
amId <- decrypt cID
AllocationMatching{allocationMatchingAllocation} <- lift . lift $ get404 amId
Allocation{allocationTerm, allocationSchool, allocationShorthand} <- lift . lift $ get404 allocationMatchingAllocation
let newRoute = AllocationR allocationTerm allocationSchool allocationShorthand $ AMatchingR cID AMLogR
tell . Any $ route /= newRoute
return newRoute

View File

@ -8,6 +8,22 @@ module Handler.Admin
import Import import Import
-- import Data.Either
import qualified Data.Set as Set
-- import qualified Data.Text.Lazy.Encoding as LBS
-- import qualified Control.Monad.Catch as Catch
-- import Servant.Client (ClientError(..), ResponseF(..))
-- import Text.Blaze.Html (preEscapedToHtml)
import Database.Esqueleto.Experimental ((:&)(..))
import qualified Database.Esqueleto.Experimental as E
import qualified Database.Esqueleto.Utils as E
import Handler.Utils.DateTime
import Handler.Utils.Avs
import Handler.Utils.Widgets
import Handler.Admin.Test as Handler.Admin import Handler.Admin.Test as Handler.Admin
import Handler.Admin.ErrorMessage as Handler.Admin import Handler.Admin.ErrorMessage as Handler.Admin
import Handler.Admin.Tokens as Handler.Admin import Handler.Admin.Tokens as Handler.Admin
@ -15,8 +31,192 @@ import Handler.Admin.Crontab as Handler.Admin
import Handler.Admin.Avs as Handler.Admin import Handler.Admin.Avs as Handler.Admin
import Handler.Admin.Ldap as Handler.Admin import Handler.Admin.Ldap as Handler.Admin
getAdminR :: Handler Html getAdminR :: Handler Html
getAdminR = getAdminR = redirect AdminProblemsR
siteLayoutMsg MsgAdminHeading $ do
setTitleI MsgAdminHeading getAdminProblemsR :: Handler Html
i18n MsgAdminPageEmpty getAdminProblemsR = do
now <- liftIO getCurrentTime
let nowaday = utctDay now
cutOffPrintDays = 7
cutOffPrintJob = addLocalDays (-cutOffPrintDays) now
(usersAreReachable, driversHaveAvsIds, rDriversHaveFs, noStalePrintJobs) <- runDB $ (,,,)
<$> areAllUsersReachable
<*> allDriversHaveAvsId nowaday
<*> allRDriversHaveFs nowaday
<*> (not <$> exists [PrintJobAcknowledged ==. Nothing, PrintJobCreated <=. cutOffPrintJob])
diffLics <- try retrieveDifferingLicences <&> \case
-- (Left (UnsupportedContentType "text/html" resp)) -> Left $ text2widget "Html received"
(Left e) -> Left $ text2widget $ tshow (e :: SomeException)
(Right (to0, to1, to2)) -> Right (Set.size to0, Set.size to1, Set.size to2)
-- Attempt to format results in a nicer way failed, since rendering Html within a modal destroyed the page layout itself
-- let procDiffLics (to0, to1, to2) = Right (Set.size to0, Set.size to1, Set.size to2)
-- diffLics <- (procDiffLics <$> retrieveDifferingLicences) `catches`
-- [ Catch.Handler (\case (UnsupportedContentType "text/html;charset=utf-8" Response{responseBody})
-- -> return $ Left $ toWidget $ preEscapedToHtml $ fromRight "Response UTF8-decoding error" $ LBS.decodeUtf8' responseBody
-- ex -> return $ Left $ text2widget $ tshow ex)
-- , Catch.Handler (\(ex::SomeException) -> return $ Left $ text2widget $ tshow ex)
-- ]
-- we abuse messageTooltip for colored icons here
msgSuccessTooltip <- messageI Success MsgMessageSuccess
msgWarningTooltip <- messageI Warning MsgMessageWarning
msgErrorTooltip <- messageI Error MsgMessageError
let flagError = messageTooltip . bool msgErrorTooltip msgSuccessTooltip
flagWarning = messageTooltip . bool msgWarningTooltip msgSuccessTooltip
flagNonZero :: Int -> Widget
flagNonZero n | n <= 0 = flagError True
| otherwise = messageTooltip =<< handlerToWidget (messageI Error (MsgProblemsDriverSynch n))
siteLayoutMsg MsgProblemsHeading $ do
setTitleI MsgProblemsHeading
$(widgetFile "admin-problems")
getProblemUnreachableR :: Handler Html
getProblemUnreachableR = do
unreachables <- runDB $ E.select retrieveUnreachableUsers
siteLayoutMsg MsgProblemsUnreachableHeading $ do
setTitleI MsgProblemsUnreachableHeading
[whamlet|
<section>
_{MsgProblemsUnreachableBody}
<ul>
$forall usr <- unreachables
<li>
^{linkUserWidget ForProfileR usr}
|]
getProblemFbutNoR :: Handler Html
getProblemFbutNoR = do
now <- liftIO getCurrentTime
let nowaday = utctDay now
rnofs <- runDB $ E.select $ retrieveDriversRWithoutF nowaday
siteLayoutMsg MsgProblemsRWithoutFHeading $ do
setTitleI MsgProblemsRWithoutFHeading
[whamlet|
<section>
_{MsgProblemsRWithoutFBody}
<ul>
$forall usr <- rnofs
<li>
^{linkUserWidget AdminUserR usr}
|]
getProblemWithoutAvsId :: Handler Html
getProblemWithoutAvsId = do
now <- liftIO getCurrentTime
let nowaday = utctDay now
rnofs <- runDB $ E.select $ retrieveDriversWithoutAvsId nowaday
siteLayoutMsg MsgProblemsNoAvsIdHeading $ do
setTitleI MsgProblemsNoAvsIdHeading
[whamlet|
<section>
_{MsgProblemsNoAvsIdBody}
<ul>
$forall usr <- rnofs
<li>
^{linkUserWidget AdminUserR usr}
|]
{-
mkUnreachableUsersTable = do
let dbtSQLQuery user -> do
E.where_ $ E.isNothing (user E.^. UserPostAddress)
E.&&. E.not_ ((user E.^. UserEmail) `E.like` E.val "%@%.%")
pure user
dbtRowKey = (E.^. UserId)
dbtProj = dbtProjFilteredPostId -- TODO: still don't understand the choices here
dbtColonnade =
-}
areAllUsersReachable :: DB Bool
-- areAllUsersReachable = isNothing <$> E.selectOne retrieveUnreachableUsers
areAllUsersReachable = E.selectNotExists retrieveUnreachableUsers
retrieveUnreachableUsers :: E.SqlQuery (E.SqlExpr (Entity User))
retrieveUnreachableUsers = do
user <- E.from $ E.table @User
E.where_ $ E.isNothing (user E.^. UserPostAddress)
E.&&. E.not_ ((user E.^. UserEmail) `E.like` E.val "%@%.%")
return user
allDriversHaveAvsId :: Day -> DB Bool
-- allDriversHaveAvsId = fmap isNothing . E.selectOne . retrieveDriversWithoutAvsId
allDriversHaveAvsId = E.selectNotExists . retrieveDriversWithoutAvsId
qIsValid :: E.SqlExpr (Entity QualificationUser) -> Day -> E.SqlExpr (E.Value Bool)
qIsValid qualUsr nowaday =
E.isNothing (qualUsr E.^. QualificationUserBlockedDue) -- not blocked
E.&&. -- currently valid
(E.val nowaday `E.between`
( qualUsr E.^. QualificationUserFirstHeld
, qualUsr E.^. QualificationUserValidUntil))
{-
-- | Returns users more than once if they own multiple avs-related valid licences, but no AvsID is known
retrieveDriversWithoutAvsId' :: Day -> E.SqlQuery (E.SqlExpr (Entity User))
retrieveDriversWithoutAvsId' nowaday = do
(usr :& qualUsr :& qual) <- E.from $ E.table @User
`E.innerJoin` E.table @QualificationUser
`E.on` (\(usr :& qualUsr) -> usr E.^. UserId E.==. qualUsr E.^. QualificationUserUser)
`E.innerJoin` E.table @Qualification
`E.on` (\(_usr :& qualUsr :& qual) -> qual E.^. QualificationId E.==. qualUsr E.^. QualificationUserQualification)
E.where_ $ -- is avs licence
E.isJust (qual E.^. QualificationAvsLicence)
E.&&. (qualUsr `qIsValid` nowaday)
E.&&. -- AvsId is unknown
E.notExists (do
avsUsr <- E.from $ E.table @UserAvs
E.where_ $ avsUsr E.^. UserAvsUser E.==. usr E.^. UserId
)
return usr
-}
-- | Returns users at most once, even if they own multiple avs-related licences, but no AvsID is known
retrieveDriversWithoutAvsId :: Day -> E.SqlQuery (E.SqlExpr (Entity User))
retrieveDriversWithoutAvsId nowaday = do
usr <- E.from $ E.table @User
E.where_ $
E.exists (do -- a valid avs licence
(qual :& qualUsr) <- E.from (E.table @Qualification
`E.innerJoin` E.table @QualificationUser
`E.on` (\(qual :& qualUsr) -> qual E.^. QualificationId E.==. qualUsr E.^. QualificationUserQualification))
E.where_ $ -- is avs licence
E.isJust (qual E.^. QualificationAvsLicence)
E.&&. (qualUsr `qIsValid` nowaday) -- currently valid
E.&&. -- matches user
(qualUsr E.^. QualificationUserUser E.==. usr E.^. UserId)
)
E.&&.
E.notExists (do -- a known AvsId
avsUsr <- E.from $ E.table @UserAvs
E.where_ $ avsUsr E.^. UserAvsUser E.==. usr E.^. UserId
)
return usr
allRDriversHaveFs :: Day -> DB Bool
-- allRDriversHaveFs = fmap isNothing . E.selectOne . retrieveDriversRWithoutF
allRDriversHaveFs = E.selectNotExists . retrieveDriversRWithoutF
-- | Returns users at most once, even if they own multiple avs-related licences, but no AvsID is known
retrieveDriversRWithoutF :: Day -> E.SqlQuery (E.SqlExpr (Entity User))
retrieveDriversRWithoutF nowaday = do
usr <- E.from $ E.table @User
let hasValidQual lic = do
(qual :& qualUsr) <- E.from (E.table @Qualification
`E.innerJoin` E.table @QualificationUser
`E.on` (\(qual :& qualUsr) -> qual E.^. QualificationId E.==. qualUsr E.^. QualificationUserQualification))
E.where_ $ (qual E.^. QualificationAvsLicence E.==. E.justVal lic) -- matches licence
E.&&. (qualUsr E.^. QualificationUserUser E.==. usr E.^. UserId) -- matches user
E.&&. (qualUsr `qIsValid` nowaday) -- currently valid
E.where_ $ E.exists (hasValidQual AvsLicenceRollfeld)
E.&&. E.notExists (hasValidQual AvsLicenceVorfeld)
return usr
{-
getAdjustLicences :: SchoolId -> QualificationShortand -> Handler Html
-}

View File

@ -2,9 +2,14 @@
-- --
-- SPDX-License-Identifier: AGPL-3.0-or-later -- SPDX-License-Identifier: AGPL-3.0-or-later
{-# OPTIONS_GHC -fno-warn-orphans #-} -- needed for HasEntity instances
{-# OPTIONS -Wno-unused-top-binds #-} -- TODO: remove me, for debugging only
{-# LANGUAGE TypeApplications #-}
module Handler.Admin.Avs module Handler.Admin.Avs
( getAdminAvsR ( getAdminAvsR, postAdminAvsR
, postAdminAvsR , getProblemAvsSynchR, postProblemAvsSynchR
) where ) where
import Import import Import
@ -12,12 +17,26 @@ import qualified Control.Monad.State.Class as State
-- import Data.Aeson (encode) -- import Data.Aeson (encode)
import qualified Data.Text as Text import qualified Data.Text as Text
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Data.Map as Map
import Handler.Utils import Handler.Utils
import Handler.Utils.Avs import Handler.Utils.Avs
import Utils.Avs import Utils.Avs
import Database.Esqueleto.Experimental ((:&)(..))
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Experimental as E hiding (from, on)
import qualified Database.Esqueleto.Experimental as X (from, on) -- needs TypeApplications Lang-Pragma
import qualified Database.Esqueleto.Utils as E
-- import Database.Esqueleto.Utils.TH
-- avoids repetition of local definitions
single :: (k,a) -> Map k a
single = uncurry Map.singleton
-- Button needed only here -- Button needed only here
data ButtonAvsTest = BtnCheckLicences | BtnSynchLicences data ButtonAvsTest = BtnCheckLicences | BtnSynchLicences
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable) deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
@ -29,7 +48,7 @@ nullaryPathPiece ''ButtonAvsTest camelToPathPiece
instance Button UniWorX ButtonAvsTest where instance Button UniWorX ButtonAvsTest where
btnLabel BtnCheckLicences = "Check all licences" -- could be msg btnLabel BtnCheckLicences = "Check all licences" -- could be msg
btnLabel BtnSynchLicences = "Synchronize all licences" -- could be msg btnLabel BtnSynchLicences = "Synchronize all licences" -- could be msg
btnClasses BtnCheckLicences = [BCIsButton, BCPrimary] btnClasses BtnCheckLicences = [BCIsButton, BCPrimary]
btnClasses BtnSynchLicences = [BCIsButton, BCDanger] btnClasses BtnSynchLicences = [BCIsButton, BCDanger]
-- END Button -- END Button
@ -47,9 +66,9 @@ makeAvsPersonForm tmpl = identifyForm FIDAvsQueryPerson . validateForm validateA
<*> aopt textField (fslI MsgAvsVersionNo) (avsPersonQueryVersionNo <$> tmpl) <*> aopt textField (fslI MsgAvsVersionNo) (avsPersonQueryVersionNo <$> tmpl)
<*> aopt textField (fslI MsgAvsFirstName) (avsPersonQueryFirstName <$> tmpl) <*> aopt textField (fslI MsgAvsFirstName) (avsPersonQueryFirstName <$> tmpl)
<*> aopt textField (fslI MsgAvsLastName) (avsPersonQueryLastName <$> tmpl) <*> aopt textField (fslI MsgAvsLastName) (avsPersonQueryLastName <$> tmpl)
<*> aopt avsInternalPersonalNoField <*> aopt avsInternalPersonalNoField
(fslI MsgAvsInternalPersonalNo) (avsPersonQueryInternalPersonalNo <$> tmpl) (fslI MsgAvsInternalPersonalNo) (avsPersonQueryInternalPersonalNo <$> tmpl)
validateAvsQueryPerson :: FormValidator AvsQueryPerson Handler () validateAvsQueryPerson :: FormValidator AvsQueryPerson Handler ()
validateAvsQueryPerson = do validateAvsQueryPerson = do
@ -103,8 +122,12 @@ postAdminAvsR = do
addMessage Info $ text2Html $ "Query: " <> tshow (toJSON fr) addMessage Info $ text2Html $ "Query: " <> tshow (toJSON fr)
res <- avsQueryPerson fr res <- avsQueryPerson fr
case res of case res of
Left err -> return . Just $ tshow err Left err -> let msg = tshow err in return $ Just [whamlet|<h2>Error:</h2> #{msg}|]
Right jsn -> return . Just $ tshow jsn Right (AvsResponsePerson pns) -> return $ Just [whamlet|
<ul>
$forall p <- pns
<li>#{tshow p}
|]
mbPerson <- formResultMaybe presult procFormPerson mbPerson <- formResultMaybe presult procFormPerson
((sresult, swidget), senctype) <- runFormPost $ makeAvsStatusForm Nothing ((sresult, swidget), senctype) <- runFormPost $ makeAvsStatusForm Nothing
@ -112,8 +135,12 @@ postAdminAvsR = do
addMessage Info $ text2Html $ "Query: " <> tshow (toJSON fr) addMessage Info $ text2Html $ "Query: " <> tshow (toJSON fr)
res <- avsQueryStatus fr res <- avsQueryStatus fr
case res of case res of
Left err -> return . Just $ tshow err Left err -> let msg = tshow err in return $ Just [whamlet|<h2>Error:</h2> #{msg}|]
Right jsn -> return . Just $ tshow jsn Right (AvsResponseStatus pns) -> return $ Just [whamlet|
<ul>
$forall p <- pns
<li>#{tshow p}
|]
mbStatus <- formResultMaybe sresult procFormStatus mbStatus <- formResultMaybe sresult procFormStatus
((crUsrRes, crUsrWgt), crUsrEnctype) <- runFormPost $ identifyForm FIDAvsCreateUser $ \html -> ((crUsrRes, crUsrWgt), crUsrEnctype) <- runFormPost $ identifyForm FIDAvsCreateUser $ \html ->
@ -134,17 +161,17 @@ postAdminAvsR = do
((getLicRes, getLicWgt), getLicEnctype) <- runFormPost $ identifyForm FIDAvsQueryLicence $ \html -> ((getLicRes, getLicWgt), getLicEnctype) <- runFormPost $ identifyForm FIDAvsQueryLicence $ \html ->
flip (renderAForm FormStandard) html $ (,,) <$> aopt intField (fslI $ text2message "Min AvsPersonId") Nothing flip (renderAForm FormStandard) html $ (,,) <$> aopt intField (fslI $ text2message "Min AvsPersonId") Nothing
<*> aopt intField (fslI $ text2message "Max AvsPersonId") Nothing <*> aopt intField (fslI $ text2message "Max AvsPersonId") Nothing
<*> aopt (selectField $ return avsLicenceOptions) (fslI MsgAvsLicence) Nothing <*> aopt (selectField $ return avsLicenceOptions) (fslI MsgAvsLicence) Nothing
let procFormGetLic fr = do let procFormGetLic fr = do
res <- avsQueryGetAllLicences res <- avsQueryGetAllLicences
case res of case res of
(Right (AvsResponseGetLicences lics)) -> do (Right (AvsResponseGetLicences lics)) -> do
let flics = Set.toList $ Set.filter lfltr lics let flics = Set.toList $ Set.filter lfltr lics
lfltr = case fr of -- not pretty, but it'll do lfltr = case fr of -- not pretty, but it'll do
(Just idmin, Just idmax, Just lic) -> \AvsPersonLicence{..} -> (avsLicenceRampLicence == lic) && (avsLicencePersonID `inBetween` (AvsPersonId idmin, AvsPersonId idmax)) (Just idmin, Just idmax, Just lic) -> \AvsPersonLicence{..} -> (avsLicenceRampLicence == lic) && (avsLicencePersonID `inBetween` (AvsPersonId idmin, AvsPersonId idmax))
(Just idmin, Nothing, Just lic) -> \AvsPersonLicence{..} -> (avsLicenceRampLicence == lic) && (avsLicencePersonID == AvsPersonId idmin) (Just idmin, Nothing, Just lic) -> \AvsPersonLicence{..} -> (avsLicenceRampLicence == lic) && (avsLicencePersonID == AvsPersonId idmin)
(Nothing , Just idmax, Just lic) -> \AvsPersonLicence{..} -> (avsLicenceRampLicence == lic) && (avsLicencePersonID == AvsPersonId idmax) (Nothing , Just idmax, Just lic) -> \AvsPersonLicence{..} -> (avsLicenceRampLicence == lic) && (avsLicencePersonID == AvsPersonId idmax)
(Nothing , Nothing, Just lic) -> \AvsPersonLicence{..} -> avsLicenceRampLicence == lic (Nothing , Nothing, Just lic) -> \AvsPersonLicence{..} -> avsLicenceRampLicence == lic
(Just idmin, Just idmax, Nothing ) -> (`inBetween` (AvsPersonId idmin, AvsPersonId idmax)) . avsLicencePersonID (Just idmin, Just idmax, Nothing ) -> (`inBetween` (AvsPersonId idmin, AvsPersonId idmax)) . avsLicencePersonID
(Just idmin, Nothing, Nothing ) -> (== AvsPersonId idmin) . avsLicencePersonID (Just idmin, Nothing, Nothing ) -> (== AvsPersonId idmin) . avsLicencePersonID
@ -152,12 +179,12 @@ postAdminAvsR = do
(Nothing , Nothing, Nothing ) -> const True (Nothing , Nothing, Nothing ) -> const True
addMessage Info $ text2Html $ "Query returned " <> tshow (length flics) <> " licences." addMessage Info $ text2Html $ "Query returned " <> tshow (length flics) <> " licences."
return $ Just [whamlet| return $ Just [whamlet|
<h2>Success:</h2> <h2>Success:</h2>
<ul> <ul>
$forall AvsPersonLicence{..} <- flics $forall AvsPersonLicence{..} <- flics
<li> #{tshow avsLicencePersonID}: #{licence2char avsLicenceRampLicence} <li> #{tshow avsLicencePersonID}: #{licence2char avsLicenceRampLicence}
|] |]
(Left err) -> do (Left err) -> do
let msg = tshow err let msg = tshow err
return $ Just [whamlet|<h2>Error:</h2> #{msg}|] return $ Just [whamlet|<h2>Error:</h2> #{msg}|]
@ -181,44 +208,44 @@ postAdminAvsR = do
mbSetLic <- formResultMaybe setLicRes procFormSetLic mbSetLic <- formResultMaybe setLicRes procFormSetLic
((qryLicRes, qryLicWgt), qryLicEnctype) <- runFormPost $ identifyForm FIDAvsQueryLicenceDiffs (buttonForm :: Form ButtonAvsTest) (qryLicForm, qryLicRes) <- runButtonForm FIDAvsQueryLicenceDiffs
let procFormQryLic btn = case btn of mbQryLic <- case qryLicRes of
BtnCheckLicences -> do Nothing -> return Nothing
res <- try $ do (Just BtnCheckLicences) -> do
allLicences <- throwLeftM avsQueryGetAllLicences res <- try $ do
computeDifferingLicences allLicences allLicences <- throwLeftM avsQueryGetAllLicences
case res of computeDifferingLicences allLicences
(Right diffs) -> do case res of
let showLics l = Text.intercalate ", " $ fmap (tshow . avsLicencePersonID) $ Set.toList $ Set.filter ((l ==) . avsLicenceRampLicence) diffs (Right diffs) -> do
r_grant = showLics AvsLicenceRollfeld let showLics l = Text.intercalate ", " $ fmap (tshow . avsLicencePersonID) $ Set.toList $ Set.filter ((l ==) . avsLicenceRampLicence) diffs
f_set = showLics AvsLicenceVorfeld r_grant = showLics AvsLicenceRollfeld
revoke = showLics AvsNoLicence f_set = showLics AvsLicenceVorfeld
return $ Just [whamlet| revoke = showLics AvsNoLicence
<h2>Licence check differences: return $ Just [whamlet|
<h3>Grant R: <h2>Licence check differences:
<p> <h3>Grant R:
#{r_grant} <p>
<h3>Set to F: #{r_grant}
<p> <h3>Set to F:
#{f_set} <p>
<h3>Revoke licence: #{f_set}
<p> <h3>Revoke licence:
#{revoke} <p>
|] #{revoke}
(Left e) -> do |]
let msg = tshow (e :: SomeException) (Left e) -> do
return $ Just [whamlet|<h2>Licence check error:</h2> #{msg}|] let msg = tshow (e :: SomeException)
BtnSynchLicences -> do return $ Just [whamlet|<h2>Licence check error:</h2> #{msg}|]
res <- try checkLicences (Just BtnSynchLicences) -> do
case res of res <- try synchAvsLicences
(Right True) -> case res of
return $ Just [whamlet|<h2>Success:</h2> Licences sychronized.|] (Right True) ->
(Right False) -> return $ Just [whamlet|<h2>Success:</h2> Licences sychronized.|]
return $ Just [whamlet|<h2>Error:</h2> Licences could not be synchronized, see error log.|] (Right False) ->
(Left e) -> do return $ Just [whamlet|<h2>Error:</h2> Licences could not be synchronized, see error log.|]
let msg = tshow (e :: SomeException) (Left e) -> do
return $ Just [whamlet|<h2>Licence synchronisation error:</h2> #{msg}|] let msg = tshow (e :: SomeException)
mbQryLic <- formResultMaybe qryLicRes procFormQryLic return $ Just [whamlet|<h2>Licence synchronisation error:</h2> #{msg}|]
actionUrl <- fromMaybe AdminAvsR <$> getCurrentRoute actionUrl <- fromMaybe AdminAvsR <$> getCurrentRoute
siteLayoutMsg MsgMenuAvs $ do siteLayoutMsg MsgMenuAvs $ do
@ -229,6 +256,205 @@ postAdminAvsR = do
crUsrForm = wrapFormHere crUsrWgt crUsrEnctype crUsrForm = wrapFormHere crUsrWgt crUsrEnctype
getLicForm = wrapFormHere getLicWgt getLicEnctype getLicForm = wrapFormHere getLicWgt getLicEnctype
setLicForm = wrapFormHere setLicWgt setLicEnctype setLicForm = wrapFormHere setLicWgt setLicEnctype
qryLicForm = wrapForm qryLicWgt def { formAction = Just $ SomeRoute actionUrl, formEncoding = qryLicEnctype, formSubmit = FormNoSubmit }
-- TODO: use i18nWidgetFile instead if this is to become permanent -- TODO: use i18nWidgetFile instead if this is to become permanent
$(widgetFile "avs") $(widgetFile "avs")
{-
type SynchTableExpr = ( E.SqlExpr (E.Value AvsPersonId)
`E.LeftOuterJoin` E.SqlExpr (Entity UserAvs)
`E.LeftOuterJoin` ( E.SqlExpr (Entity Qualification)
`E.InnerJoin` E.SqlExpr (Entity QualificationUser)
`E.InnerJoin` E.SqlExpr (Entity User)
))
type SynchDBRow = (E.Value AvsPersonId, E.Value AvsLicence, Entity Qualification, Entity QualificationUser, Entity User)
-}
data ButtonAvsSynch = BtnImportUnknownAvsIds | BtnRevokeAvsLicences
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonAvsSynch
instance Finite ButtonAvsSynch
nullaryPathPiece ''ButtonAvsSynch camelToPathPiece
embedRenderMessage ''UniWorX ''ButtonAvsSynch id
instance Button UniWorX ButtonAvsSynch where
btnClasses BtnImportUnknownAvsIds = [BCIsButton, BCPrimary]
btnClasses BtnRevokeAvsLicences = [BCIsButton, BCDanger]
data LicenceTableAction = LicenseTableChangeAvs
| LicenseTableChangeFDrive
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe LicenceTableAction
instance Finite LicenceTableAction
nullaryPathPiece ''LicenceTableAction $ camelToPathPiece' 2
embedRenderMessage ''UniWorX ''LicenceTableAction id
data LicenceTableActionData = LicenseTableChangeAvsData
| LicenseTableChangeFDriveData
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
postProblemAvsSynchR, getProblemAvsSynchR :: Handler Html
postProblemAvsSynchR = getProblemAvsSynchR
getProblemAvsSynchR = do
-- TODO: just for Testing
-- now <- liftIO getCurrentTime
-- let TimeOfDay hours minutes _seconds = timeToTimeOfDay (utctDayTime now)
-- setTo0 = Set.fromList [AvsPersonId hours, AvsPersonId minutes, AvsPersonId 12345678]
-- setTo1 = Set.fromList [AvsPersonId minutes]
-- setTo2 = Set.fromList [AvsPersonId hours, AvsPersonId 12345678]
(setTo0, setTo1, setTo2) <- try retrieveDifferingLicences >>= \case
Right res -> return res
Left err -> do addMessageModal Error (i18n MsgAvsCommunicationError) (Right (text2widget $ tshow (err :: SomeException)))
redirect AdminR
unknownLicenceOwners' <- whenNonEmpty setTo0 $ \neZeros ->
runDB $ E.select $ do
(toZero :& usrAvs) <- X.from $
E.toValues neZeros `E.leftJoin` E.table @UserAvs
`X.on` (\(toZero :& usrAvs) -> usrAvs E.?. UserAvsPersonId E.==. E.just toZero)
E.where_ $ E.isNothing (usrAvs E.?. UserAvsPersonId)
pure toZero
let unknownLicenceOwners = E.unValue <$> unknownLicenceOwners'
numUnknownLicenceOwners = length unknownLicenceOwners
(btnUnknownWgt, btnUnknownRes) <- runButtonFormHash unknownLicenceOwners FIDAbsUnknownLicences
case btnUnknownRes of
Nothing -> return ()
(Just BtnImportUnknownAvsIds) -> do
let procAid = fmap (Sum . maybe 0 (const 1)) <$> upsertAvsUserById
res <- try (getSum <$> foldMapM procAid unknownLicenceOwners)
case res of
Right oks -> do
let ms = if oks == numUnknownLicenceOwners then Info else Warning
addMessageI ms $ MsgAvsImportIDs oks numUnknownLicenceOwners
redirect ProblemAvsSynchR
Left err -> addMessageModal Error (i18n MsgAvsCommunicationError) (Right (text2widget $ tshow (err :: SomeException)))
(Just BtnRevokeAvsLicences) ->
try (setLicencesAvs $ Set.map (AvsPersonLicence AvsNoLicence) $ Set.fromList unknownLicenceOwners)
>>= \case
Right True -> addMessageI Info MsgRevokeUnknownLicencesOk
Right False -> addMessageI Error MsgRevokeUnknownLicencesFail
Left err -> addMessageModal Error (i18n MsgAvsCommunicationError) (Right (text2widget $ tshow (err :: SomeException)))
>> redirect ProblemAvsSynchR
((_,tb0),(_,tb1),(_,tb2)) <- runDB $ (,,)
<$> mkLicenceTable AvsLicenceVorfeld setTo0
<*> mkLicenceTable AvsLicenceVorfeld setTo1
<*> mkLicenceTable AvsLicenceRollfeld setTo2
siteLayoutMsg MsgAvsTitleLicenceSynch $ do
setTitleI MsgAvsTitleLicenceSynch
$(i18nWidgetFile "avs-synchronisation")
type LicenceTableExpr = ( E.SqlExpr (Entity UserAvs)
`E.InnerJoin` E.SqlExpr (Entity User)
) `E.LeftOuterJoin` (
E.SqlExpr (Maybe (Entity QualificationUser))
`E.InnerJoin` E.SqlExpr (Maybe (Entity Qualification))
)
queryUser :: LicenceTableExpr -> E.SqlExpr (Entity User)
queryUser = $(E.sqlIJproj 2 2) . $(E.sqlLOJproj 2 1)
queryQualUser :: LicenceTableExpr -> E.SqlExpr (Maybe (Entity QualificationUser))
queryQualUser = $(E.sqlIJproj 2 1) . $(E.sqlLOJproj 2 2)
queryQualification :: LicenceTableExpr -> E.SqlExpr (Maybe (Entity Qualification))
queryQualification = $(E.sqlIJproj 2 2) . $(E.sqlLOJproj 2 2)
type LicenceTableData = DBRow (Entity User, Maybe (Entity QualificationUser), AvsPersonId)
resultUser :: Lens' LicenceTableData (Entity User)
resultUser = _dbrOutput . _1
resultQualUser :: Traversal' LicenceTableData (Entity QualificationUser)
resultQualUser = _dbrOutput . _2 . _Just
resultAvsPID :: Traversal' LicenceTableData AvsPersonId
resultAvsPID = _dbrOutput . _3
instance HasEntity LicenceTableData User where
hasEntity = resultUser
instance HasUser LicenceTableData where
hasUser = resultUser . _entityVal
mkLicenceTable :: AvsLicence -> Set AvsPersonId -> DB (FormResult (LicenceTableActionData, Set UserId), Widget)
mkLicenceTable aLic apids = do
currentRoute <- fromMaybe (error "mkLicenceTable called from 404-handler") <$> liftHandler getCurrentRoute
now <- liftIO getCurrentTime
let nowaday = utctDay now
dbtIdent = "drivingLicenceSynch" :: Text
dbtStyle = def
dbtSQLQuery = \((usrAvs `E.InnerJoin` user) `E.LeftOuterJoin` (qualUser `E.InnerJoin` qual)) -> do
E.on $ qual E.?. QualificationId E.==. qualUser E.?. QualificationUserQualification
E.on $ user E.^. UserId E.=?. qualUser E.?. QualificationUserUser
E.on $ user E.^. UserId E.==. usrAvs E.^. UserAvsUser
E.where_ $ E.val aLic E.=?. E.joinV (qual E.?. QualificationAvsLicence)
E.&&. (usrAvs E.^. UserAvsPersonId `E.in_` E.vals apids)
return (user, qualUser, usrAvs E.^. UserAvsPersonId)
dbtRowKey = queryUser >>> (E.^. UserId) -- ((_usrAvs `E.InnerJoin` usr) `E.LeftOuterJoin` _) = usr E.^. UserId
dbtProj = dbtProjSimple $ \(user, qualUsr, E.Value api) -> return (user, qualUsr, api)
dbtColonnade = mconcat
[ dbSelect (applying _2) id (return . view (resultUser . _entityKey))
, colUserNameLink AdminUserR
, sortable (Just "valid-until") (i18nCell MsgLmsQualificationValidUntil) $ \(preview $ resultQualUser . _entityVal . _qualificationUserValidUntil -> d) -> cellMaybe dayCell d
, sortable (Just "last-refresh") (i18nCell MsgTableQualificationLastRefresh)$ \(preview $ resultQualUser . _entityVal . _qualificationUserLastRefresh -> d) -> cellMaybe dayCell d
, sortable (Just "first-held") (i18nCell MsgTableQualificationFirstHeld) $ \(preview $ resultQualUser . _entityVal . _qualificationUserFirstHeld -> d) -> cellMaybe dayCell d
, sortable (Just "blocked-due") (i18nCell MsgTableQualificationBlockedDue & cellTooltip MsgTableQualificationBlockedTooltip
) $ \(preview $ resultQualUser . _entityVal . _qualificationUserBlockedDue -> b) -> cellMaybe qualificationBlockedCell b
]
dbtSorting = mconcat
[ single $ sortUserNameLink queryUser
, single ("valid-until" , SortColumn $ queryQualUser >>> (E.?. QualificationUserValidUntil))
, single ("last-refresh", SortColumn $ queryQualUser >>> (E.?. QualificationUserLastRefresh))
, single ("first-held" , SortColumn $ queryQualUser >>> (E.?. QualificationUserFirstHeld))
, single ("blocked-due" , SortColumn $ queryQualUser >>> (E.?. QualificationUserBlockedDue))
]
dbtFilter = mconcat
[ single $ fltrUserNameEmail queryUser
, single ("validity" , FilterColumn . E.mkExactFilterLast $ views (to queryQualUser) ((E.>=. E.justVal nowaday) . (E.?. QualificationUserValidUntil)))
]
dbtFilterUI mPrev = mconcat
[ fltrUserNameEmailHdrUI MsgLmsUser mPrev
, prismAForm (singletonFilter "validity" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgFilterLmsValid)
]
acts :: Map LicenceTableAction (AForm Handler LicenceTableActionData)
acts = mconcat
[ singletonMap LicenseTableChangeAvs $ pure LicenseTableChangeAvsData
, singletonMap LicenseTableChangeFDrive $ pure LicenseTableChangeFDriveData
]
dbtParams = DBParamsForm
{ dbParamsFormMethod = POST
, dbParamsFormAction = Just $ SomeRoute currentRoute
, dbParamsFormAttrs = []
, dbParamsFormSubmit = FormSubmit
, dbParamsFormAdditional
= renderAForm FormStandard
$ (, mempty) . First . Just
<$> multiActionA acts (fslI MsgTableAction) Nothing
, dbParamsFormEvaluate = liftHandler . runFormPost
, dbParamsFormResult = id
, dbParamsFormIdent = def
}
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
dbtExtraReps = []
validator = def -- & defaultSorting [SortDescBy "column-label"]
postprocess :: FormResult (First LicenceTableActionData, DBFormResult UserId Bool LicenceTableData)
-> FormResult ( LicenceTableActionData, Set UserId)
postprocess inp = do
(First (Just act), usrMap) <- inp
let usrSet = Map.keysSet . Map.filter id $ getDBFormResult (const False) usrMap
return (act, usrSet)
over _1 postprocess <$> dbTable validator DBTable{..}

View File

@ -1,22 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation
( module Handler.Allocation
) where
import Handler.Allocation.Info as Handler.Allocation
import Handler.Allocation.Show as Handler.Allocation
import Handler.Allocation.Application as Handler.Allocation
import Handler.Allocation.Register as Handler.Allocation
import Handler.Allocation.List as Handler.Allocation
import Handler.Allocation.Users as Handler.Allocation
import Handler.Allocation.AddUser as Handler.Allocation
import Handler.Allocation.EditUser as Handler.Allocation
import Handler.Allocation.Prios as Handler.Allocation
import Handler.Allocation.Compute as Handler.Allocation
import Handler.Allocation.Accept as Handler.Allocation
import Handler.Allocation.Edit as Handler.Allocation
import Handler.Allocation.New as Handler.Allocation
import Handler.Allocation.Matchings as Handler.Allocation

View File

@ -1,170 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Accept
( SessionDataAllocationResults(..)
, AllocationAcceptButton(..)
, allocationAcceptForm
, getAAcceptR, postAAcceptR
) where
import Import
import Handler.Utils
import Handler.Utils.Allocation
import Data.Ratio ((%))
import Data.Map ((!?))
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Database.Esqueleto.Legacy as E
import qualified Control.Monad.State.Class as State
import Data.Sequence (Seq((:|>)))
newtype SessionDataAllocationResults = SessionDataAllocationResults
{ getSessionDataAllocationResults :: Map ( TermId
, SchoolId
, AllocationShorthand
)
( UTCTime
, AllocationFingerprint
, Set CourseId
, Set (UserId, CourseId)
, Seq MatchingLogRun
)
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
deriving newtype (ToJSON, FromJSON)
deriving (Monoid, Semigroup) via Dual (Map (TermId, SchoolId, AllocationShorthand) (UTCTime, AllocationFingerprint, Set CourseId, Set (UserId, CourseId), Seq MatchingLogRun))
makeWrapped ''SessionDataAllocationResults
data AllocationAcceptButton
= BtnAllocationAccept
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
nullaryPathPiece ''AllocationAcceptButton $ camelToPathPiece' 2
embedRenderMessage ''UniWorX ''AllocationAcceptButton id
instance Button UniWorX AllocationAcceptButton where
btnClasses BtnAllocationAccept = [BCIsButton, BCPrimary]
allocationAcceptForm :: AllocationId -> DB (Maybe (Form (UTCTime, AllocationFingerprint, Set CourseId, Set (UserId, CourseId), Seq MatchingLogRun)))
allocationAcceptForm aId = runMaybeT $ do
Allocation{..} <- MaybeT $ get aId
SessionDataAllocationResults allocMap <- MaybeT $ lookupSessionJson SessionAllocationResults
allocRes@(allocTime, allocFp, eligibleCourses, allocMatching, _ :|> MatchingLogRun{..}) <- hoistMaybe $ allocMap !? (allocationTerm, allocationSchool, allocationShorthand)
allocationUsers <- fmap (map $ bimap E.unValue E.unValue) . lift . E.select . E.from $ \allocationUser -> do
E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId
E.&&. E.not_ (E.isNothing $ allocationUser E.^. AllocationUserPriority)
let applications = E.subSelectCount . E.from $ \courseApplication ->
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.val (Just aId)
E.&&. courseApplication E.^. CourseApplicationUser E.==. allocationUser E.^. AllocationUserUser
return . (allocationUser E.^. AllocationUserUser, ) $ E.case_
[ E.when_ (E.castNum (allocationUser E.^. AllocationUserTotalCourses) E.>. applications)
E.then_ (applications :: E.SqlExpr (E.Value Int))
]
(E.else_ . E.castNum $ allocationUser E.^. AllocationUserTotalCourses)
let allocationPlacesRequested = sumOf (folded . _2) allocationUsers
userAllocations = ofoldr (\(uid, _cid) -> Map.insertWith (+) uid 1) Map.empty allocMatching
allocationUsers' <- hoistMaybe $
let (res, leftoverAllocs) = foldr (\user@(uid, _) (acc, allocCounts)
-> ( (user, Map.findWithDefault 0 uid allocCounts) : acc
, Map.delete uid allocCounts
))
([] , userAllocations) allocationUsers
in guardOn (null leftoverAllocs) res :: Maybe [((UserId, Int), Integer)]
let unmatchedUsers = olength $ filter ((<= 0) . view _2) allocationUsers'
allocationCourses <- fmap (map $ over _3 E.unValue) . lift . E.select . E.from $ \(allocationCourse `E.InnerJoin` course) -> do
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
let participants = E.subSelectCount . E.from $ \courseParticipant ->
E.where_ $ courseParticipant E.^. CourseParticipantCourse E.==. course E.^. CourseId
E.&&. courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
return (allocationCourse, course, participants)
let allocationCapacity = sumOf (folded . _2 . _entityVal . _courseCapacity . _Just) allocationCourses
allocCourses = setOf (folded . _1 . _entityVal . _allocationCourseCourse) allocationCourses
let courseAllocations = ofoldr (\(_uid, cid) -> Map.insertWith (+) cid 1) Map.empty allocMatching
allocationCourses' <- hoistMaybe $
let (res, leftoverAllocs) = foldr (\course@(_, Entity cid _, _) (acc, allocCounts)
-> ( (course, Map.findWithDefault 0 cid allocCounts) : acc
, Map.delete cid allocCounts
))
([] , courseAllocations) allocationCourses
in guardOn (null leftoverAllocs) res :: Maybe [((Entity AllocationCourse, Entity Course, Int), Int)]
let unmatchedCourses = olength $ filter ((<= 0) . view _2) allocationCourses'
let validateMatches =
guardValidation MsgAllocationAcceptFormDoesNotMatchSession =<< State.get
return . set (mapped . mapped . _1 . mapped) allocRes . validateForm validateMatches . identifyForm FIDAllocationAccept $ \csrf -> do
(prevAllocRes, prevAllocView) <- mreq hiddenField "" $ Just allocFp
let prevAllocMatches = (== allocFp) <$> prevAllocRes
let
showTerms
| [_] <- nubOrdOn (view $ _1 . _2 . _entityVal . _courseTerm) allocationCourses'
= False
| otherwise
= True
showSchools
| [_] <- nubOrdOn (view $ _1 . _2 . _entityVal . _courseSchool) allocationCourses'
= False
| otherwise
= True
optimumAllocated = round . (* optimumProportion) . fromIntegral
where optimumProportion :: Rational
optimumProportion
| allocationCapacity > 0 = fromIntegral allocationPlacesRequested % fromIntegral allocationCapacity
| otherwise = 0
allocHeat capN
= invDualHeat (optimumAllocated capN) capN
degenerateHeat capN
= capN <= optimumAllocated capN
return (prevAllocMatches, $(widgetFile "allocation/accept"))
getAAcceptR, postAAcceptR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAAcceptR = postAAcceptR
postAAcceptR tid ssh ash = do
(((_, acceptView), acceptEnctype), didStore) <- runDB $ do
aId <- getKeyBy404 $ TermSchoolAllocationShort tid ssh ash
acceptForm <- maybe (redirect $ AllocationR tid ssh ash AComputeR) return =<< allocationAcceptForm aId
formRes@((acceptRes, _), _) <- liftHandler $ runFormPost acceptForm
didStore <- formResultMaybe acceptRes $ \(now, allocFp, _, allocMatchings, allocLog) -> do
modifySessionJson SessionAllocationResults . fmap (assertM $ not . views _Wrapped onull) . over (mapped . _Wrapped :: Setter' (Maybe SessionDataAllocationResults) _) $
Map.filterWithKey (\(tid', ssh', ash') (_, allocFp', _, _, _) ->
or [ tid' /= tid
, ssh' /= ssh
, ash' /= ash
, allocFp' /= allocFp
])
storeAllocationResult aId now (allocFp, allocMatchings, allocLog)
return $ Just ()
return (formRes, is _Just didStore)
when didStore $ do
addMessageI Success MsgAllocationAccepted
redirect $ AllocationR tid ssh ash AUsersR
siteLayoutMsg MsgHeadingAllocationAccept $ do
setTitleI MsgHeadingAllocationAccept
wrapForm' BtnAllocationAccept acceptView def
{ formEncoding = acceptEnctype
}

View File

@ -1,89 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.AddUser
( getAAddUserR, postAAddUserR
) where
import Import
import Handler.Allocation.Application
import Handler.Allocation.UserForm
import Handler.Utils
import qualified Data.Map.Strict as Map
import qualified Data.Conduit.Combinators as C
getAAddUserR, postAAddUserR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAAddUserR = postAAddUserR
postAAddUserR tid ssh ash = do
(Entity _ Allocation{..}, (addUserAct, addUserForm, addUserEnctype)) <- runDB $ do
alloc@(Entity aId _) <- getBy404 $ TermSchoolAllocationShort tid ssh ash
((addUserRes, addUserForm), addUserEnctype) <- runFormPost . renderAForm FormStandard $
allocationUserForm aId Nothing
addUserAct <- formResultMaybe addUserRes $ \AllocationUserForm{..} -> Just <$> do
now <- liftIO getCurrentTime
didInsert <- is _Just <$> insertUnique AllocationUser
{ allocationUserAllocation = aId
, allocationUserUser = aauUser
, allocationUserTotalCourses = aauTotalCourses
, allocationUserPriority = aauPriority
}
if
| didInsert -> do
oldApps <- selectList [CourseApplicationUser ==. aauUser, CourseApplicationAllocation ==. Just aId] []
forM_ oldApps $ \(Entity appId CourseApplication{..}) -> do
deleteWhere [ CourseApplicationFileApplication ==. appId ]
delete appId
unless (courseApplicationCourse `Map.member` aauApplications) $
audit $ TransactionCourseApplicationDeleted courseApplicationCourse courseApplicationUser appId
iforM_ aauApplications $ \cId ApplicationForm{..} -> maybeT_ $ do
prio <- hoistMaybe afPriority
let rated = afRatingVeto || is _Just afRatingPoints
appId <- lift $ insert CourseApplication
{ courseApplicationCourse = cId
, courseApplicationUser = aauUser
, courseApplicationText = afText
, courseApplicationRatingVeto = afRatingVeto
, courseApplicationRatingPoints = afRatingPoints
, courseApplicationRatingComment = afRatingComment
, courseApplicationAllocation = Just aId
, courseApplicationAllocationPriority = Just prio
, courseApplicationTime = now
, courseApplicationRatingTime = guardOn rated now
}
lift . runConduit $ transPipe liftHandler (sequence_ afFiles) .| C.mapM_ (insert_ . review _FileReference . (, CourseApplicationFileResidual appId))
lift . audit $ TransactionCourseApplicationEdit cId aauUser appId
return $ do
addMessageI Success MsgAllocationAddUserUserAdded
redirect $ AllocationR tid ssh ash AAddUserR
| otherwise -> return $ addMessageI Error MsgAllocationAddUserUserExists
return (alloc, (addUserAct, addUserForm, addUserEnctype))
sequence_ addUserAct
MsgRenderer mr <- getMsgRenderer
let title = MsgAllocationAddUserTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationName
shortTitle = MsgAllocationAddUserShortTitle allocationTerm allocationSchool allocationShorthand
siteLayoutMsg title $ do
setTitleI shortTitle
wrapForm addUserForm FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ AllocationR tid ssh ash AAddUserR
, formEncoding = addUserEnctype
, formAttrs = []
, formSubmit = FormSubmit
, formAnchor = Nothing :: Maybe Text
}

View File

@ -1,385 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Application
( AllocationApplicationButton(..)
, ApplicationFormView(..)
, ApplicationForm(..)
, ApplicationFormMode(..)
, ApplicationFormException(..)
, applicationForm, editApplicationR
, postAApplyR
) where
import Import hiding (hash)
import Handler.Utils
import qualified Data.Text as Text
import qualified Data.Set as Set
import qualified Database.Esqueleto.Legacy as E
import qualified Data.Conduit.List as C
data AllocationApplicationButton
= BtnAllocationApply
| BtnAllocationApplicationEdit
| BtnAllocationApplicationRetract
| BtnAllocationApplicationRate
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe AllocationApplicationButton
instance Finite AllocationApplicationButton
nullaryPathPiece ''AllocationApplicationButton $ camelToPathPiece' 1
embedRenderMessage ''UniWorX ''AllocationApplicationButton id
makePrisms ''AllocationApplicationButton
instance Button UniWorX AllocationApplicationButton where
btnLabel BtnAllocationApply = [whamlet|#{iconApply True} _{MsgBtnAllocationApply}|]
btnLabel BtnAllocationApplicationRetract = [whamlet|#{iconApply False} _{MsgBtnAllocationApplicationRetract}|]
btnLabel BtnAllocationApplicationEdit = [whamlet|#{iconAllocationApplicationEdit} _{MsgBtnAllocationApplicationEdit}|]
btnLabel BtnAllocationApplicationRate = i18n BtnAllocationApplicationRate
btnClasses BtnAllocationApplicationRetract = [BCIsButton, BCDanger]
btnClasses _ = [BCIsButton, BCPrimary]
data ApplicationFormView = ApplicationFormView
{ afvPriority :: Maybe (FieldView UniWorX)
, afvForm :: [FieldView UniWorX]
, afvButtons :: ([AllocationApplicationButton], Widget)
}
data ApplicationForm = ApplicationForm
{ afPriority :: Maybe Word64
, afText :: Maybe Text
, afFiles :: Maybe FileUploads
, afRatingVeto :: Bool
, afRatingPoints :: Maybe ExamGrade
, afRatingComment :: Maybe Text
, afAction :: AllocationApplicationButton
}
data ApplicationFormMode = ApplicationFormMode
{ afmApplicant :: Bool -- ^ Show priority
, afmApplicantEdit :: Bool -- ^ Allow editing text, files, priority (if shown)
, afmLecturer :: Bool -- ^ Allow editing rating
}
data ApplicationFormException = ApplicationFormNoApplication -- ^ Could not fill forced fields of application form with data from application
deriving (Eq, Ord, Read, Show, Generic, Typeable)
instance Exception ApplicationFormException
applicationForm :: Maybe AllocationId
-> CourseId
-> Maybe UserId
-> ApplicationFormMode -- ^ Which parts of the shared form to display
-> Maybe Html -- ^ If @Just@ also include action buttons for usage as standalone form
-> MForm Handler (FormResult ApplicationForm, ApplicationFormView)
applicationForm maId@(is _Just -> isAlloc) cid muid ApplicationFormMode{..} mcsrf = do
(mApp, coursesNum, Course{..}, maxPrio) <- liftHandler . runDB $ do
mApplication <- fmap join . for muid $ \uid -> listToMaybe <$> selectList [CourseApplicationAllocation ==. maId, CourseApplicationUser ==. uid, CourseApplicationCourse ==. cid] [LimitTo 1]
coursesNum <- fromIntegral . fromMaybe 1 <$> for maId (\aId -> count [AllocationCourseAllocation ==. aId])
course <- getJust cid
(fromMaybe 0 -> maxPrio) <- fmap join . for muid $ \uid -> fmap (E.unValue <=< listToMaybe) . E.select . E.from $ \courseApplication -> do
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. E.val uid
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.val maId
E.&&. E.not_ (E.isNothing $ courseApplication E.^. CourseApplicationAllocationPriority)
return . E.joinV . E.max_ $ courseApplication E.^. CourseApplicationAllocationPriority
return (mApplication, coursesNum, course, maxPrio)
MsgRenderer mr <- getMsgRenderer
let
oldPrio :: Maybe Word64
oldPrio = mApp >>= courseApplicationAllocationPriority . entityVal
coursesNum' = succ maxPrio `max` coursesNum
mkPrioOption :: Word64 -> Option Word64
mkPrioOption i = Option
{ optionDisplay = mr . MsgAllocationCoursePriority . fromIntegral $ coursesNum' - i
, optionInternalValue = i
, optionExternalValue = tshow i
}
prioOptions :: OptionList Word64
prioOptions = OptionList
{ olOptions = sortOn (Down . optionInternalValue) . map mkPrioOption $ [0 .. pred coursesNum']
, olReadExternal = readMay
}
prioField = selectField' (Just $ SomeMessage MsgAllocationCourseNoApplication) $ return prioOptions
(prioRes, prioView) <- case (isAlloc, afmApplicant, afmApplicantEdit, mApp) of
(True , True , True , _)
| is _Nothing mApp || is _Nothing mcsrf
-> over _2 Just <$> mopt prioField (fslI MsgApplicationPriority) (Just oldPrio)
(True , True , True , Just _ )
-> over (_1 . _FormSuccess) Just . over _2 Just <$> mreq prioField (fslI MsgApplicationPriority) oldPrio
(True , True , False, _ )
-> over _2 Just <$> mforcedOpt prioField (fslI MsgApplicationPriority) oldPrio
(True , False, _ , Just _ )
| is _Just oldPrio
-> pure (FormSuccess oldPrio, Nothing)
(True , _ , _ , _ )
-> throwM ApplicationFormNoApplication
(False, _ , _ , _ )
-> pure (FormSuccess Nothing, Nothing)
let textField' = convertField (Text.strip . unTextarea) Textarea textareaField
textFs
| is _Just courseApplicationsInstructions
= fslI MsgCourseApplicationText & setTooltip MsgCourseApplicationFollowInstructions
| otherwise
= fslI MsgCourseApplicationText
(textRes, textView) <- if
| not courseApplicationsText
-> pure (FormSuccess Nothing, Nothing)
| not afmApplicantEdit
-> over _2 Just <$> mforcedOpt textField' textFs (mApp >>= courseApplicationText . entityVal)
| otherwise
-> over _2 Just . over (_1 . _FormSuccess) (assertM $ not . Text.null) <$> mopt textField' textFs (Just $ mApp >>= courseApplicationText . entityVal)
appFilesInfo <- for mApp $ \(Entity appId _) -> liftHandler . runDB $ do
hasFiles <- exists [ CourseApplicationFileApplication ==. appId ]
appCID <- encrypt appId
appFilesLink <- toTextUrl $ CApplicationR courseTerm courseSchool courseShorthand appCID CAFilesR
return (hasFiles, appFilesLink)
let hasFiles = maybe False (view _1) appFilesInfo
filesLinkView <- if
| hasFiles || (isn't _NoUpload courseApplicationsFiles && not afmApplicantEdit)
-> let filesLinkField = Field{..}
where
fieldParse _ _ = return $ Right Nothing
fieldEnctype = mempty
fieldView theId _ attrs _ _
= [whamlet|
$newline never
$case appFilesInfo
$of Just (True, appFilesLink)
<a ##{theId} *{attrs} href=#{appFilesLink}>
_{MsgCourseApplicationFiles}
$of _
<span ##{theId} *{attrs}>
_{MsgCourseApplicationNoFiles}
|]
in Just . snd <$> mforced filesLinkField (fslI MsgCourseApplicationFiles) ()
| otherwise
-> return Nothing
filesWarningView <- if
| hasFiles && isn't _NoUpload courseApplicationsFiles && afmApplicantEdit
-> fmap (Just . snd) . formMessage =<< messageIconI Info IconFileUpload MsgCourseApplicationFilesNeedReupload
| otherwise
-> return Nothing
(filesRes, filesView) <-
let mkFs = bool MsgCourseApplicationFile MsgCourseApplicationArchive
prevAppFiles (Entity aId _) = runDBSource $ selectSource [CourseApplicationFileApplication ==. aId] [Asc CourseApplicationFileTitle] .| C.map (view $ _FileReference . _1)
in if
| not afmApplicantEdit || is _NoUpload courseApplicationsFiles
-> return (FormSuccess Nothing, Nothing)
| otherwise
-> fmap (over _2 $ Just . ($ [])) . aFormToForm $ fileUploadForm False (fslI . mkFs) courseApplicationsFiles (prevAppFiles <$> mApp)
(vetoRes, vetoView) <- if
| afmLecturer
-> over _2 Just <$> mpopt checkBoxField (fslI MsgApplicationVeto & setTooltip MsgApplicationVetoTip) (Just $ Just True == fmap (courseApplicationRatingVeto . entityVal) mApp)
| otherwise
-> return (FormSuccess $ Just True == fmap (courseApplicationRatingVeto . entityVal) mApp, Nothing)
(pointsRes, pointsView) <- if
| afmLecturer
-> over _2 Just <$> mopt examGradeField (fslI MsgApplicationRatingPoints & setTooltip MsgApplicationRatingPointsTip) (fmap Just $ mApp >>= courseApplicationRatingPoints . entityVal)
| otherwise
-> return (FormSuccess $ courseApplicationRatingPoints . entityVal =<< mApp, Nothing)
(commentRes, commentView) <- if
| afmLecturer
-> over _2 Just . over (_1 . _FormSuccess) (assertM $ not . Text.null) <$> mopt textField' (fslI MsgApplicationRatingComment & setTooltip (bool MsgApplicationRatingCommentInvisibleTip MsgApplicationRatingCommentVisibleTip courseApplicationsRatingsVisible)) (fmap Just $ mApp >>= courseApplicationRatingComment . entityVal)
| otherwise
-> return (FormSuccess $ courseApplicationRatingComment . entityVal =<< mApp, Nothing)
let
buttons = catMaybes
[ guardOn (not afmApplicantEdit && is _Just mApp && afmLecturer) BtnAllocationApplicationRate
, guardOn ( afmApplicantEdit && is _Just mApp ) BtnAllocationApplicationEdit
, guardOn ( afmApplicantEdit && is _Nothing mApp ) BtnAllocationApply
, guardOn ( afmApplicantEdit && is _Just mApp ) BtnAllocationApplicationRetract
]
(actionRes, buttonsView) <- case mcsrf of
Just csrf -> buttonForm' buttons csrf
Nothing -> return (pure BtnAllocationApplicationEdit, mempty)
ratingSection <- if
| afmLecturer
, afmApplicantEdit
-> Just . set _fvTooltip (Just . toHtml $ mr MsgApplicationRatingSectionSelfTip) . snd <$> formSection MsgApplicationRatingSection
| afmLecturer
-> Just . snd <$> formSection MsgApplicationRatingSection
| otherwise
-> return Nothing
return ( ApplicationForm
<$> prioRes
<*> textRes
<*> filesRes
<*> vetoRes
<*> pointsRes
<*> commentRes
<*> actionRes
, ApplicationFormView
{ afvPriority = prioView
, afvForm = catMaybes $
[ textView
, filesLinkView
, filesWarningView
] ++ maybe [] (map Just) filesView ++
[ ratingSection
, vetoView
, pointsView
, commentView
]
, afvButtons = (buttons, buttonsView)
}
)
editApplicationR :: Maybe AllocationId
-> UserId
-> CourseId
-> Maybe CourseApplicationId
-> ApplicationFormMode
-> (AllocationApplicationButton -> Bool)
-> SomeRoute UniWorX
-> Handler (ApplicationFormView, Enctype)
editApplicationR maId uid cid mAppId afMode allowAction postAction = do
Course{..} <- runDB $ get404 cid
((appRes, appView), appEnc) <- runFormPost $ applicationForm maId cid (Just uid) afMode . Just
formResult appRes $ \ApplicationForm{..} -> do
if
| BtnAllocationApply <- afAction
, allowAction afAction
, is _Nothing maId || is _Just afPriority
-> runDB . setSerializable $ do
haveOld <- exists [ CourseApplicationCourse ==. cid
, CourseApplicationUser ==. uid
, CourseApplicationAllocation ==. maId
]
when haveOld $
invalidArgsI [MsgCourseApplicationExists]
now <- liftIO getCurrentTime
let rated = afRatingVeto || is _Just afRatingPoints
appId <- insert CourseApplication
{ courseApplicationCourse = cid
, courseApplicationUser = uid
, courseApplicationText = afText
, courseApplicationRatingVeto = afRatingVeto
, courseApplicationRatingPoints = afRatingPoints
, courseApplicationRatingComment = afRatingComment
, courseApplicationAllocation = maId
, courseApplicationAllocationPriority = afPriority
, courseApplicationTime = now
, courseApplicationRatingTime = guardOn rated now
}
runConduit $ transPipe liftHandler (sequence_ afFiles) .| C.mapM_ (insert_ . review _FileReference . (, CourseApplicationFileResidual appId))
audit $ TransactionCourseApplicationEdit cid uid appId
addMessageI Success $ MsgCourseApplicationCreated courseShorthand
| is _BtnAllocationApplicationEdit afAction || is _BtnAllocationApplicationRate afAction
, allowAction afAction
, Just appId <- mAppId
, is _Nothing maId || is _Just afPriority
-> runDB . setSerializable $ do
now <- liftIO getCurrentTime
changes <- if
| afmApplicantEdit afMode
-> let mkFilter CourseApplicationFileResidual{..} = [ CourseApplicationFileApplication ==. courseApplicationFileResidualApplication ]
in view _2 <$> replaceFileReferences mkFilter (CourseApplicationFileResidual appId) (forM_ afFiles id)
| otherwise
-> return Set.empty
oldApp <- get404 appId
let newApp = oldApp
{ courseApplicationText = afText
, courseApplicationRatingVeto = afRatingVeto
, courseApplicationRatingPoints = afRatingPoints
, courseApplicationRatingComment = afRatingComment
, courseApplicationAllocation = maId
, courseApplicationAllocationPriority = afPriority
}
newRating = any (\f -> f oldApp newApp)
[ (/=) `on` courseApplicationRatingVeto
, (/=) `on` courseApplicationRatingPoints
, (/=) `on` courseApplicationRatingComment
]
hasRating = any ($ newApp)
[ courseApplicationRatingVeto
, is _Just . courseApplicationRatingPoints
]
appChanged = any (\f -> f oldApp newApp)
[ (/=) `on` courseApplicationText
, \_ _ -> not $ Set.null changes
]
newApp' = newApp
& bool id (set _courseApplicationRatingTime Nothing) appChanged
& bool id (set _courseApplicationRatingTime $ Just now) (newRating && hasRating)
& bool id (set _courseApplicationTime now) appChanged
replace appId newApp'
audit $ TransactionCourseApplicationEdit cid uid appId
uncurry addMessageI =<< case (afmLecturer afMode, newRating, hasRating, appChanged) of
(_, False, _, True) -> return (Success, MsgCourseApplicationEdited courseShorthand)
(_, False, _, False) -> return (Info, MsgCourseApplicationNotEdited courseShorthand)
(True, True, True, _) -> return (Success, MsgCourseApplicationRated)
(True, True, False, _) -> return (Success, MsgCourseApplicationRatingDeleted)
(False, True, _, _) -> permissionDenied "rating changed without lecturer rights"
| is _BtnAllocationApplicationRetract afAction
, allowAction afAction
, Just appId <- mAppId
-> runDB $ do
delete appId
audit $ TransactionCourseApplicationDeleted cid uid appId
addMessageI Success $ MsgCourseApplicationDeleted courseShorthand
| otherwise
-> invalidArgsI [MsgCourseApplicationInvalidAction]
redirect postAction
return (appView, appEnc)
postAApplyR :: TermId -> SchoolId -> AllocationShorthand -> CryptoUUIDCourse -> Handler Void
postAApplyR tid ssh ash cID = do
uid <- requireAuthId
cid <- decrypt cID
(aId, Course{..}) <- runDB $ do
aId <- getKeyBy404 $ TermSchoolAllocationShort tid ssh ash
course <- get404 cid
return (aId, course)
afmLecturer <- hasWriteAccessTo $ CourseR courseTerm courseSchool courseShorthand CEditR
let afMode = ApplicationFormMode
{ afmApplicant = True
, afmApplicantEdit = True
, afmLecturer
}
void . editApplicationR (Just aId) uid cid Nothing afMode (== BtnAllocationApply) . SomeRoute $ AllocationR tid ssh ash AShowR :#: cID
invalidArgs ["Application form required"]

View File

@ -1,154 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Compute
( getAComputeR
, postAComputeR
) where
import Import
import Handler.Utils
import Handler.Utils.Allocation
import Handler.Allocation.Accept (SessionDataAllocationResults(..))
import qualified Data.Set as Set
import qualified Data.Map as Map
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import qualified Control.Monad.State.Class as State
data AllocationComputeForm = AllocationComputeForm
{ acfMissingPrioritiesOk :: Set UserId
, acfRestrictCourses :: Maybe (Set CourseId)
}
data AllocationComputeButton
= BtnAllocationCompute
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
nullaryPathPiece ''AllocationComputeButton $ camelToPathPiece' 2
embedRenderMessage ''UniWorX ''AllocationComputeButton id
instance Button UniWorX AllocationComputeButton where
btnClasses BtnAllocationCompute = [BCIsButton, BCPrimary]
missingPrioritiesUsers :: AllocationId -> DB (Map UserId User)
missingPrioritiesUsers aId = $cachedHereBinary aId $ do
usersWithoutPrio <- E.select . E.from $ \(user `E.InnerJoin` allocationUser) -> do
E.on $ user E.^. UserId E.==. allocationUser E.^. AllocationUserUser
E.&&. allocationUser E.^. AllocationUserAllocation E.==. E.val aId
-- Ignore users without applications
E.where_ . E.exists . E.from $ \courseApplication -> do
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.just (E.val aId)
E.&&. courseApplication E.^. CourseApplicationUser E.==. user E.^. UserId
E.where_ . E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. courseApplication E.^. CourseApplicationCourse
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
E.where_ . E.isNothing $ allocationUser E.^. AllocationUserPriority
return user
return $ toMapOf (folded .> _entityVal) usersWithoutPrio
missingPriorities :: AllocationId -> AForm DB (Set UserId)
missingPriorities aId = wFormToAForm $ do
usersWithoutPrio <- lift . lift $ missingPrioritiesUsers aId
let missingPriosField = checkBoxField { fieldView = missingPriosFieldView }
where
missingPriosFieldView theId name attrs res isReq
= $(i18nWidgetFile "allocation-confirm-missing-prios")
where checkBoxFieldView = labeledCheckBoxView (i18n MsgAllocationUsersMissingPrioritiesOk) theId name attrs res isReq
if
| null usersWithoutPrio
-> return $ pure Set.empty
| otherwise
-> fmap (bool Set.empty $ Map.keysSet usersWithoutPrio) <$> wpreq missingPriosField (fslI MsgAllocationUsersMissingPriorities & setTooltip MsgAllocationUsersMissingPrioritiesTip) (Just False)
data AllocationCourseRestrictionMode
= AllocationCourseRestrictionDontRestrict
| AllocationCourseRestrictionSubstitutes
| AllocationCourseRestrictionCustom
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
nullaryPathPiece ''AllocationCourseRestrictionMode $ camelToPathPiece' 3
embedRenderMessage ''UniWorX ''AllocationCourseRestrictionMode id
restrictCourses :: (MonadHandler m, HandlerSite m ~ UniWorX) => AllocationId -> AForm m (Maybe (Set CourseId))
restrictCourses aId = hoistAForm liftHandler $ multiActionA restrictOpts (fslI MsgAllocationRestrictCourses & setTooltip MsgAllocationRestrictCoursesTip) (Just AllocationCourseRestrictionDontRestrict)
where
restrictOpts = mapF $ \case
AllocationCourseRestrictionDontRestrict -> pure Nothing
AllocationCourseRestrictionSubstitutes -> wFormToAForm $ do
now <- liftIO getCurrentTime
allocCourses <- fmap (setOf $ folded . _Value) . liftHandler . runDB . E.select . E.from $ \allocationCourse -> do
E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
E.where_ . E.maybe E.false (E.>=. E.val now) $ allocationCourse E.^. AllocationCourseAcceptSubstitutes
return $ allocationCourse E.^. AllocationCourseCourse
return . pure $ Just allocCourses
AllocationCourseRestrictionCustom -> Just <$> selectCourses
selectCourses = courseSelectForm query coursePred miButtonAction' miIdent' fSettings fRequired mPrev
where
query = E.from $ \(course `E.InnerJoin` allocationCourse) -> do
E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
return course
coursePred _ = return True
mPrev = Nothing
fRequired = True
fSettings = fslI MsgAllocationRestrictCoursesSelection & setTooltip MsgAllocationRestrictCoursesSelectionTip
miIdent' :: Text
miIdent' = "course-selection"
miButtonAction' _ = Nothing
allocationComputeForm :: AllocationId -> AForm DB AllocationComputeForm
allocationComputeForm aId = wFormToAForm $ do
onlyComputeMsg <- messageI Info MsgAllocationOnlyCompute
aFormToWForm $ AllocationComputeForm
<$ aformMessage onlyComputeMsg
<*> missingPriorities aId
<*> restrictCourses aId
validateAllocationComputeForm :: AllocationId -> FormValidator AllocationComputeForm DB ()
validateAllocationComputeForm aId = do
usersWithoutPrio <- lift $ missingPrioritiesUsers aId
missingOk <- State.gets acfMissingPrioritiesOk
guardValidation MsgAllocationUsersMissingPrioritiesNotOk $
Map.keysSet usersWithoutPrio `Set.isSubsetOf` missingOk
getAComputeR, postAComputeR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAComputeR = postAComputeR
postAComputeR tid ssh ash = do
(_, ((_computeFormRes, computeFormView), computeFormEnctype)) <- runDB $ do
aEnt@(Entity aId _) <- getBy404 $ TermSchoolAllocationShort tid ssh ash
formRes@((computeFormRes, _), _) <- runFormPost . validateForm (validateAllocationComputeForm aId) . renderAForm FormStandard $ allocationComputeForm aId
formResult computeFormRes $ \AllocationComputeForm{..} -> do
now <- liftIO getCurrentTime
(allocFp, eligibleCourses, allocMatching, allocLog) <- computeAllocation aEnt acfRestrictCourses
tellSessionJson SessionAllocationResults . SessionDataAllocationResults $
Map.singleton (tid, ssh, ash) (now, allocFp, eligibleCourses, allocMatching, allocLog)
addMessageI Success MsgAllocationComputed
redirect $ AllocationR tid ssh ash AUsersR -- Redirect aborts transaction for safety
return (aEnt, formRes)
siteLayoutMsg MsgHeadingAllocationCompute $ do
setTitleI MsgHeadingAllocationCompute
wrapForm' BtnAllocationCompute computeFormView def
{ formEncoding = computeFormEnctype
}

View File

@ -1,84 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Edit
( getAEditR, postAEditR
) where
import Import
import Handler.Utils
import Handler.Allocation.Form
import qualified Data.Set as Set
getAEditR, postAEditR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAEditR = postAEditR
postAEditR tid ssh ash = do
(Allocation{..}, (mAct, (formView, formEnc))) <- runDB $ do
Entity aId alloc@Allocation{..} <- getBy404 $ TermSchoolAllocationShort tid ssh ash
let template = AllocationForm
{ afTerm = allocationTerm
, afSchool = allocationSchool
, afShorthand = allocationShorthand
, afName = allocationName
, afLegacyShorthands = Set.fromList allocationLegacyShorthands
, afDescription = allocationDescription
, afStaffDescription = allocationStaffDescription
, afStaffRegisterFrom = allocationStaffRegisterFrom
, afStaffRegisterTo = allocationStaffRegisterTo
, afRegisterFrom = allocationRegisterFrom
, afRegisterTo = allocationRegisterTo
, afStaffAllocationFrom = allocationStaffAllocationFrom
, afStaffAllocationTo = allocationStaffAllocationTo
, afRegisterByStaffFrom = allocationRegisterByStaffFrom
, afRegisterByStaffTo = allocationRegisterByStaffTo
, afRegisterByCourse = allocationRegisterByCourse
, afOverrideDeregister = allocationOverrideDeregister
}
((formRes, formView), formEnc) <- runFormPost . renderAForm FormStandard . allocationForm $ Just template
mAct <- formResultMaybe formRes $ \AllocationForm{..} -> runMaybeT $ do
didUpdate <- fmap (is _Nothing) . lift $ replaceUnique aId alloc
{ allocationTerm = afTerm
, allocationSchool = afSchool
, allocationShorthand = afShorthand
, allocationName = afName
, allocationLegacyShorthands = Set.toList afLegacyShorthands
, allocationDescription = afDescription
, allocationStaffDescription = afStaffDescription
, allocationStaffRegisterFrom = afStaffRegisterFrom
, allocationStaffRegisterTo = afStaffRegisterTo
, allocationStaffAllocationFrom = afStaffAllocationFrom
, allocationStaffAllocationTo = afStaffAllocationTo
, allocationRegisterFrom = afRegisterFrom
, allocationRegisterTo = afRegisterTo
, allocationRegisterByStaffFrom = afRegisterByStaffFrom
, allocationRegisterByStaffTo = afRegisterByStaffTo
, allocationRegisterByCourse = afRegisterByCourse
, allocationOverrideDeregister = afOverrideDeregister
}
unless didUpdate $ do
addMessageI Error MsgAllocationEditAlreadyExists
mzero
return $ do
addMessageI Success MsgAllocationEditSuccess
redirect $ AllocationR afTerm afSchool afShorthand AShowR
return (alloc, (mAct, (formView, formEnc)))
sequence_ mAct
siteLayoutMsg (MsgHeadingAllocationEdit allocationTerm allocationSchool allocationName) $ do
setTitleI $ MsgTitleAllocationEdit allocationTerm allocationSchool allocationShorthand
wrapForm formView def
{ formAction = Just . SomeRoute $ AllocationR tid ssh ash AEditR
, formEncoding = formEnc
}

View File

@ -1,244 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.EditUser
( getAEditUserR, postAEditUserR
, getADelUserR, postADelUserR
) where
import Import
import Handler.Allocation.Application
import Handler.Allocation.UserForm
import Handler.Utils
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Conduit.Combinators as C
import Handler.Utils.Delete
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import Handler.Course.Register (deregisterParticipant)
import Jobs.Queue
data AllocationCourseParticipantFormDefaultReason = AllocationCourseParticipantFormDefaultReason
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
embedRenderMessage ''UniWorX ''AllocationCourseParticipantFormDefaultReason id
getAEditUserR, postAEditUserR :: TermId -> SchoolId -> AllocationShorthand -> CryptoUUIDUser -> Handler Html
getAEditUserR = postAEditUserR
postAEditUserR tid ssh ash cID = do
(Entity _ Allocation{..}, User{..}, editUserAct, editUserForm, regFormForm, formEnctype) <- runDBJobs $ do
uid <- decrypt cID
user <- get404 uid
alloc@(Entity aId _) <- getBy404 $ TermSchoolAllocationShort tid ssh ash
Entity auId oldAllocationUser@AllocationUser{..} <- getBy404 $ UniqueAllocationUser aId uid
regState <- do
courses <- E.select . E.from $ \((course `E.InnerJoin` allocationCourse) `E.LeftOuterJoin` courseParticipant `E.LeftOuterJoin` allocationDeregister) -> do
E.on $ allocationDeregister E.?. AllocationDeregisterUser E.==. E.justVal uid
E.&&. E.joinV (allocationDeregister E.?. AllocationDeregisterCourse) E.==. E.just (allocationCourse E.^. AllocationCourseCourse)
E.on $ courseParticipant E.?. CourseParticipantUser E.==. E.justVal uid
E.&&. courseParticipant E.?. CourseParticipantCourse E.==. E.just (allocationCourse E.^. AllocationCourseCourse)
E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
return ( course E.^. CourseId
, ( ( course E.^. CourseTerm
, course E.^. CourseSchool
, course E.^. CourseShorthand
)
, course E.^. CourseName
, ( ( E.joinV (courseParticipant E.?. CourseParticipantAllocated) E.==. E.justVal aId
E.||. E.isNothing (courseParticipant E.?. CourseParticipantId)
, courseParticipant E.?. CourseParticipantState
)
, ( E.isJust $ allocationDeregister E.?. AllocationDeregisterId
, E.joinV $ allocationDeregister E.?. AllocationDeregisterReason
)
)
)
)
MsgRenderer mr <- getMsgRenderer
return $
let toRegState (E.Value cId, (ident, E.Value cname, regState'))
= (cId, ((tid', ssh', csh), cname, courseRegState))
where (E.Value tid', E.Value ssh', E.Value csh) = ident
((E.Value isAlloc, E.Value mParState), (E.Value isDeregister, E.Value regReason)) = regState'
courseRegState
| not isAlloc = CourseParticipantFormNotAllocated
| isDeregister = CourseParticipantFormDeregistered
{ cpfDeregisterReason = Just $ fromMaybe defReason regReason
, cpfEverRegistered = True
}
| mParState == Just CourseParticipantActive = CourseParticipantFormRegistered
| otherwise = CourseParticipantFormDeregistered
{ cpfDeregisterReason = Nothing
, cpfEverRegistered = is _Just mParState
}
defReason = [st|<#{mr AllocationCourseParticipantFormDefaultReason}>|]
in Map.fromList $ map toRegState courses
((formRes, (regFormForm, editUserForm)), formEnctype) <- runFormPost $ \csrf
-> let allocForm = renderAForm FormStandard $
allocationUserForm aId $ Just AllocationUserForm
{ aauUser = uid
, aauTotalCourses = allocationUserTotalCourses
, aauPriority = allocationUserPriority
, aauApplications = Map.empty -- form collects existing applications itself
}
in (\(regRes, regForm) (editUserRes, editUserForm) -> ((,) <$> regRes <*> editUserRes, (regForm, editUserForm))) <$> courseParticipantForm regState csrf <*> allocForm mempty
editUserAct <- formResultMaybe formRes $ \(regState', AllocationUserForm{..}) -> Just <$> do
now <- liftIO getCurrentTime
iforM_ (Map.intersectionWith (,) regState' regState) $ \cId (cpf, (_, _, oldCPF)) -> when (cpf /= oldCPF) $ case cpf of
CourseParticipantFormNotAllocated -> return ()
CourseParticipantFormDeregistered mReason _ -> do
hoist liftHandler $ deregisterParticipant uid =<< getJustEntity cId
app <- getYesod
let mReason' = mReason <&> \str -> maybe (Just str) (const Nothing) (listToMaybe $ unRenderMessageLenient @AllocationCourseParticipantFormDefaultReason app str)
deleteWhere [AllocationDeregisterUser ==. uid, AllocationDeregisterCourse ==. Just cId]
for_ mReason' $ \allocationDeregisterReason ->
insert AllocationDeregister
{ allocationDeregisterCourse = Just cId
, allocationDeregisterTime = now
, allocationDeregisterUser = uid
, allocationDeregisterReason
}
CourseParticipantFormRegistered -> do
void $ upsert CourseParticipant
{ courseParticipantCourse = cId
, courseParticipantUser = uid
, courseParticipantAllocated = Just aId
, courseParticipantState = CourseParticipantActive
, courseParticipantRegistration = now
}
[ CourseParticipantRegistration =. now
, CourseParticipantAllocated =. Just aId
, CourseParticipantState =. CourseParticipantActive
]
audit $ TransactionCourseParticipantEdit cId uid
queueDBJob . JobQueueNotification $ NotificationCourseRegistered uid cId
let newAllocationUser = AllocationUser
{ allocationUserAllocation = aId
, allocationUserUser = aauUser
, allocationUserTotalCourses = aauTotalCourses
, allocationUserPriority = aauPriority
}
when (newAllocationUser /= oldAllocationUser) $ do
replace auId newAllocationUser
audit $ TransactionAllocationUserEdited aauUser aId
-- Applications are complicated and it isn't easy to detect if something changed
-- Therefore we just always replace...
oldApps <- selectList [CourseApplicationUser ==. aauUser, CourseApplicationAllocation ==. Just aId] []
forM_ oldApps $ \(Entity appId CourseApplication{..}) -> do
deleteWhere [ CourseApplicationFileApplication ==. appId ]
delete appId
unless (courseApplicationCourse `Map.member` aauApplications) $
audit $ TransactionCourseApplicationDeleted courseApplicationCourse courseApplicationUser appId
iforM_ aauApplications $ \cId ApplicationForm{..} -> maybeT_ $ do
prio <- hoistMaybe afPriority
let rated = afRatingVeto || is _Just afRatingPoints
appId <- lift $ insert CourseApplication
{ courseApplicationCourse = cId
, courseApplicationUser = aauUser
, courseApplicationText = afText
, courseApplicationRatingVeto = afRatingVeto
, courseApplicationRatingPoints = afRatingPoints
, courseApplicationRatingComment = afRatingComment
, courseApplicationAllocation = Just aId
, courseApplicationAllocationPriority = Just prio
, courseApplicationTime = now
, courseApplicationRatingTime = guardOn rated now
}
lift . runConduit $ transPipe liftHandler (sequence_ afFiles) .| C.mapM_ (insert_ . review _FileReference . (, CourseApplicationFileResidual appId))
lift . audit $ TransactionCourseApplicationEdit cId aauUser appId
return $ do
addMessageI Success MsgAllocationEditUserUserEdited
redirect . AllocationR tid ssh ash $ AEditUserR cID
return (alloc, user, editUserAct, editUserForm, regFormForm, formEnctype)
sequence_ editUserAct
MsgRenderer mr <- getMsgRenderer
let title = MsgAllocationEditUserTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationShorthand userDisplayName
shortTitle = MsgAllocationEditUserShortTitle allocationTerm allocationSchool allocationShorthand userDisplayName
siteLayoutMsg title $ do
setTitleI shortTitle
wrapForm $(widgetFile "allocation/edit-user") FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute . AllocationR tid ssh ash $ AEditUserR cID
, formEncoding = formEnctype
, formAttrs = []
, formSubmit = FormSubmit
, formAnchor = Nothing :: Maybe Text
}
getADelUserR, postADelUserR :: TermId -> SchoolId -> AllocationShorthand -> CryptoUUIDUser -> Handler Html
getADelUserR = postADelUserR
postADelUserR tid ssh ash cID = do
uid <- decrypt cID
(aId, auId) <- runDB . maybeT notFound $ do
aId <- MaybeT . getKeyBy $ TermSchoolAllocationShort tid ssh ash
auId <- MaybeT . getKeyBy $ UniqueAllocationUser aId uid
return (aId, auId)
deleteR DeleteRoute
{ drRecords = Set.singleton auId
, drGetInfo = \(allocationUser `E.InnerJoin` user) -> do
E.on $ allocationUser E.^. AllocationUserUser E.==. user E.^. UserId
let appsCount = E.subSelectCount . E.from $ \courseApplication ->
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. allocationUser E.^. AllocationUserUser
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationUser E.^. AllocationUserAllocation)
allocsCount = E.subSelectCount . E.from $ \courseParticipant ->
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. allocationUser E.^. AllocationUserUser
E.&&. courseParticipant E.^. CourseParticipantAllocated E.==. E.just (allocationUser E.^. AllocationUserAllocation)
return ( ( user E.^. UserDisplayName, user E.^. UserSurname )
, appsCount :: E.SqlExpr (E.Value Word64)
, allocsCount :: E.SqlExpr (E.Value Word64)
)
, drUnjoin = \(allocationUser `E.InnerJoin` _user) -> allocationUser
, drRenderRecord = \((E.Value dName, E.Value sName), E.Value (assertM' (> 0) -> appsCount), E.Value (assertM' (> 0) -> allocsCount)) -> return
[whamlet|
$newline never
^{nameWidget dName sName}
$if is _Just appsCount || is _Just allocsCount
\ (
$maybe c <- appsCount
_{MsgAllocationApplicationsCount c}
$if is _Just appsCount || is _Just allocsCount
, #
$maybe c <- appsCount
_{MsgAllocationAllocationsCount c}
)
|]
, drRecordConfirmString = \((E.Value dName, _), _, _) -> return [st|#{dName}|]
, drFormMessage = \_ -> return Nothing
, drCaption = SomeMessage MsgAllocationUserDeleteQuestion
, drSuccessMessage = SomeMessage MsgAllocationUserDeleted
, drAbort = SomeRoute . AllocationR tid ssh ash $ AEditUserR cID
, drSuccess = SomeRoute $ AllocationR tid ssh ash AUsersR
, drDelete = \_k doDelete -> do
res <- doDelete
audit $ TransactionAllocationUserDeleted uid aId
return res
}

View File

@ -1,186 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Form
( AllocationForm(..)
, allocationForm
) where
import Import
import Handler.Utils
import qualified Database.Esqueleto.Legacy as E
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Control.Monad.State.Class as State
import qualified Data.CaseInsensitive as CI
import qualified Data.Text as Text
data AllocationForm = AllocationForm
{ afTerm :: TermId
, afSchool :: SchoolId
, afShorthand :: AllocationShorthand
, afName :: AllocationName
, afLegacyShorthands :: Set AllocationShorthand
, afDescription, afStaffDescription :: Maybe StoredMarkup
, afStaffRegisterFrom, afStaffRegisterTo
, afRegisterFrom, afRegisterTo
, afStaffAllocationFrom, afStaffAllocationTo
, afRegisterByStaffFrom, afRegisterByStaffTo
, afRegisterByCourse, afOverrideDeregister :: Maybe UTCTime
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
allocationForm :: Maybe AllocationForm
-> AForm (YesodDB UniWorX) AllocationForm
allocationForm mTemplate = validateAForm validateAllocationForm . wFormToAForm $ do
mayEditTerms <- lift . lift $ hasWriteAccessTo TermEditR
now <- liftIO getCurrentTime
muid <- maybeAuthId
termOptions <-
let termQuery :: E.SqlQuery (E.SqlExpr (E.Value TermId))
termQuery = E.from $ \t -> do
unless mayEditTerms $
E.where_ $ E.just (t E.^. TermId) E.==. E.val (afTerm <$> mTemplate)
E.||. termIsActiveE (E.val now) (E.val muid) (t E.^. TermId)
E.orderBy [E.desc $ t E.^. TermStart]
return $ t E.^. TermId
in lift . lift $ mkOptionsE
termQuery
(return . toPathPiece . E.unValue)
(return . ShortTermIdentifier . unTermKey . E.unValue)
(return . E.unValue)
schoolOptions <-
let schoolQuery :: E.SqlQuery (E.SqlExpr (Entity School))
schoolQuery = E.from $ \s -> do
E.where_ $ E.exists (E.from $ \userFunction -> do
E.where_ $ userFunction E.^. UserFunctionSchool E.==. s E.^. SchoolId
E.&&. E.just (userFunction E.^. UserFunctionUser) E.==. E.val muid
E.&&. userFunction E.^. UserFunctionFunction `E.in_` E.valList [SchoolAdmin, SchoolAllocation]
)
E.||. E.just (s E.^. SchoolId) E.==. E.val (afSchool <$> mTemplate)
E.orderBy [E.asc $ s E.^. SchoolShorthand]
return s
in lift . lift $ mkOptionsE
schoolQuery
(return . toPathPiece . entityKey)
(return . schoolName . entityVal)
(return . entityKey)
template <- maybe (lift . lift $ suggestAllocationForm termOptions schoolOptions) (return . Just) mTemplate
let cfCommaSeparatedSet :: forall m. Functor m => Field m Text -> Field m (Set (CI Text))
cfCommaSeparatedSet = guardField (not . Set.null) . convertField (Set.fromList . mapMaybe (fmap CI.mk . assertM' (not . Text.null) . Text.strip) . Text.splitOn ",") (Text.intercalate ", " . map CI.original . Set.toList)
aFormToWForm . hoistAForm liftHandler $ AllocationForm
<$> areq (selectField $ return termOptions) (fslI MsgAllocationFormTerm) (afTerm <$> template)
<*> areq (selectField $ return schoolOptions) (fslI MsgAllocationFormSchool) (afSchool <$> template)
<*> areq (textField & cfStrip & cfCI) (fslI MsgAllocationFormShorthand) (afShorthand <$> template)
<*> areq (textField & cfStrip & cfCI) (fslI MsgAllocationFormName) (afName <$> template)
<*> (fromMaybe Set.empty <$> aopt (textField & cfCommaSeparatedSet) (fslI MsgAllocationFormLegacyShorthands & setTooltip MsgAllocationFormLegacyShorthandsTip) (fmap Just $ afLegacyShorthands <$> template))
<* aformSection MsgAllocationFormDescriptions
<*> aopt htmlField (fslI MsgAllocationFormDescription & setTooltip MsgAllocationFormDescriptionTip) (afDescription <$> template)
<*> aopt htmlField (fslI MsgAllocationFormStaffDescription & setTooltip MsgAllocationFormStaffDescriptionTip) (afStaffDescription <$> template)
<* aformSection MsgAllocationFormDeadlines
<*> aopt utcTimeField (fslI MsgAllocationFormStaffRegisterFrom & setTooltip MsgAllocationFormStaffRegisterFromTip) (afStaffRegisterFrom <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormStaffRegisterTo & setTooltip MsgAllocationFormStaffRegisterToTip) (afStaffRegisterTo <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormRegisterFrom & setTooltip MsgAllocationFormRegisterFromTip) (afRegisterFrom <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormRegisterTo & setTooltip MsgAllocationFormRegisterToTip) (afRegisterTo <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormStaffAllocationFrom & setTooltip MsgAllocationFormStaffAllocationFromTip) (afStaffAllocationFrom <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormStaffAllocationTo & setTooltip MsgAllocationFormStaffAllocationToTip) (afStaffAllocationTo <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormRegisterByStaffFrom & setTooltip MsgAllocationFormRegisterByStaffFromTip) (afRegisterByStaffFrom <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormRegisterByStaffTo & setTooltip MsgAllocationFormRegisterByStaffToTip) (afRegisterByStaffTo <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormRegisterByCourse & setTooltip MsgAllocationFormRegisterByCourseTip) (afRegisterByCourse <$> template)
<*> aopt utcTimeField (fslI MsgAllocationFormOverrideDeregister & setTooltip MsgAllocationFormOverrideDeregisterTip) (afOverrideDeregister <$> template)
validateAllocationForm :: FormValidator AllocationForm (YesodDB UniWorX) ()
validateAllocationForm = do
State.modify $ \af -> af { afLegacyShorthands = Set.delete (afShorthand af) $ afLegacyShorthands af }
AllocationForm{..} <- State.get
guardValidation MsgAllocationFormStaffRegisterToMustBeAfterFrom
$ NTop afStaffRegisterFrom <= NTop afStaffRegisterTo
guardValidation MsgAllocationFormStaffAllocationToMustBeAfterFrom
$ NTop afStaffAllocationFrom <= NTop afStaffAllocationTo
guardValidation MsgAllocationFormRegisterToMustBeAfterFrom
$ NTop afRegisterFrom <= NTop afRegisterTo
guardValidation MsgAllocationFormRegisterByStaffToMustBeAfterFrom
$ NTop afRegisterByStaffFrom <= NTop afRegisterByStaffTo
guardValidation MsgAllocationFormStaffRegisterFromMustBeBeforeStaffAllocationFrom
$ NTop afStaffRegisterFrom <= NTop afStaffAllocationFrom
guardValidation MsgAllocationFormStaffRegisterToMustBeBeforeStaffAllocationTo
$ NTop afStaffRegisterTo <= NTop afStaffAllocationTo
guardValidation MsgAllocationFormStaffRegisterFromMustBeBeforeRegisterFrom
$ NTop afStaffRegisterFrom <= NTop afRegisterFrom
guardValidation MsgAllocationFormStaffRegisterToMustBeBeforeRegisterTo
$ NTop afStaffRegisterTo <= NTop afRegisterTo
warnValidation MsgAllocationFormStaffAllocationToShouldBeBeforeRegisterByStaffFrom
$ NTop afStaffAllocationTo <= NTop afRegisterByStaffFrom
warnValidation MsgAllocationFormStaffAllocationToShouldBeBeforeRegisterByCourse
$ NTop afStaffAllocationTo <= NTop afRegisterByCourse
warnValidation MsgAllocationFormStaffAllocationToShouldBeAfterRegisterTo
$ NTop afStaffAllocationTo >= NTop afRegisterTo
warnValidation MsgAllocationFormRegisterToShouldBeBeforeRegisterByStaffFrom
$ NTop afRegisterTo <= NTop afRegisterByStaffFrom
warnValidation MsgAllocationFormRegisterToShouldBeBeforeRegisterByCourse
$ NTop afRegisterTo <= NTop afRegisterByCourse
warnValidation MsgAllocationFormRegisterByStaffFromShouldBeBeforeRegisterByCourse
$ NTop afRegisterByStaffFrom <= NTop afRegisterByCourse
suggestAllocationForm :: OptionList TermId -> OptionList SchoolId
-> DB (Maybe AllocationForm)
suggestAllocationForm (Set.fromList . map optionInternalValue . olOptions -> terms) (Set.fromList . map optionInternalValue . olOptions -> schools) = runMaybeT $ do
allocs <- lift . E.select . E.from $ \alloc -> do
E.where_ $ alloc E.^. AllocationSchool `E.in_` E.valList (Set.toList schools)
return ( alloc E.^. AllocationTerm
, alloc E.^. AllocationSchool
, alloc E.^. AllocationShorthand
)
let allocTerms = Map.fromListWith (<>) $ do
(E.Value tid, E.Value ssh, E.Value ash) <- allocs
return ((ssh, ash), Set.singleton tid)
nextAlloc = flip Map.mapMaybe allocTerms $ \(Set.toDescList -> tids) -> case tids of
TermKey t1 : TermKey t2 : _ -> Just . TermKey . toEnum $ fromEnum t1 + (fromEnum t1 - fromEnum t2)
TermKey t1 : _ -> Just . TermKey $ succ t1
_other -> Nothing
maxAllocTerm <- hoistMaybe . fmap maximum . fromNullable $ Map.mapMaybe Set.lookupMax allocTerms
((ssh, ash), tid) <- hoistMaybe . fmap (minimumBy $ comparing (view _2) <> comparing (view _1)) . fromNullable . Map.toList $ Map.filter (\t -> t >= maxAllocTerm && t `Set.member` terms) nextAlloc
oldTid <- hoistMaybe $ Set.lookupMax =<< Map.lookup (ssh, ash) allocTerms
oldTerm <- MaybeT $ get oldTid
newTerm <- MaybeT $ get tid
Entity _ Allocation{..} <- MaybeT . getBy $ TermSchoolAllocationShort oldTid ssh ash
let addTime = addLocalDays $ (diffDays `on` termLectureStart) newTerm oldTerm
return AllocationForm
{ afTerm = tid
, afSchool = ssh
, afShorthand = ash
, afName = allocationName
, afLegacyShorthands = Set.delete ash $ Set.fromList allocationLegacyShorthands
, afDescription = allocationDescription
, afStaffDescription = allocationStaffDescription
, afStaffRegisterFrom = addTime <$> allocationStaffRegisterFrom
, afStaffRegisterTo = addTime <$> allocationStaffRegisterTo
, afStaffAllocationFrom = addTime <$> allocationStaffAllocationFrom
, afStaffAllocationTo = addTime <$> allocationStaffAllocationTo
, afRegisterFrom = addTime <$> allocationRegisterFrom
, afRegisterTo = addTime <$> allocationRegisterTo
, afRegisterByStaffFrom = addTime <$> allocationRegisterByStaffFrom
, afRegisterByStaffTo = addTime <$> allocationRegisterByStaffTo
, afRegisterByCourse = addTime <$> allocationRegisterByCourse
, afOverrideDeregister = addTime <$> allocationOverrideDeregister
}

View File

@ -1,20 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Info
( getInfoAllocationR
) where
import Import
import Handler.Utils
import Handler.Info (FAQItem(..))
getInfoAllocationR :: Handler Html
getInfoAllocationR =
siteLayoutMsg MsgHeadingAllocationInfo $ do
setTitleI MsgHeadingAllocationInfo
faqItemUrlAllocationNoPlaces <- toTextUrl $ FaqR :#: FAQAllocationNoPlaces
$(i18nWidgetFile "allocation-info")

View File

@ -1,149 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
module Handler.Allocation.List
( getAllocationListR
) where
import Import
import Utils.Course (mayViewCourse)
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import Handler.Utils.Table.Columns
import Handler.Utils.Table.Pagination
type AllocationTableExpr = E.SqlExpr (Entity Allocation)
type AllocationTableData = DBRow (Entity Allocation, Natural, Natural)
allocationListIdent :: Text
allocationListIdent = "allocations"
queryAllocation :: Getter AllocationTableExpr (E.SqlExpr (Entity Allocation))
queryAllocation = id
countCourses :: (Num n, PersistField n)
=> Maybe UserId -> AuthTagActive -> UTCTime
-> (E.SqlExpr (Entity AllocationCourse) -> E.SqlExpr (E.Value Bool))
-> E.SqlExpr (Entity Allocation)
-> E.SqlExpr (E.Value n)
countCourses muid ata now addWhere allocation = E.subSelectCount . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. allocation E.^. AllocationId
E.&&. E.exists (E.from $ \course -> E.where_ $
course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse
E.&&. mayViewCourse muid ata now course (E.just $ allocation E.^. AllocationId)
) E.&&. addWhere allocationCourse
queryAvailable :: Maybe UserId -> AuthTagActive -> UTCTime
-> Getter AllocationTableExpr (E.SqlExpr (E.Value Word64))
queryAvailable muid ata now = queryAllocation . to (countCourses muid ata now $ const E.true)
queryApplied :: AuthTagActive -> UTCTime -> UserId -> Getter AllocationTableExpr (E.SqlExpr (E.Value Word64))
queryApplied ata now uid = queryAllocation . to (\allocation -> countCourses (Just uid) ata now (addWhere allocation) allocation)
where
addWhere allocation allocationCourse
= E.exists . E.from $ \courseApplication ->
E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. allocationCourse E.^. AllocationCourseCourse
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocation E.^. AllocationId)
E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val uid
resultAllocation :: Lens' AllocationTableData (Entity Allocation)
resultAllocation = _dbrOutput . _1
resultAvailable, resultApplied :: Lens' AllocationTableData Natural
resultAvailable = _dbrOutput . _2
resultApplied = _dbrOutput . _3
allocationTermLink :: TermId -> SomeRoute UniWorX
allocationTermLink tid = SomeRoute (AllocationListR, [(dbFilterKey allocationListIdent "term", toPathPiece tid)])
allocationSchoolLink :: SchoolId -> SomeRoute UniWorX
allocationSchoolLink ssh = SomeRoute (AllocationListR, [(dbFilterKey allocationListIdent "school", toPathPiece ssh)])
allocationLink :: Allocation -> SomeRoute UniWorX
allocationLink Allocation{..} = SomeRoute $ AllocationR allocationTerm allocationSchool allocationShorthand AShowR
getAllocationListR :: Handler Html
getAllocationListR = do
muid <- maybeAuthId
ata <- getSessionActiveAuthTags
now <- liftIO getCurrentTime
let
dbtSQLQuery :: AllocationTableExpr -> E.SqlQuery _
dbtSQLQuery = runReaderT $ (,,)
<$> view queryAllocation
<*> view (queryAvailable muid ata now)
<*> view (maybe (to . const $ E.val 0) (queryApplied ata now) muid)
dbtProj :: _ AllocationTableData
dbtProj = dbtProjId
<&> _dbrOutput . _2 %~ fromIntegral . E.unValue
<&> _dbrOutput . _3 %~ fromIntegral . E.unValue
dbtRowKey = view $ queryAllocation . to (E.^. AllocationId)
dbtColonnade :: Colonnade Sortable _ _
dbtColonnade = mconcat
[ anchorColonnade (views (resultAllocation . _entityVal . _allocationTerm) allocationTermLink) $ colTermShort (resultAllocation . _entityVal . _allocationTerm)
, anchorColonnade (views (resultAllocation . _entityVal . _allocationSchool) allocationSchoolLink) $ colSchoolShort (resultAllocation . _entityVal . _allocationSchool)
, anchorColonnade (views (resultAllocation . _entityVal) allocationLink) $ colAllocationName (resultAllocation . _entityVal . _allocationName)
, sortable (Just "available") (i18nCell MsgAllocationAvailableCourses) $ views resultAvailable i18nCell
, if
| Just _ <- muid
-> sortable (Just "applied") (i18nCell MsgAllocationAppliedCourses) . views resultApplied $ maybe mempty i18nCell . assertM' (> 0)
| otherwise
-> mempty
]
dbtSorting = mconcat
[ sortTerm $ queryAllocation . to (E.^. AllocationTerm)
, sortSchoolShort $ queryAllocation . to (E.^. AllocationSchool)
, sortAllocationName $ queryAllocation . to (E.^. AllocationName)
, singletonMap "available" . SortColumn $ view (queryAvailable muid ata now)
, if
| Just uid <- muid
-> singletonMap "applied" . SortColumn . view $ queryApplied ata now uid
| otherwise
-> mempty
]
dbtFilter = mconcat
[ fltrAllocationActive now queryAllocation
, fltrTerm $ queryAllocation . to (E.^. AllocationTerm)
, fltrSchool $ queryAllocation . to (E.^. AllocationSchool)
, fltrAllocation queryAllocation
]
dbtFilterUI = mconcat
[ fltrAllocationActiveUI
, fltrTermUI
, fltrSchoolUI
, fltrAllocationUI
]
dbtStyle = def
{ dbsFilterLayout = defaultDBSFilterLayout
}
dbtParams = def
dbtCsvEncode = noCsvEncode
dbtCsvDecode = Nothing
dbtExtraReps = []
dbtIdent = allocationListIdent
psValidator :: PSValidator _ _
psValidator = def
& defaultSorting [SortDescBy "term", SortAscBy "school-short", SortAscBy "allocation"]
table <- runDB $ dbTableWidget' psValidator DBTable{..}
siteLayoutMsg MsgAllocationListTitle $ do
setTitleI MsgAllocationListTitle
table

View File

@ -1,43 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Matchings
( getAMatchingListR
, getAMLogR
) where
import Import
import Handler.Utils
import Data.ByteString.Base32
import qualified Data.ByteArray as BA
import qualified Data.CaseInsensitive as CI
getAMatchingListR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAMatchingListR tid ssh ash = do
(Allocation{..}, matchings) <- runDB $ do
Entity aId alloc <- getBy404 $ TermSchoolAllocationShort tid ssh ash
matchings <- selectList [ AllocationMatchingAllocation ==. aId ] [ Desc AllocationMatchingTime ]
matchings' <- forM matchings $ \(Entity matchingId m) -> (, m) <$> encrypt matchingId
return (alloc, matchings')
siteLayoutMsg (MsgHeadingAllocationMatchings allocationTerm allocationSchool allocationName) $ do
setTitleI $ MsgTitleAllocationMatchings allocationTerm allocationSchool allocationShorthand
$(widgetFile "allocation/matchings")
where
showFingerprint = CI.foldCase . encodeBase32Unpadded . BA.convert
getAMLogR :: TermId -> SchoolId -> AllocationShorthand -> CryptoUUIDAllocationMatching -> Handler TypedContent
getAMLogR tid ssh ash cID = serveOneFile $ do
matchingId <- decrypt @AllocationMatchingId cID
AllocationMatching{..} <- lift $ get404 matchingId
mr <- getMessageRender
let fileReferenceTitle = unpack . mr $ MsgAllocationMatchingLogFileName tid ssh ash cID
yield FileReference
{ fileReferenceTitle
, fileReferenceContent = Just allocationMatchingLog
, fileReferenceModified = allocationMatchingTime
}

View File

@ -1,67 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.New
( getAllocationNewR, postAllocationNewR
) where
import Import
import Handler.Utils
import Handler.Allocation.Form
import qualified Crypto.Random as Crypto
import qualified Data.Set as Set
getAllocationNewR, postAllocationNewR :: Handler Html
getAllocationNewR = postAllocationNewR
postAllocationNewR = do
(mAct, (formView, formEnc)) <- runDB $ do
((formRes, formView), formEnc) <- runFormPost . renderAForm FormStandard $ allocationForm Nothing
mAct <- formResultMaybe formRes $ \AllocationForm{..} -> runMaybeT $ do
allocationMatchingSeed <- liftIO $ Crypto.getRandomBytes 32
insertRes <- lift $ insertUnique Allocation
{ allocationTerm = afTerm
, allocationSchool = afSchool
, allocationShorthand = afShorthand
, allocationName = afName
, allocationLegacyShorthands = Set.toList afLegacyShorthands
, allocationDescription = afDescription
, allocationStaffDescription = afStaffDescription
, allocationStaffRegisterFrom = afStaffRegisterFrom
, allocationStaffRegisterTo = afStaffRegisterTo
, allocationStaffAllocationFrom = afStaffAllocationFrom
, allocationStaffAllocationTo = afStaffAllocationTo
, allocationRegisterFrom = afRegisterFrom
, allocationRegisterTo = afRegisterTo
, allocationRegisterByStaffFrom = afRegisterByStaffFrom
, allocationRegisterByStaffTo = afRegisterByStaffTo
, allocationRegisterByCourse = afRegisterByCourse
, allocationOverrideDeregister = afOverrideDeregister
, allocationMatchingSeed
}
unless (is _Just insertRes) $ do
addMessageI Error MsgAllocationNewAlreadyExists
mzero
return $ do
addMessageI Success MsgAllocationNewSuccess
redirect $ AllocationR afTerm afSchool afShorthand AShowR
return (mAct, (formView, formEnc))
sequence_ mAct
siteLayoutMsg MsgTitleAllocationNew $ do
setTitleI MsgTitleAllocationNew
wrapForm formView def
{ formAction = Just $ SomeRoute AllocationNewR
, formEncoding = formEnc
}

View File

@ -1,100 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Prios
( getAPriosR, postAPriosR
) where
import Import
import Handler.Utils
import Handler.Utils.Csv
import Handler.Utils.Allocation
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import qualified Data.Conduit.List as C
import qualified Data.Csv as Csv
data AllocationPrioritiesMode
= AllocationPrioritiesNumeric
| AllocationPrioritiesOrdinal
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe AllocationPrioritiesMode
instance Finite AllocationPrioritiesMode
nullaryPathPiece ''AllocationPrioritiesMode $ camelToPathPiece' 2
embedRenderMessage ''UniWorX ''AllocationPrioritiesMode id
getAPriosR, postAPriosR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAPriosR = postAPriosR
postAPriosR tid ssh ash = do
doNumericPrios <- runDB $ do
Entity aId _ <- getBy404 $ TermSchoolAllocationShort tid ssh ash
numericPrios <- E.selectCountRows . E.from $ \allocationUser -> do
E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId
E.where_ . E.maybe E.false sqlAllocationPriorityNumeric $ allocationUser E.^. AllocationUserPriority
ordinalPrios <- E.selectCountRows . E.from $ \allocationUser -> do
E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId
E.where_ . E.maybe E.false (E.not_ . sqlAllocationPriorityNumeric) $ allocationUser E.^. AllocationUserPriority
let doNumericPrios = ((>=) :: Int64 -> Int64 -> Bool) numericPrios ordinalPrios
return doNumericPrios
let explainAllocationPrioMode = \case
AllocationPrioritiesNumeric -> return $(i18nWidgetFile "allocation-priority-explanation/numeric")
AllocationPrioritiesOrdinal -> return $(i18nWidgetFile "allocation-priority-explanation/ordinal")
ignoreWarningMsg <- messageIconI Warning IconMissingAllocationPriority MsgAllocationMissingPrioritiesIgnored
((priosRes, priosView), priosEnctype) <- runFormPost . renderAForm FormStandard $ (,)
<$> apopt (explainedSelectionField Nothing (explainOptionList optionsFinite explainAllocationPrioMode)) (fslI MsgAllocationPrioritiesMode) (Just $ bool AllocationPrioritiesOrdinal AllocationPrioritiesNumeric doNumericPrios)
<* aformMessage ignoreWarningMsg
<*> areq fileField (fslI MsgAllocationPrioritiesFile) Nothing
formResult priosRes $ \(mode, fInfo) -> do
let sourcePrios = case mode of
AllocationPrioritiesNumeric -> transPipe liftHandler fInfo .| fileSourceCsvPositional Csv.NoHeader
AllocationPrioritiesOrdinal -> transPipe liftHandler fInfo .| fileSourceCsvPositional Csv.NoHeader .| C.map Csv.fromOnly .| ordinalPriorities
(matrSunk, matrMissing) <- runDB $ do
Entity aId _ <- getBy404 $ TermSchoolAllocationShort tid ssh ash
updateWhere
[ AllocationUserAllocation ==. aId ]
[ AllocationUserPriority =. Nothing ]
matrSunk <- runConduit $ sourcePrios .| sinkAllocationPriorities aId
matrMissing <- E.selectCountRows . E.from $ \allocationUser -> do
E.where_ $ allocationUser E.^. AllocationUserAllocation E.==. E.val aId
E.&&. E.isNothing (allocationUser E.^. AllocationUserPriority)
E.where_ . E.exists . E.from $ \(courseApplication `E.InnerJoin` allocationCourse) -> do
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. courseApplication E.^. CourseApplicationCourse
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationCourse E.^. AllocationCourseAllocation)
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. allocationUser E.^. AllocationUserUser
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (E.val aId)
return (matrSunk, matrMissing)
when (matrSunk > 0) $
addMessageI Success $ MsgAllocationPrioritiesSunk matrSunk
when (matrMissing > 0) $
addMessageI Error $ MsgAllocationPrioritiesMissing matrMissing
redirect $ AllocationR tid ssh ash AUsersR
siteLayoutMsg MsgAllocationPriorities $ do
setTitleI $ MsgAllocationPrioritiesTitle tid ssh ash
let priosForm = wrapForm priosView def
{ formEncoding = priosEnctype
, formAction = Just . SomeRoute $ AllocationR tid ssh ash APriosR
}
gradeScale <- getsYesod $ view _appAllocationGradeScale
gradeOrdinalProportion <- getsYesod $ view _appAllocationGradeOrdinalProportion
$(i18nWidgetFile "allocation-priorities")

View File

@ -1,79 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Register
( AllocationRegisterForm(..)
, AllocationRegisterButton(..)
, allocationRegisterForm
, allocationUserToForm
, postARegisterR
) where
import Import
import Handler.Utils.Form
{-# ANN module ("HLint: ignore Use newtype instead of data"::String) #-}
data AllocationRegisterForm = AllocationRegisterForm
{ arfTotalCourses :: Word64
}
allocationRegisterForm :: Maybe AllocationRegisterForm -> AForm Handler AllocationRegisterForm
allocationRegisterForm template
= AllocationRegisterForm
<$> areq (posIntFieldI MsgAllocationTotalCoursesNegative) (fslI MsgAllocationTotalCourses & setTooltip MsgAllocationTotalCoursesTip) (arfTotalCourses <$> template <|> Just 1)
allocationUserToForm :: AllocationUser -> AllocationRegisterForm
allocationUserToForm AllocationUser{..} = AllocationRegisterForm
{ arfTotalCourses = allocationUserTotalCourses
}
data AllocationRegisterButton = BtnAllocationRegister | BtnAllocationRegistrationEdit
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe AllocationRegisterButton
instance Finite AllocationRegisterButton
nullaryPathPiece ''AllocationRegisterButton $ camelToPathPiece' 1
embedRenderMessage ''UniWorX ''AllocationRegisterButton id
instance Button UniWorX AllocationRegisterButton where
btnLabel BtnAllocationRegister
= [whamlet|
$newline never
#{iconAllocationRegister} \
_{BtnAllocationRegister}
|]
btnLabel BtnAllocationRegistrationEdit
= [whamlet|
$newline never
#{iconAllocationRegistrationEdit} \
_{BtnAllocationRegistrationEdit}
|]
btnClasses _ = [BCIsButton, BCPrimary]
postARegisterR :: TermId -> SchoolId -> AllocationShorthand -> Handler Void
postARegisterR tid ssh ash = do
uid <- requireAuthId
((registerRes, _), _) <- runFormPost . renderAForm FormStandard $ allocationRegisterForm Nothing
formResult registerRes $ \AllocationRegisterForm{..} -> runDB $ do
aId <- getKeyBy404 $ TermSchoolAllocationShort tid ssh ash
isRegistered <- existsBy $ UniqueAllocationUser aId uid
void $ upsert AllocationUser
{ allocationUserAllocation = aId
, allocationUserUser = uid
, allocationUserTotalCourses = arfTotalCourses
, allocationUserPriority = Nothing
}
[ AllocationUserTotalCourses =. arfTotalCourses
]
audit $ TransactionAllocationUserEdited uid aId
if
| isRegistered -> addMessageI Success MsgAllocationRegistrationEdited
| otherwise -> addMessageI Success MsgAllocationRegistered
redirect $ AllocationR tid ssh ash AShowR :#: ("allocation-participation" :: Text)

View File

@ -1,226 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.Show
( getAShowR, postAShowR
) where
import Import
import Utils.Course
import Handler.Utils
import Handler.Utils.Allocation (allocationNotifyNewCourses)
import Handler.Allocation.Register
import Handler.Allocation.Application
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
data NotifyNewCourseButton
= BtnNotifyNewCourseForceOn
| BtnNotifyNewCourseForceOff
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
embedRenderMessage ''UniWorX ''NotifyNewCourseButton id
nullaryPathPiece ''NotifyNewCourseButton $ camelToPathPiece' 2
instance Button UniWorX NotifyNewCourseButton where
btnLabel BtnNotifyNewCourseForceOn
= [whamlet|
$newline never
#{iconNotification} \
_{BtnNotifyNewCourseForceOn}
|]
btnLabel BtnNotifyNewCourseForceOff
= [whamlet|
$newline never
#{iconNoNotification} \
_{BtnNotifyNewCourseForceOff}
|]
btnClasses _ = [BCIsButton]
getAShowR, postAShowR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAShowR = postAShowR
postAShowR tid ssh ash = do
muid <- maybeAuthId
now <- liftIO getCurrentTime
ata <- getSessionActiveAuthTags
let
resultCourse :: _ => Lens' a (Entity Course)
resultCourse = _1
resultCourseApplication :: _ => Traversal' a (Entity CourseApplication)
resultCourseApplication = _2 . _Just
resultHasTemplate :: _ => Lens' a Bool
resultHasTemplate = _3 . _Value
resultIsRegistered :: _ => Lens' a Bool
resultIsRegistered = _4 . _Value
resultCourseVisible :: _ => Lens' a Bool
resultCourseVisible = _5 . _Value
resultAllocationCourse :: _ => Lens' a AllocationCourse
resultAllocationCourse = _6 . _entityVal
resultParticipantCount :: _ => Lens' a Int
resultParticipantCount = _7 . _Value
resultRatingsCount :: _ => Getter a (Maybe Word64)
resultRatingsCount = _8 . _1 . _Value . to (assertM' (> 0))
resultVetosCount :: _ => Lens' a Word64
resultVetosCount = _8 . _2 . _Value
(Entity aId Allocation{..}, School{..}, isAnyLecturer, isAdmin, courses, registration, wouldNotifyNewCourse) <- runDB $ do
alloc@(Entity aId Allocation{allocationSchool}) <- getBy404 $ TermSchoolAllocationShort tid ssh ash
school <- getJust allocationSchool
courses <- E.select . E.from $ \((allocationCourse `E.InnerJoin` course) `E.LeftOuterJoin` courseApplication `E.LeftOuterJoin` registration) -> do
E.on $ registration E.?. CourseParticipantCourse E.==. E.just (course E.^. CourseId)
E.&&. registration E.?. CourseParticipantUser E.==. E.val muid
E.&&. registration E.?. CourseParticipantState E.==. E.just (E.val CourseParticipantActive)
E.on $ courseApplication E.?. CourseApplicationCourse E.==. E.just (course E.^. CourseId)
E.&&. courseApplication E.?. CourseApplicationUser E.==. E.val muid
E.&&. courseApplication E.?. CourseApplicationAllocation E.==. E.just (E.just $ E.val aId)
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
E.&&. ( E.isJust (courseApplication E.?. CourseApplicationId)
E.||. mayViewCourse muid ata now course (E.justVal aId)
)
E.orderBy [E.asc $ course E.^. CourseName]
let hasTemplate = E.exists . E.from $ \courseAppInstructionFile ->
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. course E.^. CourseId
participantCount = E.subSelectCount . E.from $ \courseParticipant ->
E.where_ $ courseParticipant E.^. CourseParticipantCourse E.==. course E.^. CourseId
E.&&. courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
ratingsCount = E.subSelectCount . E.from $ \courseApplication' -> do
E.where_ $ courseApplication' E.^. CourseApplicationCourse E.==. course E.^. CourseId
E.&&. courseApplication' E.^. CourseApplicationAllocation E.==. E.justVal aId
E.&&. ( E.isJust (courseApplication' E.^. CourseApplicationRatingPoints)
E.||. E.isJust (courseApplication' E.^. CourseApplicationRatingComment)
E.||. courseApplication' E.^. CourseApplicationRatingVeto
)
vetosCount = E.subSelectCount . E.from $ \courseApplication' -> do
E.where_ $ courseApplication' E.^. CourseApplicationCourse E.==. course E.^. CourseId
E.&&. courseApplication' E.^. CourseApplicationAllocation E.==. E.justVal aId
E.&&. courseApplication' E.^. CourseApplicationRatingVeto
return ( course
, courseApplication
, hasTemplate
, E.not_ . E.isNothing $ registration E.?. CourseParticipantId
, courseIsVisible now course $ E.justVal aId
, allocationCourse
, participantCount
, (ratingsCount, vetosCount)
)
registration <- fmap join . for muid $ getBy . UniqueAllocationUser aId
isAnyLecturer <- hasWriteAccessTo CourseNewR
isAdmin <- hasReadAccessTo $ AllocationR tid ssh ash AUsersR
wouldNotifyNewCourse <- fmap (maybe False E.unValue . join) . for muid $ E.selectMaybe . pure . allocationNotifyNewCourses (E.val aId) . E.val
return (alloc, school, isAnyLecturer, isAdmin, nubOrdOn (view $ resultCourse . _entityKey) courses, registration, wouldNotifyNewCourse)
let nextSubstitutesDeadline = minimumOf (folded . resultAllocationCourse . _allocationCourseAcceptSubstitutes . _Just . filtered (>= now)) courses
freeCapacity = fmap getSum . getAp . flip foldMap courses $ \cEntry -> Ap . fmap (Sum . max 0) $ subtract (cEntry ^. resultParticipantCount) <$> preview (resultCourse . _entityVal . _courseCapacity . _Just) cEntry
MsgRenderer mr <- getMsgRenderer
let title = MsgAllocationTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationName
shortTitle = MsgAllocationShortTitle (mr . ShortTermIdentifier $ unTermKey allocationTerm) (unSchoolKey allocationSchool) allocationShorthand
-- staffInformation <- anyM courses $ \(view $ resultCourse . _entityVal -> Course{..}) ->
-- hasReadAccessTo $ CourseR courseTerm courseSchool courseShorthand CApplicationsR
mayRegister <- hasWriteAccessTo $ AllocationR tid ssh ash ARegisterR
(registerForm, registerEnctype) <- generateFormPost . identifyForm FIDAllocationRegister . renderAForm FormStandard . allocationRegisterForm $ allocationUserToForm . entityVal <$> registration
let
registerBtn = bool BtnAllocationRegister BtnAllocationRegistrationEdit $ is _Just registration
registerForm' = wrapForm' registerBtn registerForm FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ AllocationR tid ssh ash ARegisterR
, formEncoding = registerEnctype
, formAttrs = []
, formSubmit = FormSubmit
, formAnchor = Nothing :: Maybe Text
}
((notificationResult, notificationForm), notificationEnctype) <- runFormPost . identifyForm FIDAllocationNotification . buttonForm' $ if
| wouldNotifyNewCourse
-> [BtnNotifyNewCourseForceOff]
| otherwise
-> [BtnNotifyNewCourseForceOn]
let
allocationNotificationIdent = "allocation-notification" :: Text
notificationForm' = wrapForm notificationForm FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ AllocationR tid ssh ash AShowR
, formEncoding = notificationEnctype
, formAttrs = []
, formSubmit = FormNoSubmit
, formAnchor = Just allocationNotificationIdent
}
whenIsJust muid $ \uid -> formResult notificationResult $ \notificationBtn -> do
let allocationNotificationSettingIsOptOut = case notificationBtn of
BtnNotifyNewCourseForceOn -> False
BtnNotifyNewCourseForceOff -> True
runDB . void $ upsertBy (UniqueAllocationNotificationSetting uid aId) AllocationNotificationSetting
{ allocationNotificationSettingUser = uid
, allocationNotificationSettingAllocation = aId
, allocationNotificationSettingIsOptOut
}
[ AllocationNotificationSettingIsOptOut =. allocationNotificationSettingIsOptOut ]
addMessageI Success $ bool MsgAllocationNotificationNewCourseSuccessForceOn MsgAllocationNotificationNewCourseSuccessForceOff allocationNotificationSettingIsOptOut
redirect $ AllocationR allocationTerm allocationSchool allocationShorthand AShowR :#: allocationNotificationIdent
siteLayoutMsg title $ do
setTitleI shortTitle
let courseWidgets = flip map courses $ \cEntry -> do
let Entity cid Course{..} = cEntry ^. resultCourse
hasApplicationTemplate = cEntry ^. resultHasTemplate
mApp = cEntry ^? resultCourseApplication
isRegistered = cEntry ^. resultIsRegistered
courseVisible = cEntry ^. resultCourseVisible
AllocationCourse{..} = cEntry ^. resultAllocationCourse
partCount = cEntry ^. resultParticipantCount
mRatings = cEntry ^. resultRatingsCount
vetos = cEntry ^. resultVetosCount
cID <- encrypt cid :: WidgetFor UniWorX CryptoUUIDCourse
mayApply <- hasWriteAccessTo . AllocationR tid ssh ash $ AApplyR cID
mayEdit <- hasWriteAccessTo $ CourseR tid ssh courseShorthand CEditR
isLecturer <- hasWriteAccessTo $ CourseR courseTerm courseSchool courseShorthand CEditR
mApplyFormView <- liftHandler . for muid $ \uid -> generateFormPost $ applicationForm (Just aId) cid (Just uid) (ApplicationFormMode True mayApply isLecturer) . Just
tRoute <- case mApp of
Nothing -> return . AllocationR tid ssh ash $ AApplyR cID
Just (Entity appId _) -> CApplicationR courseTerm courseSchool courseShorthand <$> encrypt appId <*> pure CAEditR
mApplicationTemplate <- runMaybeT $ do
guard hasApplicationTemplate
toTextUrl $ CourseR courseTerm courseSchool courseShorthand CRegisterTemplateR
let mApplyFormView' = view _1 <$> mApplyFormView
overrideVisible = not mayApply && is _Just mApp
case mApplyFormView of
Just (_, appFormEnctype)
-> wrapForm $(widgetFile "allocation/show/course") FormSettings
{ formMethod = POST
, formAction = Just $ SomeRoute tRoute
, formEncoding = appFormEnctype
, formAttrs = [ ("class", "allocation-course")
]
, formSubmit = FormNoSubmit
, formAnchor = Just cID
}
Nothing
-> let wdgt = $(widgetFile "allocation/show/course")
in [whamlet|
<div .allocation-course ##{toPathPiece cID}>
^{wdgt}
|]
let daysToRegistrationStart = assertM (>0) $ (`diffUTCTime` now) <$> allocationRegisterFrom
allocationInfoModal = modal [whamlet|_{MsgHeadingAllocationInfo}|] $ Left $ SomeRoute InfoAllocationR
numCourses = length courses
numAppliedCourses = lengthOf (folded . _2 . _Just) courses
$(widgetFile "allocation/show")

View File

@ -1,231 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Allocation.UserForm
( AllocationUserForm(..)
, allocationUserForm
, CourseParticipantForm(..)
, _CourseParticipantFormNotAllocated, _CourseParticipantFormDeregistered, _CourseParticipantFormRegistered, _cpfDeregisterReason, _cpfEverRegistered
, CourseParticipantForm'
, courseParticipantForm
) where
import Import
import Handler.Allocation.Application
import Handler.Utils
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import qualified Database.Esqueleto.PostgreSQL as E
import qualified Data.Map.Strict as Map
import Text.Blaze (toMarkup)
data AllocationUserForm = AllocationUserForm
{ aauUser :: UserId
, aauTotalCourses :: Word64
, aauPriority :: Maybe AllocationPriority
, aauApplications :: Map CourseId ApplicationForm
}
allocationUserForm :: forall m backend.
( MonadHandler m, HandlerSite m ~ UniWorX
, E.SqlBackendCanRead backend, IsSqlBackend backend
)
=> AllocationId
-> Maybe AllocationUserForm
-> AForm (ReaderT backend m) AllocationUserForm
allocationUserForm aId mTemplate = wFormToAForm $ do
allocCourses <- lift . lift . E.select . E.from $ \(course `E.InnerJoin` allocationCourse) -> do
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.where_ $ allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
return ( course
, E.exists . E.from $ \courseAppInstructionFile ->
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. course E.^. CourseId
, allocationCourse
)
MsgRenderer mr <- getMsgRenderer
userRes <- case aauUser <$> mTemplate of
Just u -> do
User{..} <- lift . lift $ get404 u
fvId <- newIdent
lift . tell $ pure FieldView
{ fvLabel = toMarkup $ mr MsgAllocationAddUserUser
, fvTooltip = Nothing
, fvId
, fvInput = nameWidget userDisplayName userSurname
, fvErrors = Nothing
, fvRequired = False
}
return $ FormSuccess u
Nothing -> wreq (checkMap (first $ const MsgAllocationAddUserUserNotFound) Right $ userField False Nothing) (fslpI MsgAllocationAddUserUser (mr MsgAllocationAddUserUserPlaceholder)) Nothing
totalCoursesRes <- wreq (posIntFieldI MsgAllocationAddUserTotalCoursesLessThanOne) (fslI MsgAllocationAddUserTotalCourses) ((aauTotalCourses <$> mTemplate) <|> Just 1)
priorityRes <- hoist (hoist liftHandler) $ optionalActionW (allocationPriorityForm (fslI MsgAllocationAddUserPriority) $ aauPriority =<< mTemplate) (fslI MsgAllocationAddUserSetPriority) ((is _Just . aauPriority <$> mTemplate) <|> Just True)
applicationsRes <- aFormToWForm $ allocationApplicationsForm aId (aauUser <$> mTemplate) (Map.fromList [ (cId, (course, allocationCourse, hasTemplate)) | (Entity cId course, E.Value hasTemplate, Entity _ allocationCourse) <- allocCourses ]) (fslI MsgAllocationAddUserApplications) False
return $ AllocationUserForm
<$> userRes
<*> totalCoursesRes
<*> priorityRes
<*> applicationsRes
allocationApplicationsForm :: forall m backend.
( MonadHandler m, HandlerSite m ~ UniWorX
, E.SqlBackendCanRead backend
)
=> AllocationId
-> Maybe UserId
-> Map CourseId (Course, AllocationCourse, Bool)
-> FieldSettings UniWorX
-> Bool
-> AForm (ReaderT backend m) (Map CourseId ApplicationForm)
allocationApplicationsForm aId muid courses FieldSettings{..} fvRequired = formToAForm $ do
now <- liftIO getCurrentTime
let afmApplicant = True
afmApplicantEdit = True
afmLecturer = True
appsRes' <- iforM courses $ \cId (course, allocCourse, hasApplicationTemplate) -> do
mApplicationTemplate <- runMaybeT $ do
guard hasApplicationTemplate
let Course{..} = course
toTextUrl $ CourseR courseTerm courseSchool courseShorthand CRegisterTemplateR
counts <- lift . fmap (maybe (Nothing, 0) $ bimap (assertM' (> 0) . E.unValue) E.unValue) . E.selectMaybe . E.from $ \courseApplication -> do
E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. E.val cId
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.justVal aId
let hasRating = E.isJust (courseApplication E.^. CourseApplicationRatingPoints)
E.||. E.isJust (courseApplication E.^. CourseApplicationRatingComment)
E.||. courseApplication E.^. CourseApplicationRatingVeto
return ( E.count (courseApplication E.^. CourseApplicationId) `E.filterWhere` hasRating
, E.count (courseApplication E.^. CourseApplicationId) `E.filterWhere` (courseApplication E.^. CourseApplicationRatingVeto)
)
hoist liftHandler $ over _2 (course, allocCourse, mApplicationTemplate, counts, ) <$> applicationForm (Just aId) cId muid ApplicationFormMode{..} Nothing
let appsRes = sequenceA $ view _1 <$> appsRes'
appsViews = view _2 <$> appsRes'
let fvInput =
[whamlet|
$newline never
<div .allocation__courses>
$forall (Course{courseTerm, courseSchool, courseShorthand, courseName, courseApplicationsInstructions}, AllocationCourse{allocationCourseAcceptSubstitutes}, mApplicationTemplate, (mRatings, vetos), ApplicationFormView{afvPriority, afvForm}) <- Map.elems appsViews
<div .allocation-course>
<div .allocation-course__priority-label .allocation__label>
_{MsgAllocationPriority}
<div .allocation-course__priority>
$maybe prioView <- afvPriority
^{fvWidget prioView}
<a .allocation-course__name href=@{CourseR courseTerm courseSchool courseShorthand CShowR} target="_blank">
#{courseName}
<div .allocation-course__admin-info>
<p>
$maybe deadline <- allocationCourseAcceptSubstitutes
_{MsgCourseAllocationCourseAcceptsSubstitutesUntil}: #
^{formatTimeW SelFormatDateTime deadline}
$nothing
_{MsgCourseAllocationCourseAcceptsSubstitutesNever}
$if allocationCourseAcceptSubstitutes >= Just now
\ ^{iconOK}
$maybe ratings <- mRatings
^{notification NotificationBroad =<< messageI Warning (MsgAllocationCourseHasRatings ratings vetos)}
$if is _Just mApplicationTemplate || is _Just courseApplicationsInstructions
<div .allocation-course__instructions-label .allocation__label>
_{MsgCourseAllocationApplicationInstructionsApplication}
<div .allocation-course__instructions>
$maybe aInst <- courseApplicationsInstructions
<p>
#{aInst}
$maybe templateUrl <- mApplicationTemplate
<p>
<a href=#{templateUrl}>
#{iconRegisterTemplate} _{MsgCourseAllocationApplicationTemplateApplication}
<div .allocation-course__application-label .interactive-fieldset__target .allocation__label uw-interactive-fieldset data-conditional-input=#{maybe "" fvId afvPriority} data-conditional-value="" data-conditional-negated>
_{MsgCourseApplication}
<div .allocation-course__application .interactive-fieldset__target uw-interactive-fieldset data-conditional-input=#{maybe "" fvId afvPriority} data-conditional-value="" data-conditional-negated>
^{renderFieldViews FormStandard afvForm}
|]
MsgRenderer mr <- getMsgRenderer
let fvLabel = toHtml $ mr fsLabel
fvTooltip = toHtml . mr <$> fsTooltip
fvErrors = case appsRes of
FormFailure errs -> Just
[shamlet|
$newline never
<ul>
$forall err <- errs
<li>#{err}
|]
_other -> Nothing
fvId <- maybe newIdent return fsId
return (appsRes, pure FieldView{..})
data CourseParticipantForm
= CourseParticipantFormNotAllocated -- ^ User is registered but not through allocation; no control
| CourseParticipantFormDeregistered -- ^ User is not currently registered
{ cpfDeregisterReason :: Maybe Text -- ^ `Just` if user was deregistered "self-inflicted", reason is required
, cpfEverRegistered :: Bool
}
| CourseParticipantFormRegistered -- ^ User is currently registered
deriving (Eq, Ord, Read, Show, Generic, Typeable)
type CourseParticipantForm' = Map CourseId CourseParticipantForm
makePrisms ''CourseParticipantForm
makeLenses_ ''CourseParticipantForm
courseParticipantForm :: forall m.
( MonadHandler m, HandlerSite m ~ UniWorX )
=> Map CourseId ((TermId, SchoolId, CourseShorthand), CourseName, CourseParticipantForm)
-> (Html -> MForm m (FormResult CourseParticipantForm', Widget))
courseParticipantForm courses csrf = do
lines' <- iforM courses $ \_cId ((tid, ssh, csh), cname, prevSt)
-> let toLine fCell = $(widgetFile "allocation/user-course-participant-form/line")
in over _2 toLine <$> case prevSt of
CourseParticipantFormNotAllocated -> do
(_, isRegView) <- mforced checkBoxField def True
return ( FormSuccess CourseParticipantFormNotAllocated
, $(widgetFile "allocation/user-course-participant-form/not-allocated")
)
_other -> do
let deregReason = prevSt ^? _cpfDeregisterReason . _Just
isRegPrev = is _CourseParticipantFormRegistered prevSt
everRegistered = fromMaybe True $ prevSt ^? _cpfEverRegistered
(isRegRes, isRegView) <- mpopt checkBoxField def $ Just isRegPrev
let selfInflictedFS = def
& addAttr "uw-interactive-fieldset" ""
& addAttr "data-conditional-input" (fvId isRegView)
& addAttr "data-conditional-negated" ""
(isSelfInflictedRes, isSelfInflictedView) <- if
| everRegistered -> over _2 Just <$> mopt (textField & cfStrip) selfInflictedFS (Just deregReason)
| otherwise -> return (FormSuccess Nothing, Nothing)
return ( case isRegRes of
FormMissing -> FormMissing
FormFailure es1 -> FormFailure $ es1 <> view _FormFailure isSelfInflictedRes
FormSuccess True
| FormFailure es2 <- isSelfInflictedRes
-> FormFailure es2
| otherwise
-> FormSuccess CourseParticipantFormRegistered
FormSuccess False
-> CourseParticipantFormDeregistered <$> isSelfInflictedRes <*> pure everRegistered
, $(widgetFile "allocation/user-course-participant-form/cell")
)
let linesWidget = Map.intersectionWith (,) courses lines'
& Map.elems
& sortOn (view $ _1 . _1)
& view (folded . _2 . _2)
return ( forM lines' $ view _1
, $(widgetFile "allocation/user-course-participant-form/layout")
)

View File

@ -1,350 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
module Handler.Allocation.Users
( getAUsersR, postAUsersR
) where
import Import
import Handler.Allocation.Accept
import Handler.Utils
import Handler.Utils.Allocation
import Handler.Utils.StudyFeatures
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import qualified Data.Csv as Csv
import Data.Map ((!?))
import qualified Data.Map as Map
import qualified Data.Set as Set
import Text.Blaze (toMarkup)
import qualified Data.Conduit.Combinators as C
type UserTableExpr = E.SqlExpr (Entity User)
`E.InnerJoin` E.SqlExpr (Entity AllocationUser)
queryUser :: Getter UserTableExpr (E.SqlExpr (Entity User))
queryUser = to $(E.sqlIJproj 2 1)
queryAllocationUser :: Getter UserTableExpr (E.SqlExpr (Entity AllocationUser))
queryAllocationUser = to $(E.sqlIJproj 2 2)
queryAppliedCourses :: Getter UserTableExpr (E.SqlExpr (E.Value Int))
queryAppliedCourses = queryAllocationUser . to queryAppliedCourses'
where queryAppliedCourses' allocationUser = E.subSelectCount . E.from $ \(courseApplication `E.InnerJoin` allocationCourse) -> do
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. courseApplication E.^. CourseApplicationCourse
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationCourse E.^. AllocationCourseAllocation)
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. allocationUser E.^. AllocationUserUser
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationUser E.^. AllocationUserAllocation)
queryAssignedCourses :: Getter UserTableExpr (E.SqlExpr (E.Value Int))
queryAssignedCourses = queryAllocationUser . to queryAssignedCourses'
where queryAssignedCourses' allocationUser = E.subSelectCount . E.from $ \courseParticipant ->
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. allocationUser E.^. AllocationUserUser
E.&&. courseParticipant E.^. CourseParticipantAllocated E.==. E.just (allocationUser E.^. AllocationUserAllocation)
E.&&. courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
queryVetoedCourses :: Getter UserTableExpr (E.SqlExpr (E.Value Int))
queryVetoedCourses = queryAllocationUser . to queryVetoedCourses'
where queryVetoedCourses' allocationUser = E.subSelectCount . E.from $ \(courseApplication `E.InnerJoin` allocationCourse) -> do
E.on $ allocationCourse E.^. AllocationCourseCourse E.==. courseApplication E.^. CourseApplicationCourse
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationCourse E.^. AllocationCourseAllocation)
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. allocationUser E.^. AllocationUserUser
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationUser E.^. AllocationUserAllocation)
E.where_ $ courseApplication E.^. CourseApplicationRatingVeto
E.||. courseApplication E.^. CourseApplicationRatingPoints `E.in_` E.valList (map Just $ filter (view $ passingGrade . _Wrapped . to not) universeF)
type UserTableData = DBRow ( Entity User
, UserTableStudyFeatures
, Entity AllocationUser
, Int
, Int
, Int
)
-- ^ `Int`s are applied, assigned, vetoed in that order
resultUser :: Lens' UserTableData (Entity User)
resultUser = _dbrOutput . _1
resultStudyFeatures :: Lens' UserTableData UserTableStudyFeatures
resultStudyFeatures = _dbrOutput . _2
resultAllocationUser :: Lens' UserTableData (Entity AllocationUser)
resultAllocationUser = _dbrOutput . _3
resultAppliedCourses, resultAssignedCourses, resultVetoedCourses :: Lens' UserTableData Int
resultAppliedCourses = _dbrOutput . _4
resultAssignedCourses = _dbrOutput . _5
resultVetoedCourses = _dbrOutput . _6
data AllocationUserTableCsv = AllocationUserTableCsv
{ csvAUserSurname :: Text
, csvAUserFirstName :: Text
, csvAUserName :: Text
, csvAUserMatriculation :: Maybe Text
, csvAUserStudyFeatures :: UserTableStudyFeatures
, csvAUserRequested
, csvAUserApplied
, csvAUserVetos
, csvAUserAssigned :: Natural
, csvAUserNewAssigned :: Maybe Natural
, csvAUserPriority :: Maybe AllocationPriority
} deriving (Generic)
makeLenses_ ''AllocationUserTableCsv
allocationUserTableCsvOptions :: Csv.Options
allocationUserTableCsvOptions = Csv.defaultOptions { Csv.fieldLabelModifier = camelToPathPiece' 3}
instance Csv.ToNamedRecord AllocationUserTableCsv where
toNamedRecord AllocationUserTableCsv{..} = Csv.namedRecord $
[ "surname" Csv..= csvAUserSurname
, "first-name" Csv..= csvAUserFirstName
, "name" Csv..= csvAUserName
, "matriculation" Csv..= csvAUserMatriculation
, "study-features" Csv..= csvAUserStudyFeatures
, "requested" Csv..= csvAUserRequested
, "applied" Csv..= csvAUserApplied
, "vetos" Csv..= csvAUserVetos
, "assigned" Csv..= csvAUserAssigned
] ++
[ "new-assigned" Csv..= newAssigned
| newAssigned <- hoistMaybe csvAUserNewAssigned
] ++
[ "priority" Csv..= csvAUserPriority
]
instance CsvColumnsExplained AllocationUserTableCsv where
csvColumnsExplanations = genericCsvColumnsExplanations allocationUserTableCsvOptions $ mconcat
[ singletonMap 'csvAUserSurname MsgCsvColumnAllocationUserSurname
, singletonMap 'csvAUserFirstName MsgCsvColumnAllocationUserFirstName
, singletonMap 'csvAUserName MsgCsvColumnAllocationUserName
, singletonMap 'csvAUserMatriculation MsgCsvColumnAllocationUserMatriculation
, singletonMap 'csvAUserStudyFeatures MsgCsvColumnAllocationUserStudyFeatures
, singletonMap 'csvAUserRequested MsgCsvColumnAllocationUserRequested
, singletonMap 'csvAUserApplied MsgCsvColumnAllocationUserApplied
, singletonMap 'csvAUserVetos MsgCsvColumnAllocationUserVetos
, singletonMap 'csvAUserAssigned MsgCsvColumnAllocationUserAssigned
, singletonMap 'csvAUserNewAssigned MsgCsvColumnAllocationUserNewAssigned
, singletonMap 'csvAUserPriority MsgCsvColumnAllocationUserPriority
]
userTableCsvHeader :: Bool -> Csv.Header
userTableCsvHeader hasNewAssigned = Csv.header $
[ "surname"
, "first-name"
, "name"
, "matriculation"
, "study-features"
, "requested"
, "applied"
, "vetos"
, "assigned"
] ++
[ "new-assigned"
| hasNewAssigned
] ++
[ "priority"
]
getAUsersR, postAUsersR :: TermId -> SchoolId -> AllocationShorthand -> Handler Html
getAUsersR = postAUsersR
postAUsersR tid ssh ash = do
(usersTable, acceptForm) <- runDB $ do
Entity aId _ <- getBy404 $ TermSchoolAllocationShort tid ssh ash
resultsDone <- is _Just <$> allocationStarted aId
allocMatching <- runMaybeT $ do
SessionDataAllocationResults allocMap <- MaybeT $ lookupSessionJson SessionAllocationResults
allocMatching <- fmap (view _4) . hoistMaybe $ allocMap !? (tid, ssh, ash)
return $ Map.fromListWith (<>) [ (uid, opoint cid) | (uid, cid) <- Set.toList allocMatching ] :: _ (Map UserId (NonNull (Set CourseId)))
let
allocationUsersDBTable = DBTable{..}
where
dbtSQLQuery = runReaderT $ do
user <- view queryUser
allocationUser <- view queryAllocationUser
applied <- view queryAppliedCourses
assigned <- view queryAssignedCourses
vetoed <- view queryVetoedCourses
lift $ do
E.on $ user E.^. UserId E.==. allocationUser E.^. AllocationUserUser
E.&&. allocationUser E.^. AllocationUserAllocation E.==. E.val aId
E.where_ $ applied E.>. E.val 0
E.||. assigned E.>. E.val 0
return ( user
, allocationUser
, applied
, assigned
, vetoed)
dbtRowKey = views queryAllocationUser (E.^. AllocationUserId)
dbtProj = dbtProjSimple . runReaderT $ do
feats <- lift . allocationUserStudyFeatures aId =<< views _1 entityKey
(,,,,,)
<$> view _1 <*> pure feats <*> view _2 <*> view (_3 . _Value) <*> view (_4 . _Value) <*> view (_5 . _Value)
dbtColonnade :: Colonnade Sortable _ _
dbtColonnade = mconcat . catMaybes $
[ pure . sortable (Just "user-name") (i18nCell MsgUserDisplayName) $ \(view resultUser -> Entity uid User{..})
-> let mkUrl = do
cID <- encrypt uid
return . AllocationR tid ssh ash $ AEditUserR cID
in anchorCellM mkUrl (nameWidget userDisplayName userSurname)
, pure $ colUserMatriculation (resultUser . _entityVal . _userMatrikelnummer)
, pure $ colStudyFeatures resultStudyFeatures
, pure $ colAllocationRequested (resultAllocationUser . _entityVal . _allocationUserTotalCourses . _Integral)
, pure . coursesModalApplied $ colAllocationApplied resultAppliedCourses
, pure . coursesModalVetoed $ colAllocationVetoed resultVetoedCourses
, guardOn resultsDone . coursesModalAssigned . bool id (assignedHeated $ view resultAssignedCourses) resultsDone $ colAllocationAssigned resultAssignedCourses
, coursesModalNewAssigned <$> do
allocMatching' <- allocMatching
let newAssigned uid = maybe 0 olength $ allocMatching' !? uid
pure . assignedHeated (views (resultUser . _entityKey) newAssigned) . sortable (Just "new-assigned") (i18nCell MsgAllocationUserNewMatches) .
views (resultUser . _entityKey) $ cell . toWidget . toMarkup . newAssigned
, pure $ emptyOpticColonnade' emptyPriorityCell (resultAllocationUser . _entityVal . _allocationUserPriority . _Just) colAllocationPriority
]
where
emptyPriorityCell = addCellClass ("table__td--center" :: Text) . cell $
messageTooltip =<< messageIconI Error IconMissingAllocationPriority MsgAllocationMissingPrioritiesIgnored
assignedHeated fAssigned = imapColonnade assignedHeated'
where
assignedHeated' res
= let maxAssign = min (res ^. resultAllocationUser . _entityVal . _allocationUserTotalCourses . to fromIntegral)
(res ^. resultAppliedCourses)
assigned = fAssigned res
in cellAttrs <>~ [ ("class", "heated")
, ("style", [st|--hotness: #{tshow (coHeat maxAssign assigned)}|])
]
coursesModalApplied = coursesModal $ \res -> E.from $ \(course `E.InnerJoin` courseApplication) -> do
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.val (Just aId)
E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val (res ^. resultUser . _entityKey)
E.orderBy [E.desc $ courseApplication E.^. CourseApplicationAllocationPriority]
return ( course
, courseApplication E.^. CourseApplicationRatingPoints
, E.just $ courseApplication E.^. CourseApplicationRatingVeto
, E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
)
coursesModalVetoed = coursesModal $ \res -> E.from $ \(course `E.InnerJoin` courseApplication) -> do
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. E.val (Just aId)
E.&&. courseApplication E.^. CourseApplicationUser E.==. E.val (res ^. resultUser . _entityKey)
E.where_ $ courseApplication E.^. CourseApplicationRatingVeto
E.||. courseApplication E.^. CourseApplicationRatingPoints `E.in_` E.valList (map Just $ filter (view $ passingGrade . _Wrapped . to not) universeF)
return ( course
, E.nothing
, E.nothing
, E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. E.val aId
)
coursesModalAssigned = coursesModal $ \res -> E.from $ \(course `E.InnerJoin` courseParticipant) -> do
E.on $ courseParticipant E.^. CourseParticipantCourse E.==. course E.^. CourseId
E.&&. courseParticipant E.^. CourseParticipantAllocated E.==. E.val (Just aId)
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val (res ^. resultUser . _entityKey)
E.orderBy [E.asc $ courseParticipant E.^. CourseParticipantRegistration]
return ( course
, E.nothing
, E.nothing
, courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
)
coursesModalNewAssigned = coursesModal $ \res -> E.from $ \course -> do
E.where_ $ course E.^. CourseId `E.in_` E.valList (maybe [] otoList $ Map.lookup (res ^. resultUser . _entityKey) =<< allocMatching)
return ( course
, E.nothing
, E.nothing
, E.true
)
coursesModal :: (_ -> E.SqlQuery (E.SqlExpr (Entity Course), E.SqlExpr (E.Value (Maybe ExamGrade)), E.SqlExpr (E.Value (Maybe Bool)), E.SqlExpr (E.Value Bool))) -> _ -> _
coursesModal courseSel = imapColonnade coursesModal'
where
coursesModal' res innerCell = review dbCell . (innerCell ^. cellAttrs, ) $ do
courses <- lift . E.select $ courseSel res
contents <- innerCell ^. cellContents
return $ if
| null courses -> contents
| otherwise -> let tooltipContent = $(widgetFile "table/cell/allocation-courses")
in $(widgetFile "widgets/tooltip_no-handle")
dbtSorting = mconcat
[ sortUserName' $ queryUser . $(multifocusG 2) (to (E.^. UserDisplayName)) (to (E.^. UserSurname))
, sortUserMatriculation $ queryUser . to (E.^. UserMatrikelnummer)
, sortAllocationApplied queryAppliedCourses
, sortAllocationAssigned queryAssignedCourses
, sortAllocationRequested $ queryAllocationUser . to (E.^. AllocationUserTotalCourses)
, sortAllocationVetoed queryVetoedCourses
, sortAllocationPriority $ queryAllocationUser . to (E.^. AllocationUserPriority)
, singletonMap "new-assigned" $
SortProjected . comparing $ (\uid -> maybe 0 olength $ Map.lookup uid =<< allocMatching) . view (resultUser . _entityKey)
]
dbtFilter = mconcat
[ fltrUserName' $ queryUser . to (E.^. UserDisplayName)
, fltrUserMatriculation $ queryUser . to (E.^. UserMatrikelnummer)
]
dbtFilterUI = mconcat
[ fltrUserNameUI'
, fltrUserMatriculationUI
]
dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout }
dbtParams = def
dbtIdent :: Text
dbtIdent = "allocation-users"
dbtCsvName = MsgAllocationUsersCsvName tid ssh ash
dbtCsvSheetName = MsgAllocationUsersCsvSheetName tid ssh ash
dbtCsvEncode = return DBTCsvEncode
{ dbtCsvExportForm = pure ()
, dbtCsvDoEncode = \() -> C.mapM $ \(_, row) -> flip runReaderT row $
AllocationUserTableCsv
<$> view (resultUser . _entityVal . _userSurname)
<*> view (resultUser . _entityVal . _userFirstName)
<*> view (resultUser . _entityVal . _userDisplayName)
<*> view (resultUser . _entityVal . _userMatrikelnummer)
<*> view resultStudyFeatures
<*> view (resultAllocationUser . _entityVal . _allocationUserTotalCourses . _Integral)
<*> view (resultAppliedCourses . _Integral)
<*> view (resultVetoedCourses . _Integral)
<*> view (resultAssignedCourses . _Integral)
<*> views (resultUser . _entityKey) (\uid -> maybe 0 (fromIntegral . olength) . Map.lookup uid <$> allocMatching)
<*> view (resultAllocationUser . _entityVal . _allocationUserPriority)
, dbtCsvName, dbtCsvSheetName
, dbtCsvNoExportData = Just id
, dbtCsvHeader = \_ -> return . userTableCsvHeader $ is _Just allocMatching
, dbtCsvExampleData = Nothing
}
dbtCsvDecode = Nothing
dbtExtraReps = []
allocationUsersDBTableValidator = def
& defaultSorting [SortAscBy "priority", SortAscBy "user-matriculation"]
& defaultPagesize (PagesizeLimit 500)
usersTable <- dbTableDB' allocationUsersDBTableValidator allocationUsersDBTable
acceptForm <- allocationAcceptForm aId
return (usersTable, acceptForm)
acceptView <- for acceptForm $ \acceptForm' -> do
(acceptWgt, acceptEnctype) <- generateFormPost acceptForm'
return $ wrapForm' BtnAllocationAccept acceptWgt def
{ formAction = Just . SomeRoute $ AllocationR tid ssh ash AAcceptR
, formEncoding = acceptEnctype
}
siteLayoutMsg MsgAllocationUsers $ do
setTitleI $ MsgAllocationUsersTitle tid ssh ash
$(widgetFile "allocation/users")

View File

@ -21,7 +21,6 @@ import Handler.Course.Register as Handler.Course
import Handler.Course.Show as Handler.Course import Handler.Course.Show as Handler.Course
import Handler.Course.User as Handler.Course import Handler.Course.User as Handler.Course
import Handler.Course.Users as Handler.Course import Handler.Course.Users as Handler.Course
import Handler.Course.Application as Handler.Course
import Handler.ExamOffice.Course as Handler.Course import Handler.ExamOffice.Course as Handler.Course
import Handler.Course.News as Handler.Course import Handler.Course.News as Handler.Course
import Handler.Course.Events as Handler.Course import Handler.Course.Events as Handler.Course

View File

@ -1,11 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Course.Application
( module Handler.Course.Application
) where
import Handler.Course.Application.List as Handler.Course.Application
import Handler.Course.Application.Files as Handler.Course.Application
import Handler.Course.Application.Edit as Handler.Course.Application

View File

@ -1,62 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Course.Application.Edit
( getCAEditR, postCAEditR
) where
import Import
import Handler.Utils
import Handler.Allocation.Application
getCAEditR, postCAEditR :: TermId -> SchoolId -> CourseShorthand -> CryptoFileNameCourseApplication -> Handler Html
getCAEditR = postCAEditR
postCAEditR tid ssh csh cID = do
uid <- requireAuthId
appId <- decrypt cID
(mAlloc, Entity cid Course{..}, CourseApplication{..}, User{..}) <- runDB $ do
course <- getBy404 $ TermSchoolCourseShort tid ssh csh
app <- get404 appId
mAlloc <- traverse getEntity404 $ courseApplicationAllocation app
appUser <- get404 $ courseApplicationUser app
return (mAlloc, course, app, appUser)
isAdmin <- case mAlloc of
Just (Entity _ Allocation{..})
-> hasWriteAccessTo $ AllocationR allocationTerm allocationSchool allocationShorthand AEditR
Nothing
-> hasWriteAccessTo $ SchoolR courseSchool SchoolEditR
let afmApplicant = uid == courseApplicationUser || isAdmin
afmLecturer <- hasWriteAccessTo $ CourseR courseTerm courseSchool courseShorthand CEditR
mayEdit <- hasWriteAccessTo $ CApplicationR tid ssh csh cID CAEditR
courseCID <- encrypt cid :: Handler CryptoUUIDCourse
let afMode = ApplicationFormMode
{ afmApplicant
, afmApplicantEdit = afmApplicant && mayEdit
, afmLecturer
}
(ApplicationFormView{..}, appEnc) <- editApplicationR (entityKey <$> mAlloc) courseApplicationUser cid (Just appId) afMode (/= BtnAllocationApply) $ if
| uid == courseApplicationUser
, Just (Entity _ Allocation{..}) <- mAlloc
-> SomeRoute $ AllocationR allocationTerm allocationSchool allocationShorthand AShowR :#: courseCID
| otherwise
-> SomeRoute $ CApplicationR tid ssh csh cID CAEditR
let title = MsgCourseApplicationTitle userDisplayName courseShorthand
siteLayoutMsg title $ do
setTitleI title
wrapForm ((<> snd afvButtons) . renderFieldViews FormStandard . maybe id (:) afvPriority$ afvForm) FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ CApplicationR tid ssh csh cID CAEditR
, formEncoding = appEnc
, formAttrs = []
, formSubmit = FormNoSubmit
, formAnchor = Nothing :: Maybe Text
}

View File

@ -1,111 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
module Handler.Course.Application.Files
( getCAFilesR
, getCAppsFilesR
) where
import Import
import Handler.Utils
import qualified Data.Conduit.List as C
import qualified Database.Esqueleto.Legacy as E
import qualified Data.CaseInsensitive as CI
getCAFilesR :: TermId -> SchoolId -> CourseShorthand -> CryptoFileNameCourseApplication -> Handler TypedContent
getCAFilesR tid ssh csh cID = do
appId <- decrypt cID
User{..} <- runDB $ do
CourseApplication{..} <- get404 appId
Course{..} <- get404 courseApplicationCourse
let matches = and
[ tid == courseTerm
, ssh == courseSchool
, csh == courseShorthand
]
unless matches . redirectWith movedPermanently301 $ CApplicationR courseTerm courseSchool courseShorthand cID CAFilesR
get404 courseApplicationUser
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack) . ap getMessageRender . pure $ MsgCourseApplicationArchiveName tid ssh csh cID userDisplayName
let
fsSource = E.selectSource . E.from $ \courseApplicationFile -> do
E.where_ $ courseApplicationFile E.^. CourseApplicationFileApplication E.==. E.val appId
return courseApplicationFile
serveSomeFiles archiveName $ fsSource .| C.map entityVal
getCAppsFilesR :: TermId -> SchoolId -> CourseShorthand -> Handler TypedContent
getCAppsFilesR tid ssh csh = do
runDB . existsBy404 $ TermSchoolCourseShort tid ssh csh
MsgRenderer mr <- getMsgRenderer
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack) . ap getMessageRender . pure $ MsgCourseAllApplicationsArchiveName tid ssh csh
let
fsSource :: ConduitT () CourseApplicationFile DB ()
fsSource = do
apps <- lift . E.select . E.from $ \((course `E.InnerJoin` courseApplication `E.InnerJoin` user) `E.LeftOuterJoin` allocation) -> do
E.on $ allocation E.?. AllocationId E.==. courseApplication E.^. CourseApplicationAllocation
E.on $ user E.^. UserId E.==. courseApplication E.^. CourseApplicationUser
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
return (allocation, user, courseApplication)
apps' <- flip filterM apps $ \(_, _, Entity appId _) -> do
cID <- encrypt appId
lift . hasReadAccessTo $ CApplicationR tid ssh csh cID CAFilesR
let
applicationAllocs = setOf (folded . _1) apps'
allocations = applicationAllocs ^.. folded . _Just . _entityVal . $(multifocusG 3) _allocationTerm _allocationSchool _allocationShorthand
allEqualOn :: Eq x => Getter _ x -> Bool
allEqualOn l = maybe True (\x -> allOf (folded . l) (== x) allocations) (allocations ^? _head . l)
mkAllocationDir mbAlloc
| not $ allEqualOn _1
, Just Allocation{..} <- mbAlloc
= (</>) $ unpack [st|#{CI.foldCase (termToText (unTermKey allocationTerm))}-#{CI.foldedCase (unSchoolKey allocationSchool)}-#{CI.foldedCase allocationShorthand}|]
| not $ allEqualOn _2
, Just Allocation{..} <- mbAlloc
= (</>) $ unpack [st|#{CI.foldedCase (unSchoolKey allocationSchool)}-#{CI.foldedCase allocationShorthand}|]
| not $ allEqualOn _3
, Just Allocation{..} <- mbAlloc
= (</>) . unpack $ CI.foldedCase allocationShorthand
| Just Allocation{} <- mbAlloc
, not $ all (is _Just) applicationAllocs
= (</>) . unpack $ mr MsgCourseApplicationsAllocatedDirectory
| Nothing <- mbAlloc
, any (is _Just) applicationAllocs
= (</>) . unpack $ mr MsgCourseApplicationsNotAllocatedDirectory
| otherwise
= id
forM_ apps' $ \(mbAlloc, Entity _ User{..}, Entity appId CourseApplication{..}) -> do
cID <- encrypt appId :: _ CryptoFileNameCourseApplication
let mkAppDir = mkAllocationDir (entityVal <$> mbAlloc) . (</>) (unpack [st|#{CI.foldedCase $ ciphertext cID}-#{CI.foldCase userSurname}|])
fileEntitySource = E.selectSource . E.from $ \courseApplicationFile -> do
E.where_ $ courseApplicationFile E.^. CourseApplicationFileApplication E.==. E.val appId
return courseApplicationFile
yield $ _FileReference # ( FileReference
{ fileReferenceModified = courseApplicationTime
, fileReferenceTitle = mkAppDir ""
, fileReferenceContent = Nothing
}
, CourseApplicationFileResidual appId
)
fileEntitySource .| C.map (view _entityVal) .| C.map (over (_FileReference . _1 . _fileReferenceTitle) mkAppDir)
serveSomeFiles archiveName fsSource

View File

@ -1,634 +0,0 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
module Handler.Course.Application.List
( getCApplicationsR, postCApplicationsR
) where
import Import
import Handler.Utils
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import Database.Esqueleto.Utils.TH
import qualified Data.Csv as Csv
import qualified Data.Text as Text
import qualified Data.Text.Lens as Text
import qualified Data.CaseInsensitive as CI
import qualified Data.Map as Map
import qualified Data.Conduit.List as C
import Handler.Course.ParticipantInvite
import Handler.Utils.StudyFeatures
import Jobs.Queue
type CourseApplicationsTableExpr = ( E.SqlExpr (Entity CourseApplication)
`E.InnerJoin` E.SqlExpr (Entity User)
)
`E.LeftOuterJoin` E.SqlExpr (Maybe (Entity Allocation))
`E.LeftOuterJoin` E.SqlExpr (Maybe (Entity CourseParticipant))
type CourseApplicationsTableData = DBRow ( Entity CourseApplication
, Entity User
, Bool -- hasFiles
, Maybe (Entity Allocation)
, Bool -- isParticipant
, UserTableStudyFeatures
, Bool -- hasAllocationUser
)
courseApplicationsIdent :: Text
courseApplicationsIdent = "applications"
queryCourseApplication :: Getter CourseApplicationsTableExpr (E.SqlExpr (Entity CourseApplication))
queryCourseApplication = to $ $(sqlIJproj 2 1) . $(sqlLOJproj 3 1)
queryUser :: Getter CourseApplicationsTableExpr (E.SqlExpr (Entity User))
queryUser = to $ $(sqlIJproj 2 2) . $(sqlLOJproj 3 1)
queryHasFiles :: Getter CourseApplicationsTableExpr (E.SqlExpr (E.Value Bool))
queryHasFiles = to $ hasFiles . $(sqlIJproj 2 1) . $(sqlLOJproj 3 1)
where
hasFiles appl = E.exists . E.from $ \courseApplicationFile ->
E.where_ $ courseApplicationFile E.^. CourseApplicationFileApplication E.==. appl E.^. CourseApplicationId
queryAllocation :: Getter CourseApplicationsTableExpr (E.SqlExpr (Maybe (Entity Allocation)))
queryAllocation = to $(sqlLOJproj 3 2)
queryHasAllocationUser :: Getter CourseApplicationsTableExpr (E.SqlExpr (E.Value Bool))
queryHasAllocationUser = to hasAllocationUser
where
hasAllocationUser (view queryCourseApplication -> courseApplication) = E.exists . E.from $ \allocationUser ->
E.where_ $ E.just (allocationUser E.^. AllocationUserAllocation) E.==. courseApplication E.^. CourseApplicationAllocation
E.&&. allocationUser E.^. AllocationUserUser E.==. courseApplication E.^. CourseApplicationUser
queryCourseParticipant :: Getter CourseApplicationsTableExpr (E.SqlExpr (Maybe (Entity CourseParticipant)))
queryCourseParticipant = to $(sqlLOJproj 3 3)
queryIsParticipant :: Getter CourseApplicationsTableExpr (E.SqlExpr (E.Value Bool))
queryIsParticipant = to $ E.not_ . E.isNothing . (E.?. CourseParticipantId) . $(sqlLOJproj 3 3)
resultCourseApplication :: Lens' CourseApplicationsTableData (Entity CourseApplication)
resultCourseApplication = _dbrOutput . _1
resultUser :: Lens' CourseApplicationsTableData (Entity User)
resultUser = _dbrOutput . _2
resultHasFiles :: Lens' CourseApplicationsTableData Bool
resultHasFiles = _dbrOutput . _3
resultAllocation :: Traversal' CourseApplicationsTableData (Entity Allocation)
resultAllocation = _dbrOutput . _4 . _Just
resultIsParticipant :: Lens' CourseApplicationsTableData Bool
resultIsParticipant = _dbrOutput . _5
resultStudyFeatures :: Lens' CourseApplicationsTableData UserTableStudyFeatures
resultStudyFeatures = _dbrOutput . _6
resultHasAllocationUser :: Lens' CourseApplicationsTableData Bool
resultHasAllocationUser = _dbrOutput . _7
newtype CourseApplicationsTableVeto = CourseApplicationsTableVeto Bool
deriving (Eq, Ord, Read, Show, Generic, Typeable)
deriving newtype (Enum, Bounded)
makePrisms ''CourseApplicationsTableVeto
instance Csv.ToField CourseApplicationsTableVeto where
toField (CourseApplicationsTableVeto True) = "veto"
toField (CourseApplicationsTableVeto False) = ""
instance Csv.FromField CourseApplicationsTableVeto where
parseField f = do
(CI.map Text.strip -> t :: CI Text) <- Csv.parseField f
return . CourseApplicationsTableVeto $ elem t
[ "veto", "v", "yes", "y", "ja", "j", "wahr", "w", "true", "t", "1" ]
data CourseApplicationsTableCsv = CourseApplicationsTableCsv
{ csvCAAllocation :: Maybe AllocationShorthand
, csvCAApplication :: Maybe CryptoFileNameCourseApplication
, csvCAName :: Maybe Text
, csvCAEmail :: Maybe UserEmail
, csvCAMatriculation :: Maybe Text
, csvCAStudyFeatures :: UserTableStudyFeatures
, csvCAText :: Maybe Text
, csvCAHasFiles :: Maybe Bool
, csvCAVeto :: Maybe CourseApplicationsTableVeto
, csvCARating :: Maybe ExamGrade
, csvCAComment :: Maybe Text
} deriving (Generic)
makeLenses_ ''CourseApplicationsTableCsv
courseApplicationsTableCsvOptions :: Csv.Options
courseApplicationsTableCsvOptions = Csv.defaultOptions { Csv.fieldLabelModifier = camelToPathPiece' 2 }
instance Csv.ToNamedRecord CourseApplicationsTableCsv where
toNamedRecord = Csv.genericToNamedRecord courseApplicationsTableCsvOptions
instance Csv.FromNamedRecord CourseApplicationsTableCsv where
parseNamedRecord csv
= CourseApplicationsTableCsv
<$> csv .:?? "allocation"
<*> csv .:?? "application"
<*> csv .:?? "name"
<*> csv .:?? "email"
<*> csv .:?? "matriculation"
<*> pure mempty
<*> csv .:?? "text"
<*> csv .:?? "has-files"
<*> csv .:?? "veto"
<*> csv .:?? "rating"
<*> csv .:?? "comment"
instance Csv.DefaultOrdered CourseApplicationsTableCsv where
headerOrder = Csv.genericHeaderOrder courseApplicationsTableCsvOptions
instance CsvColumnsExplained CourseApplicationsTableCsv where
csvColumnsExplanations = genericCsvColumnsExplanations courseApplicationsTableCsvOptions $ Map.fromList
[ ('csvCAAllocation , MsgCsvColumnApplicationsAllocation )
, ('csvCAApplication , MsgCsvColumnApplicationsApplication )
, ('csvCAName , MsgCsvColumnApplicationsName )
, ('csvCAEmail , MsgCsvColumnApplicationsEmail )
, ('csvCAMatriculation, MsgCsvColumnApplicationsMatriculation)
, ('csvCAStudyFeatures, MsgCsvColumnUserAppStudyFeatures )
, ('csvCAText , MsgCsvColumnApplicationsText )
, ('csvCAHasFiles , MsgCsvColumnApplicationsHasFiles )
, ('csvCAVeto , MsgCsvColumnApplicationsVeto )
, ('csvCARating , MsgCsvColumnApplicationsRating )
, ('csvCAComment , MsgCsvColumnApplicationsComment )
]
data CourseApplicationsTableCsvActionClass
= CourseApplicationsTableCsvSetVeto
| CourseApplicationsTableCsvSetRating
| CourseApplicationsTableCsvSetComment
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
embedRenderMessage ''UniWorX ''CourseApplicationsTableCsvActionClass id
data CourseApplicationsTableCsvAction
= CourseApplicationsTableCsvSetVetoData
{ caCsvActApplication :: CourseApplicationId
, caCsvActVeto :: Bool
}
| CourseApplicationsTableCsvSetRatingData
{ caCsvActApplication :: CourseApplicationId
, caCsvActRating :: Maybe ExamGrade
}
| CourseApplicationsTableCsvSetCommentData
{ caCsvActApplication :: CourseApplicationId
, caCsvActComment :: Maybe Text
}
deriving (Eq, Ord, Read, Show, Generic, Typeable)
deriveJSON defaultOptions
{ constructorTagModifier = over Text.packed $ Text.intercalate "-" . map Text.toLower . drop 4 . dropEnd 1 . splitCamel
, fieldLabelModifier = camelToPathPiece' 3
, sumEncoding = TaggedObject "action" "data"
} ''CourseApplicationsTableCsvAction
data CourseApplicationsTableCsvException
= CourseApplicationsTableCsvExceptionNoMatchingUser
| CourseApplicationsTableCsvExceptionNoMatchingAllocation
| CourseApplicationsTableCsvExceptionNoMatchingStudyFeatures
deriving (Show, Generic, Typeable)
instance Exception CourseApplicationsTableCsvException
embedRenderMessage ''UniWorX ''CourseApplicationsTableCsvException id
data ButtonAcceptApplications = BtnAcceptApplications
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonAcceptApplications
instance Finite ButtonAcceptApplications
nullaryPathPiece ''ButtonAcceptApplications $ camelToPathPiece' 1
embedRenderMessage ''UniWorX ''ButtonAcceptApplications id
instance Button UniWorX ButtonAcceptApplications where
btnClasses BtnAcceptApplications = [BCIsButton]
data AcceptApplicationsMode = AcceptApplicationsInvite
| AcceptApplicationsDirect
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe AcceptApplicationsMode
instance Finite AcceptApplicationsMode
nullaryPathPiece ''AcceptApplicationsMode $ camelToPathPiece' 2
embedRenderMessage ''UniWorX ''AcceptApplicationsMode id
data AcceptApplicationsSecondary = AcceptApplicationsSecondaryRandom
| AcceptApplicationsSecondaryTime
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe AcceptApplicationsSecondary
instance Finite AcceptApplicationsSecondary
nullaryPathPiece ''AcceptApplicationsSecondary $ camelToPathPiece' 3
embedRenderMessage ''UniWorX ''AcceptApplicationsSecondary id
getCApplicationsR, postCApplicationsR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCApplicationsR = postCApplicationsR
postCApplicationsR tid ssh csh = do
(table, allocationsBounds, mayAccept) <- runDB $ do
now <- liftIO getCurrentTime
Entity cid Course{..} <- getBy404 $ TermSchoolCourseShort tid ssh csh
let
allocationLink :: Allocation -> SomeRoute UniWorX
allocationLink Allocation{..} = SomeRoute $ AllocationR allocationTerm allocationSchool allocationShorthand AShowR
participantLink :: (MonadHandler m, HandlerSite m ~ UniWorX) => UserId -> m (SomeRoute UniWorX)
participantLink uid = liftHandler $ do
cID <- encrypt uid
return . SomeRoute . CourseR tid ssh csh $ CUserR cID
applicationLink :: (MonadHandler m, HandlerSite m ~ UniWorX) => CourseApplicationId -> m (SomeRoute UniWorX)
applicationLink appId = liftHandler $ do
cID <- encrypt appId
return . SomeRoute $ CApplicationR tid ssh csh cID CAEditR
dbtSQLQuery :: CourseApplicationsTableExpr -> E.SqlQuery _
dbtSQLQuery = runReaderT $ do
courseApplication <- view queryCourseApplication
hasFiles <- view queryHasFiles
user <- view queryUser
allocation <- view queryAllocation
courseParticipant <- view queryCourseParticipant
hasAllocationUser <- view queryHasAllocationUser
lift $ do
E.on $ E.just (user E.^. UserId) E.==. courseParticipant E.?. CourseParticipantUser
E.&&. courseParticipant E.?. CourseParticipantCourse E.==. E.just (E.val cid)
E.&&. courseParticipant E.?. CourseParticipantState E.==. E.just (E.val CourseParticipantActive)
E.on $ courseApplication E.^. CourseApplicationAllocation E.==. allocation E.?. AllocationId
E.on $ user E.^. UserId E.==. courseApplication E.^. CourseApplicationUser
E.&&. courseApplication E.^. CourseApplicationCourse E.==. E.val cid
E.where_ $ E.maybe E.true (E.maybe E.false (E.<=. E.val now)) (allocation E.?. AllocationStaffAllocationFrom)
return ( courseApplication
, user
, hasFiles
, allocation
, E.not_ . E.isNothing $ courseParticipant E.?. CourseParticipantId
, hasAllocationUser
)
dbtProj :: _ CourseApplicationsTableData
dbtProj = dbtProjSimple $ \(application, user, E.Value hasFiles, allocation, E.Value isParticipant, E.Value hasAllocationUser) -> do
feats <- courseUserStudyFeatures (application ^. _entityVal . _courseApplicationCourse) (user ^. _entityKey)
return (application, user, hasFiles, allocation, isParticipant, feats, hasAllocationUser)
dbtRowKey = view $ queryCourseApplication . to (E.^. CourseApplicationId)
dbtColonnade :: Cornice Sortable ('Cap 'Base) _ _
dbtColonnade = mconcat
[ cap (Sortable Nothing generatedColumnsHeader) $ mconcat
[ sortable (Just "participant") (i18nCell MsgCourseApplicationIsParticipant) $ bool mempty (cell $ toWidget iconOK) . view resultIsParticipant
, sortable (Just "allocation-short") (i18nCell MsgAllocation) $ \x ->
let noAllocationUserShort alloc =
[whamlet|
$newline never
<span .allocation--no-allocation-user>
#{view _allocationShorthand alloc}
|]
noAllocationUserTooltip = messageTooltip =<< messageIconI Warning IconNoAllocationUser MsgApplicationAllocationNoAllocationUser
in if | Just alloc <- x ^? resultAllocation . _entityVal
, x ^. resultHasAllocationUser
-> anchorCell (allocationLink alloc) $ alloc ^. _allocationShorthand
| Just alloc <- x ^? resultAllocation . _entityVal
-> anchorCell (allocationLink alloc) (noAllocationUserShort alloc)
& cellContents . mapped %~ (<> noAllocationUserTooltip)
| otherwise
-> mempty
, anchorColonnadeM (views (resultCourseApplication . _entityKey) applicationLink) $ colApplicationId (resultCourseApplication . _entityKey)
, anchorColonnadeM (views (resultUser . _entityKey) participantLink) $ colUserDisplayName (resultUser . _entityVal . $(multifocusL 2) _userDisplayName _userSurname)
, lmap (view $ resultUser . _entityVal) colUserEmail
, colUserMatriculation (resultUser . _entityVal . _userMatrikelnummer)
, colStudyFeatures resultStudyFeatures
]
, cap (Sortable Nothing $ i18nCell MsgApplicationUserColumns) $ mconcat
[ colApplicationText (resultCourseApplication . _entityVal . _courseApplicationText)
, lmap ((tid, ssh, csh), ) $ colApplicationFiles ($(multifocusG 5) (_1 . _1) (_1 . _2) (_1 . _3) (_2 . resultCourseApplication . _entityKey) (_2 . resultHasFiles))
]
, cap (Sortable Nothing $ i18nCell MsgApplicationRatingColumns) $ mconcat
[ colApplicationVeto (resultCourseApplication . _entityVal . _courseApplicationRatingVeto)
, colApplicationRatingPoints (resultCourseApplication . _entityVal . _courseApplicationRatingPoints)
, colApplicationRatingComment (resultCourseApplication . _entityVal . _courseApplicationRatingComment)
]
]
where generatedColumnsHeader = cell $ i18n MsgApplicationGeneratedColumns <> (messageTooltip =<< messageI Info MsgApplicationGeneratedColumnsTip)
dbtSorting = mconcat
[ singletonMap "participant" . SortColumn $ view queryIsParticipant
, sortAllocationShorthand $ queryAllocation . to (E.?. AllocationShorthand)
, sortUserName' $ $(multifocusG 2) (queryUser . to (E.^. UserDisplayName)) (queryUser . to (E.^. UserSurname))
, uncurry singletonMap . sortUserEmail $ view queryUser
, sortUserMatriculation $ queryUser . to (E.^. UserMatrikelnummer)
, sortApplicationText $ queryCourseApplication . to (E.^. CourseApplicationText)
, sortApplicationFiles queryHasFiles
, sortApplicationVeto $ queryCourseApplication . to (E.^. CourseApplicationRatingVeto)
, sortApplicationRatingPoints $ queryCourseApplication . to (E.^. CourseApplicationRatingPoints)
, sortApplicationRatingComment $ queryCourseApplication . to (E.^. CourseApplicationRatingComment)
]
dbtFilter = mconcat
[ fltrAllocation queryAllocation
, fltrUserName' $ queryUser . to (E.^. UserDisplayName)
, uncurry singletonMap . fltrUserEmail $ view queryUser
, fltrUserMatriculation $ queryUser . to (E.^. UserMatrikelnummer)
, fltrApplicationText $ queryCourseApplication . to (E.^. CourseApplicationText)
, fltrApplicationFiles queryHasFiles
, fltrApplicationVeto $ queryCourseApplication . to (E.^. CourseApplicationRatingVeto)
, fltrApplicationRatingPoints $ queryCourseApplication . to (E.^. CourseApplicationRatingPoints)
, fltrApplicationRatingComment $ queryCourseApplication . to (E.^. CourseApplicationRatingComment)
, fltrRelevantStudyFeaturesTerms (to $
\t -> ( E.val courseTerm
, views queryUser (E.^. UserId) t
))
, fltrRelevantStudyFeaturesDegree (to $
\t -> ( E.val courseTerm
, views queryUser (E.^. UserId) t
))
, fltrRelevantStudyFeaturesSemester (to $
\t -> ( E.val courseTerm
, views queryUser (E.^. UserId) t
))
]
dbtFilterUI = mconcat
[ fltrAllocationUI
, fltrUserNameUI'
, fltrUserMatriculationUI
, fltrUserEmailUI
, fltrApplicationTextUI
, fltrApplicationFilesUI
, fltrApplicationVetoUI
, fltrApplicationRatingPointsUI
, fltrApplicationRatingCommentUI
, fltrRelevantStudyFeaturesTermsUI
, fltrRelevantStudyFeaturesDegreeUI
, fltrRelevantStudyFeaturesSemesterUI
]
dbtStyle = def
{ dbsFilterLayout = defaultDBSFilterLayout
}
dbtParams = def
dbtCsvName = MsgCourseApplicationsTableCsvName tid ssh csh
dbtCsvSheetName = MsgCourseApplicationsTableCsvSheetName tid ssh csh
dbtCsvEncode = simpleCsvEncodeM dbtCsvName dbtCsvSheetName $ CourseApplicationsTableCsv
<$> preview (resultAllocation . _entityVal . _allocationShorthand)
<*> (preview (resultCourseApplication . _entityKey) >>= traverse encrypt)
<*> preview (resultUser . _entityVal . _userDisplayName)
<*> preview (resultUser . _entityVal . _userEmail)
<*> preview (resultUser . _entityVal . _userMatrikelnummer . _Just)
<*> view resultStudyFeatures
<*> preview (resultCourseApplication . _entityVal . _courseApplicationText . _Just)
<*> preview resultHasFiles
<*> preview (resultCourseApplication . _entityVal . _courseApplicationRatingVeto . re _CourseApplicationsTableVeto)
<*> preview (resultCourseApplication . _entityVal . _courseApplicationRatingPoints . _Just)
<*> preview (resultCourseApplication . _entityVal . _courseApplicationRatingComment . _Just)
dbtCsvDecode = Just DBTCsvDecode
{ dbtCsvRowKey = \csv -> do
appRes <- lift $ guessUser csv
case appRes of
Right appId -> return $ E.Value appId
Left uid -> do
alloc <- lift $ guessAllocation csv
[appId] <- lift $ selectKeysList [CourseApplicationUser ==. uid, CourseApplicationAllocation ==. alloc] [LimitTo 2]
return $ E.Value appId
, dbtCsvComputeActions = \case
DBCsvDiffMissing{}
-> return () -- no deletion
DBCsvDiffNew{}
-> return () -- no addition
DBCsvDiffExisting{..} -> do
let appId = dbCsvOld ^. resultCourseApplication . _entityKey
let mVeto = dbCsvNew ^? _csvCAVeto . _Just . _CourseApplicationsTableVeto
whenIsJust mVeto $ \veto ->
when (veto /= dbCsvOld ^. resultCourseApplication . _entityVal . _courseApplicationRatingVeto) $
yield $ CourseApplicationsTableCsvSetVetoData appId veto
when (dbCsvNew ^. _csvCARating /= dbCsvOld ^. resultCourseApplication . _entityVal . _courseApplicationRatingPoints) $
yield $ CourseApplicationsTableCsvSetRatingData appId (dbCsvNew ^. _csvCARating)
when (dbCsvNew ^. _csvCAComment /= dbCsvOld ^. resultCourseApplication . _entityVal . _courseApplicationRatingComment) $
yield $ CourseApplicationsTableCsvSetCommentData appId (dbCsvNew ^. _csvCAComment)
, dbtCsvClassifyAction = \case
CourseApplicationsTableCsvSetVetoData{} -> CourseApplicationsTableCsvSetVeto
CourseApplicationsTableCsvSetRatingData{} -> CourseApplicationsTableCsvSetRating
CourseApplicationsTableCsvSetCommentData{} -> CourseApplicationsTableCsvSetComment
, dbtCsvCoarsenActionClass = const DBCsvActionExisting
, dbtCsvValidateActions = return ()
, dbtCsvExecuteActions = do
C.mapM_ $ \case
CourseApplicationsTableCsvSetVetoData{..} -> do
CourseApplication{..} <- updateGet caCsvActApplication [ CourseApplicationRatingVeto =. caCsvActVeto
, CourseApplicationRatingTime =. Just now
]
audit $ TransactionCourseApplicationEdit cid courseApplicationUser caCsvActApplication
CourseApplicationsTableCsvSetRatingData{..} -> do
CourseApplication{..} <- updateGet caCsvActApplication [ CourseApplicationRatingPoints =. caCsvActRating
, CourseApplicationRatingTime =. Just now
]
audit $ TransactionCourseApplicationEdit cid courseApplicationUser caCsvActApplication
CourseApplicationsTableCsvSetCommentData{..} -> do
CourseApplication{..} <- updateGet caCsvActApplication [ CourseApplicationRatingComment =. caCsvActComment
, CourseApplicationRatingTime =. Just now
]
audit $ TransactionCourseApplicationEdit cid courseApplicationUser caCsvActApplication
return $ CourseR tid ssh csh CApplicationsR
, dbtCsvRenderKey = \(existingApplicantName -> existingApplicantName') -> \case
CourseApplicationsTableCsvSetVetoData{..} ->
[whamlet|
$newline never
^{existingApplicantName' caCsvActApplication}
$if caCsvActVeto
, _{MsgCourseApplicationVeto}
$else
, _{MsgCourseApplicationNoVeto}
|]
CourseApplicationsTableCsvSetRatingData{..} ->
[whamlet|
$newline never
^{existingApplicantName' caCsvActApplication}
$maybe newResult <- caCsvActRating
, _{newResult}
$nothing
, _{MsgCourseApplicationNoRatingPoints}
|]
CourseApplicationsTableCsvSetCommentData{..} ->
[whamlet|
$newline never
^{existingApplicantName' caCsvActApplication}
$if is _Nothing caCsvActComment
, _{MsgCourseApplicationNoRatingComment}
|]
, dbtCsvRenderActionClass = toWidget <=< ap getMessageRender . pure
, dbtCsvRenderException = ap getMessageRender . pure :: CourseApplicationsTableCsvException -> DB Text
}
where
guessUser :: CourseApplicationsTableCsv -> DB (Either UserId CourseApplicationId)
guessUser csv = do
mApp <- runMaybeT $ do
appId <- squash . catchIfMaybeT (const True :: CryptoIDError -> Bool) . MaybeT . traverse decrypt $ csv ^? _csvCAApplication . _Just
CourseApplication{..} <- MaybeT $ get appId
guard $ courseApplicationCourse == cid
return appId
maybe (Left <$> guessUser' csv) (return . Right) mApp
where
guessUser' :: CourseApplicationsTableCsv -> DB UserId
guessUser' CourseApplicationsTableCsv{..} = $cachedHereBinary (csvCAMatriculation, csvCAName) $ do
users <- E.select . E.from $ \user -> do
E.where_ . E.and $ catMaybes
[ (user E.^. UserMatrikelnummer E.==.) . E.val . Just <$> csvCAMatriculation
, (user E.^. UserEmail E.==.) . E.val <$> csvCAEmail
, (user E.^. UserDisplayName E.==.) . E.val <$> csvCAName
]
return $ user E.^. UserId
case users of
[E.Value uid]
-> return uid
_other
-> throwM CourseApplicationsTableCsvExceptionNoMatchingUser
guessAllocation :: CourseApplicationsTableCsv -> DB (Maybe AllocationId)
guessAllocation CourseApplicationsTableCsv{..} = $cachedHereBinary csvCAAllocation . for csvCAAllocation $ \ash -> do
mAlloc <- traverse (getJustEntity . allocationCourseAllocation . entityVal) <=< getBy $ UniqueAllocationCourse cid
case mAlloc of
Just (Entity allocId Allocation{..})
| allocationShorthand == ash
-> return allocId
_other
-> throwM CourseApplicationsTableCsvExceptionNoMatchingAllocation
existingApplicantName :: Map (E.Value CourseApplicationId) CourseApplicationsTableData -> CourseApplicationId -> Widget
existingApplicantName existing (E.Value -> appId) = nameWidget userDisplayName userSurname
where
Entity _ User{..} = existing ^. singular (ix appId . resultUser)
dbtExtraReps = []
dbtIdent = courseApplicationsIdent
psValidator :: PSValidator _ _
psValidator = def
& defaultSorting [SortAscBy "user-name"]
participants <- count [ CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantActive ]
let remainingCapacity = subtract participants <$> courseCapacity
allocationsBounds' <- E.select . E.from $ \(allocation `E.InnerJoin` allocationCourse) -> do
E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation
E.&&. allocationCourse E.^. AllocationCourseCourse E.==. E.val cid
let numApps addWhere = E.subSelectCount . E.from $ \courseApplication -> do
E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. E.val cid
E.&&. courseApplication E.^. CourseApplicationAllocation E.==. E.just (allocationCourse E.^. AllocationCourseAllocation)
addWhere courseApplication
numApps' = numApps . const $ return ()
numFirstChoice = numApps $ \courseApplication ->
E.where_ . E.not_ . E.exists . E.from $ \courseApplication' -> do
E.where_ $ courseApplication E.^. CourseApplicationAllocation E.==. courseApplication' E.^. CourseApplicationAllocation
E.&&. courseApplication E.^. CourseApplicationUser E.==. courseApplication' E.^. CourseApplicationUser
E.where_ . E.not_ $ E.isNothing (courseApplication E.^. CourseApplicationAllocationPriority)
E.||. E.isNothing (courseApplication' E.^. CourseApplicationAllocationPriority)
E.where_ $ courseApplication' E.^. CourseApplicationAllocationPriority E.>. courseApplication E.^. CourseApplicationAllocationPriority
return (allocation, numApps', numFirstChoice)
let
allocationsBounds = [ (allocation, numApps', numFirstChoice', capped)
| (Entity _ allocation, E.Value numApps, E.Value numFirstChoice) <- allocationsBounds'
, let numApps' = max 0 $ maybe id min remainingCapacity numApps
numFirstChoice' = max 0 $ maybe id min remainingCapacity numFirstChoice
capped = numApps' /= numApps
|| numFirstChoice' /= numFirstChoice
]
mayAccept <- hasWriteAccessTo $ CourseR tid ssh csh CAddUserR
(, allocationsBounds, mayAccept) <$> dbTableWidget' psValidator DBTable{..}
now <- liftIO getCurrentTime
let title = prependCourseTitle tid ssh csh MsgCourseApplicationsListTitle
registrationOpen = maybe True (now <)
((acceptRes, acceptWgt'), acceptEnc) <- runFormPost . identifyForm BtnAcceptApplications . renderAForm FormStandard $
(,) <$> apopt (selectField optionsFinite) (fslI MsgAcceptApplicationsMode & setTooltip MsgAcceptApplicationsModeTip) (Just AcceptApplicationsInvite)
<*> apopt (selectField optionsFinite) (fslI MsgAcceptApplicationsSecondary & setTooltip MsgAcceptApplicationsSecondaryTip) (Just AcceptApplicationsSecondaryTime)
let acceptWgt = wrapForm' BtnAcceptApplications acceptWgt' def
{ formSubmit = FormSubmit
, formAction = Just . SomeRoute $ CourseR tid ssh csh CApplicationsR
, formEncoding = acceptEnc
}
when mayAccept $
formResult acceptRes $ \(invMode, appsSecOrder) -> do
runDBJobs $ do
Entity cid Course{..} <- getBy404 $ TermSchoolCourseShort tid ssh csh
participants <- count [ CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantActive ]
let openCapacity = subtract participants <$> courseCapacity
applications <- E.select . E.from $ \(user `E.InnerJoin` application) -> do
E.on $ user E.^. UserId E.==. application E.^. CourseApplicationUser
E.where_ $ application E.^. CourseApplicationCourse E.==. E.val cid
E.&&. E.isNothing (application E.^. CourseApplicationAllocation)
E.&&. E.not_ (application E.^. CourseApplicationRatingVeto)
E.&&. E.maybe E.true (`E.in_` E.valList (filter (view $ passingGrade . _Wrapped) universeF)) (application E.^. CourseApplicationRatingPoints )
E.where_ . E.not_ . E.exists . E.from $ \participant ->
E.where_ $ participant E.^. CourseParticipantCourse E.==. E.val cid
E.&&. participant E.^. CourseParticipantUser E.==. user E.^. UserId
E.&&. participant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
return (user, application)
let
ratingL = _2 . _entityVal . _courseApplicationRatingPoints . to (Down . ExamGradeDefCenter)
cmp = case appsSecOrder of
AcceptApplicationsSecondaryTime
-> comparing . view $ $(multifocusG 2) ratingL (_2 . _entityVal . _courseApplicationTime)
AcceptApplicationsSecondaryRandom
-> comparing $ view ratingL
sortedApplications <- unstableSortBy cmp applications
let applicants = sortedApplications
& nubOrdOn (view $ _1 . _entityKey)
& maybe id take openCapacity
& setOf (case invMode of
AcceptApplicationsDirect -> folded . _1 . _entityKey . to Right
AcceptApplicationsInvite -> folded . _1 . _entityVal . _userEmail . to Left
)
mapM_ addMessage' <=< execWriterT $ registerUsers cid applicants
redirect $ CourseR tid ssh csh CUsersR
let
studyFeaturesWarning = $(i18nWidgetFile "applications-list-info")
siteLayoutMsg title $ do
setTitleI title
$(widgetFile "course/applications-list")

View File

@ -99,19 +99,7 @@ postCCommR tid ssh csh = do
E.&&. user E.^. UserId E.==. corrector E.^. SheetCorrectorUser E.&&. user E.^. UserId E.==. corrector E.^. SheetCorrectorUser
return user return user
) )
] ++ tuts ++ exams ++ sheets ++ ] ++ tuts ++ exams ++ sheets
[ ( RGCourseUnacceptedApplicants
, E.from $ \user -> do
E.where_ . E.exists . E.from $ \courseApplication ->
E.where_ $ courseApplication E.^. CourseApplicationCourse E.==. E.val cid
E.&&. courseApplication E.^. CourseApplicationUser E.==. user E.^. UserId
E.where_ . E.not_ . E.exists . E.from $ \courseParticipant ->
E.where_ $ courseParticipant E.^. CourseParticipantCourse E.==. E.val cid
E.&&. courseParticipant E.^. CourseParticipantUser E.==. user E.^. UserId
E.&&. courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
return user
)
]
, crRecipientAuth = Just $ \uid -> do , crRecipientAuth = Just $ \uid -> do
cID <- encrypt uid cID <- encrypt uid
evalAccessDB (CourseR tid ssh csh $ CUserR cID) False evalAccessDB (CourseR tid ssh csh $ CUserR cID) False

View File

@ -23,14 +23,11 @@ import qualified Data.Map as Map
import qualified Control.Monad.State.Class as State import qualified Control.Monad.State.Class as State
import qualified Database.Esqueleto.Legacy as E import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import Jobs.Queue import Jobs.Queue
import Handler.Course.LecturerInvite import Handler.Course.LecturerInvite
import qualified Data.Conduit.List as C
data CourseForm = CourseForm data CourseForm = CourseForm
{ cfCourseId :: Maybe CourseId { cfCourseId :: Maybe CourseId
@ -43,13 +40,6 @@ data CourseForm = CourseForm
, cfVisFrom :: Maybe UTCTime , cfVisFrom :: Maybe UTCTime
, cfVisTo :: Maybe UTCTime , cfVisTo :: Maybe UTCTime
, cfMatFree :: Bool , cfMatFree :: Bool
, cfAllocation :: Maybe AllocationCourseForm
, cfAppRequired :: Bool
, cfAppInstructions :: Maybe StoredMarkup
, cfAppInstructionFiles :: Maybe FileUploads
, cfAppText :: Bool
, cfAppFiles :: UploadMode
, cfAppRatingsVisible :: Bool
, cfCapacity :: Maybe Int , cfCapacity :: Maybe Int
, cfSecret :: Maybe Text , cfSecret :: Maybe Text
, cfRegFrom :: Maybe UTCTime , cfRegFrom :: Maybe UTCTime
@ -58,18 +48,10 @@ data CourseForm = CourseForm
, cfLecturers :: [Either (UserEmail, Maybe LecturerType) (UserId, LecturerType)] , cfLecturers :: [Either (UserEmail, Maybe LecturerType) (UserId, LecturerType)]
} }
data AllocationCourseForm = AllocationCourseForm
{ acfAllocation :: AllocationId
, acfMinCapacity :: Int
, acfAcceptSubstitutes :: Maybe UTCTime
, acfDeregisterNoShow :: Bool
}
makeLenses_ ''CourseForm makeLenses_ ''CourseForm
makeLenses_ ''AllocationCourseForm
courseToForm :: Entity Course -> [Lecturer] -> Map UserEmail (InvitationDBData Lecturer) -> Maybe (Entity AllocationCourse) -> CourseForm courseToForm :: Entity Course -> [Lecturer] -> Map UserEmail (InvitationDBData Lecturer) -> CourseForm
courseToForm cEnt@(Entity cid Course{..}) lecs lecInvites alloc = CourseForm courseToForm (Entity cid Course{..}) lecs lecInvites = CourseForm
{ cfCourseId = Just cid { cfCourseId = Just cid
, cfName = courseName , cfName = courseName
, cfDesc = courseDescription , cfDesc = courseDescription
@ -80,13 +62,6 @@ courseToForm cEnt@(Entity cid Course{..}) lecs lecInvites alloc = CourseForm
, cfCapacity = courseCapacity , cfCapacity = courseCapacity
, cfSecret = courseRegisterSecret , cfSecret = courseRegisterSecret
, cfMatFree = courseMaterialFree , cfMatFree = courseMaterialFree
, cfAllocation = allocationCourseToForm cEnt <$> alloc
, cfAppRequired = courseApplicationsRequired
, cfAppInstructions = courseApplicationsInstructions
, cfAppInstructionFiles
, cfAppText = courseApplicationsText
, cfAppFiles = courseApplicationsFiles
, cfAppRatingsVisible = courseApplicationsRatingsVisible
, cfVisFrom = courseVisibleFrom , cfVisFrom = courseVisibleFrom
, cfVisTo = courseVisibleTo , cfVisTo = courseVisibleTo
, cfRegFrom = courseRegisterFrom , cfRegFrom = courseRegisterFrom
@ -95,21 +70,8 @@ courseToForm cEnt@(Entity cid Course{..}) lecs lecInvites alloc = CourseForm
, cfLecturers = [Right (lecturerUser, lecturerType) | Lecturer{..} <- lecs] , cfLecturers = [Right (lecturerUser, lecturerType) | Lecturer{..} <- lecs]
++ [Left (email, mType) | (email, InvDBDataLecturer mType) <- Map.toList lecInvites ] ++ [Left (email, mType) | (email, InvDBDataLecturer mType) <- Map.toList lecInvites ]
} }
where
cfAppInstructionFiles = Just . transPipe runDB $ selectAppFiles .| C.map (view $ _entityVal . _FileReference . _1)
where selectAppFiles = E.selectSource . E.from $ \courseAppInstructionFile -> do
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. E.val cid
return courseAppInstructionFile
allocationCourseToForm :: Entity Course -> Entity AllocationCourse -> AllocationCourseForm
allocationCourseToForm (Entity _ Course{..}) (Entity _ AllocationCourse{..}) = AllocationCourseForm
{ acfAllocation = allocationCourseAllocation
, acfMinCapacity = allocationCourseMinCapacity
, acfAcceptSubstitutes = allocationCourseAcceptSubstitutes
, acfDeregisterNoShow = courseDeregisterNoShow
}
makeCourseForm :: (forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)) -> Maybe CourseForm -> Form CourseForm makeCourseForm :: (forall p. PathPiece p => p -> Maybe (SomeRoute UniWorX)) -> Maybe CourseForm -> Form CourseForm
makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB validateCourse $ \html -> do makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB validateCourse $ \html -> do
-- TODO: Refactor to avoid the four repeated calls to liftHandler and three runDBs -- TODO: Refactor to avoid the four repeated calls to liftHandler and three runDBs
@ -211,77 +173,6 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB
, Just . beforeMidnight . termEnd <$> mbLastTerm , Just . beforeMidnight . termEnd <$> mbLastTerm
) )
let
allocationForm :: AForm Handler (Maybe AllocationCourseForm)
allocationForm = wFormToAForm $ do
muid <- maybeAuthId
availableAllocations' <- liftHandler . runDB . E.select . E.from $ \(allocation `E.InnerJoin` term) -> do
E.on $ allocation E.^. AllocationTerm E.==. term E.^. TermId
let alreadyParticipates = flip (maybe E.false) (template >>= cfCourseId) $ \cid ->
E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. E.val cid
E.&&. allocationCourse E.^. AllocationCourseAllocation E.==. allocation E.^. AllocationId
E.where_ $ termIsActiveE (E.val now) (E.val muid) (term E.^. TermId)
E.||. alreadyParticipates
E.||. allocation E.^. AllocationSchool `E.in_` E.valList adminSchools
return (allocation, alreadyParticipates)
let
allocationEnabled :: Entity Allocation -> Bool
allocationEnabled (Entity _ Allocation{..})
= ( NTop allocationStaffRegisterFrom <= NTop (Just now)
&& NTop (Just now) <= NTop allocationStaffRegisterTo
) || allocationSchool `elem` adminSchools
availableAllocations = availableAllocations' ^.. folded . filtered (allocationEnabled . view _1) . _1
activeAllocations = availableAllocations' ^.. folded . filtered ((&&) <$> (not <$> allocationEnabled . view _1) <*> view (_2 . _Value)) . _1
mkAllocationOption :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX) => Entity Allocation -> m (Option AllocationId)
mkAllocationOption (Entity aId Allocation{..}) = liftHandler $ do
cID <- encrypt aId :: Handler CryptoUUIDAllocation
return . Option (mr . MsgCourseAllocationOption (mr . ShortTermIdentifier $ unTermKey allocationTerm) $ CI.original allocationName) aId $ toPathPiece cID
currentAllocationAvailable = (\alloc -> any ((== alloc) . entityKey) availableAllocations) . acfAllocation <$> (template >>= cfAllocation)
case (currentAllocationAvailable, availableAllocations) of
(Nothing, []) -> wforced (convertField (const Nothing) (const False) checkBoxField) (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseNoAllocationsAvailable) Nothing
_ -> do
allocationOptions <- mkOptionList <$> mapM mkAllocationOption (availableAllocations ++ activeAllocations)
let
explainedAllocationOptions = return allocationOptions `explainOptionList` \allocId -> hoistMaybe . listToMaybe $ do
(Entity allocId' Allocation{..}, _) <- availableAllocations'
guard $ allocId' == allocId
toWidget <$> hoistMaybe allocationStaffDescription
doExplain = has (folded . _entityVal . _allocationStaffDescription . _Just) $ availableAllocations ++ activeAllocations
allocField | doExplain = explainedSelectionField Nothing explainedAllocationOptions
| otherwise = selectField' Nothing $ return allocationOptions
userAdmin = not $ null adminSchools
mayChange = Just False /= fmap (|| userAdmin) currentAllocationAvailable
allocationForm' =
let ainp :: Field Handler a -> FieldSettings UniWorX -> Maybe a -> AForm Handler a
ainp
| mayChange
= apreq
| otherwise
= aforcedJust
in AllocationCourseForm
<$> ainp allocField (fslI MsgCourseAllocation) (fmap acfAllocation $ template >>= cfAllocation)
<*> ainp (natFieldI MsgCourseAllocationMinCapacityMustBeNonNegative) (fslI MsgCourseAllocationMinCapacity & setTooltip MsgCourseAllocationMinCapacityTip) (fmap acfMinCapacity $ template >>= cfAllocation)
<*> aopt utcTimeField (fslI MsgCourseAcceptSubstitutesUntil & setTooltip MsgCourseAcceptSubstitutesUntilTip) (fmap acfAcceptSubstitutes $ template >>= cfAllocation)
<*> apopt checkBoxField (fslI MsgCourseDeregisterNoShow & setTooltip MsgCourseDeregisterNoShowTip) ((<|> Just True) . fmap acfDeregisterNoShow $ template >>= cfAllocation)
let
addTip :: (MonadHandler m, HandlerSite m ~ UniWorX)
=> FieldView UniWorX -> m (FieldView UniWorX)
addTip = addFieldViewTooltipWidget $(i18nWidgetFile "allocation-participate-tip")
hoist (censorM $ traverseOf _head addTip) $ optionalActionW' (bool mforcedJust mpopt mayChange) allocationForm' (fslI MsgCourseAllocationParticipate) (is _Just . cfAllocation <$> template)
multipleSchoolsMsg <- messageI Warning MsgCourseSchoolMultipleTip multipleSchoolsMsg <- messageI Warning MsgCourseSchoolMultipleTip
multipleTermsMsg <- messageI Warning MsgCourseSemesterMultipleTip multipleTermsMsg <- messageI Warning MsgCourseSemesterMultipleTip
@ -305,13 +196,6 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB
& setTooltip MsgCourseVisibleToTip) (cfVisTo <$> template) & setTooltip MsgCourseVisibleToTip) (cfVisTo <$> template)
<*> apopt checkBoxField (fslI MsgCourseMaterialFree) (cfMatFree <$> template) <*> apopt checkBoxField (fslI MsgCourseMaterialFree) (cfMatFree <$> template)
<* aformSection MsgCourseFormSectionRegistration <* aformSection MsgCourseFormSectionRegistration
<*> allocationForm
<*> apopt checkBoxField (fslI MsgCourseApplicationRequired & setTooltip MsgCourseApplicationRequiredTip) (cfAppRequired <$> template)
<*> aopt htmlField (fslI MsgCourseApplicationInstructions & setTooltip MsgCourseApplicationInstructionsTip) (cfAppInstructions <$> template)
<*> aopt (multiFileField' . maybeVoid $ cfAppInstructionFiles =<< template) (fslI MsgCourseApplicationTemplate & setTooltip MsgCourseApplicationTemplateTip) (cfAppInstructionFiles <$> template)
<*> apopt checkBoxField (fslI MsgCourseApplicationsText & setTooltip MsgCourseApplicationsTextTip) (cfAppText <$> template)
<*> uploadModeForm (fslI MsgCourseApplicationsFiles & setTooltip MsgCourseApplicationsFilesTip) (fmap cfAppFiles template <|> pure NoUpload)
<*> apopt checkBoxField (fslI MsgCourseApplicationRatingsVisible & setTooltip MsgCourseApplicationRatingsVisibleTip) (cfAppRatingsVisible <$> template)
<*> aopt (natFieldI MsgCourseCapacity) (fslI MsgCourseCapacity <*> aopt (natFieldI MsgCourseCapacity) (fslI MsgCourseCapacity
& setTooltip MsgCourseCapacityTip) (cfCapacity <$> template) & setTooltip MsgCourseCapacityTip) (cfCapacity <$> template)
<*> aopt (textField & cfStrip) (fslpI MsgCourseSecret (mr MsgCourseSecretFormat) <*> aopt (textField & cfStrip) (fslpI MsgCourseSecret (mr MsgCourseSecretFormat)
@ -331,31 +215,8 @@ validateCourse :: FormValidator CourseForm (YesodDB UniWorX) ()
validateCourse = do validateCourse = do
CourseForm{..} <- State.get CourseForm{..} <- State.get
now <- liftIO getCurrentTime
uid <- liftHandler requireAuthId uid <- liftHandler requireAuthId
userAdmin <- lift . hasWriteAccessTo $ SchoolR cfSchool SchoolEditR userAdmin <- lift . hasWriteAccessTo $ SchoolR cfSchool SchoolEditR
newAllocationTerm <- for (acfAllocation <$> cfAllocation) $ lift . fmap allocationTerm . getJust
prevAllocationCourse <- join <$> traverse (lift . getBy . UniqueAllocationCourse) cfCourseId
prevAllocation <- fmap join . traverse (lift . getEntity) $ allocationCourseAllocation . entityVal <$> prevAllocationCourse
oldAllocatedCapacity <- if
| Just (Entity _ Allocation{..}) <- prevAllocation
, Just (Entity _ AllocationCourse{..}) <- prevAllocationCourse
, NTop allocationStaffAllocationTo <= NTop (Just now)
, NTop allocationRegisterByCourse > NTop (Just now)
-> lift $ Just . courseCapacity <$> getJust allocationCourseCourse
| otherwise
-> return Nothing
let oldAllocation = do
Entity allocId Allocation{..} <- prevAllocation
guard $ NTop (Just now) > NTop allocationStaffRegisterTo
pure $ Just allocId
oldAllocatedMinCapacity = do
Entity _ Allocation{..} <- prevAllocation
Entity _ AllocationCourse{..} <- prevAllocationCourse
guard $ NTop (Just now) > NTop allocationStaffRegisterTo
pure $ Just allocationCourseMinCapacity
guardValidation MsgCourseVisibilityEndMustBeAfterStart guardValidation MsgCourseVisibilityEndMustBeAfterStart
$ NTop cfVisFrom <= NTop cfVisTo $ NTop cfVisFrom <= NTop cfVisTo
@ -363,29 +224,15 @@ validateCourse = do
$ NTop cfRegFrom <= NTop cfRegTo $ NTop cfRegFrom <= NTop cfRegTo
guardValidation MsgCourseDeregistrationEndMustBeAfterStart guardValidation MsgCourseDeregistrationEndMustBeAfterStart
$ Just False /= ((<=) <$> cfRegFrom <*> cfDeRegUntil) $ Just False /= ((<=) <$> cfRegFrom <*> cfDeRegUntil)
guardValidation MsgCourseAllocationRequiresCapacity
$ is _Nothing cfAllocation || is _Just cfCapacity
guardValidation MsgCourseAllocationTermMustMatch
$ maybe True (== cfTerm) newAllocationTerm
unless userAdmin $ do unless userAdmin $ do
guardValidation MsgCourseUserMustBeLecturer guardValidation MsgCourseUserMustBeLecturer
$ anyOf (traverse . _Right . _1) (== uid) cfLecturers $ anyOf (traverse . _Right . _1) (== uid) cfLecturers
guardValidation MsgCourseAllocationCapacityMayNotBeChanged
$ maybe True (== cfCapacity) oldAllocatedCapacity
guardValidation MsgAllocationStaffRegisterToExpiredAllocation
$ maybe True (== fmap acfAllocation cfAllocation) oldAllocation
guardValidation MsgAllocationStaffRegisterToExpiredMinCapacity
$ maybe True (== fmap acfMinCapacity cfAllocation) oldAllocatedMinCapacity
warnValidation MsgCourseShorthandTooLong warnValidation MsgCourseShorthandTooLong
$ length (CI.original cfShort) <= 10 $ length (CI.original cfShort) <= 10
warnValidation MsgCourseNotAlwaysVisibleDuringRegistration warnValidation MsgCourseNotAlwaysVisibleDuringRegistration
$ NTop cfVisFrom <= NTop cfRegFrom && NTop cfRegTo <= NTop cfVisTo $ NTop cfVisFrom <= NTop cfRegFrom && NTop cfRegTo <= NTop cfVisTo
warnValidation MsgCourseApplicationInstructionsRecommended
$ (is _Just cfAppInstructions || is _Just cfAppInstructionFiles)
|| not (cfAppText || isn't _NoUpload cfAppFiles)
getCourseNewR :: Handler Html -- call via toTextUrl getCourseNewR :: Handler Html -- call via toTextUrl
getCourseNewR = do getCourseNewR = do
@ -434,7 +281,7 @@ getCourseNewR = do
return course return course
template <- case listToMaybe oldCourses of template <- case listToMaybe oldCourses of
(Just oldTemplate) -> (Just oldTemplate) ->
let newTemplate = courseToForm oldTemplate mempty mempty Nothing in let newTemplate = courseToForm oldTemplate mempty mempty in
return $ Just $ newTemplate return $ Just $ newTemplate
{ cfCourseId = Nothing { cfCourseId = Nothing
, cfTerm = TermKey $ termFromRational 0 -- invalid, will be ignored; undefined won't work due to strictness , cfTerm = TermKey $ termFromRational 0 -- invalid, will be ignored; undefined won't work due to strictness
@ -467,11 +314,10 @@ pgCEditR tid ssh csh = do
mbCourse <- getBy (TermSchoolCourseShort tid ssh csh) mbCourse <- getBy (TermSchoolCourseShort tid ssh csh)
mbLecs <- for mbCourse $ \course -> map entityVal <$> selectList [LecturerCourse ==. entityKey course] [Asc LecturerType] mbLecs <- for mbCourse $ \course -> map entityVal <$> selectList [LecturerCourse ==. entityKey course] [Asc LecturerType]
mbLecInvites <- for mbCourse $ sourceInvitationsF . entityKey mbLecInvites <- for mbCourse $ sourceInvitationsF . entityKey
mbAllocation <- for mbCourse $ \course -> getBy . UniqueAllocationCourse $ entityKey course return $ (,,) <$> mbCourse <*> mbLecs <*> mbLecInvites
return $ (,,,) <$> mbCourse <*> mbLecs <*> mbLecInvites <*> mbAllocation
-- IMPORTANT: both GET and POST Handler must use the same template, -- IMPORTANT: both GET and POST Handler must use the same template,
-- since an Edit is identified via CourseID, which is not embedded in the received form data for security reasons. -- since an Edit is identified via CourseID, which is not embedded in the received form data for security reasons.
courseEditHandler (\p -> Just . SomeRoute $ CourseR tid ssh csh CEditR :#: p) $ $(uncurryN 4) courseToForm <$> courseData courseEditHandler (\p -> Just . SomeRoute $ CourseR tid ssh csh CEditR :#: p) $ $(uncurryN 3) courseToForm <$> courseData
-- | Course Creation and Editing -- | Course Creation and Editing
@ -501,24 +347,17 @@ courseEditHandler miButtonAction mbCourseForm = do
, courseCapacity = cfCapacity , courseCapacity = cfCapacity
, courseRegisterSecret = cfSecret , courseRegisterSecret = cfSecret
, courseMaterialFree = cfMatFree , courseMaterialFree = cfMatFree
, courseApplicationsRequired = cfAppRequired
, courseApplicationsInstructions = cfAppInstructions
, courseApplicationsText = cfAppText
, courseApplicationsFiles = cfAppFiles
, courseApplicationsRatingsVisible = cfAppRatingsVisible
, courseVisibleFrom = cfVisFrom , courseVisibleFrom = cfVisFrom
, courseVisibleTo = cfVisTo , courseVisibleTo = cfVisTo
, courseRegisterFrom = cfRegFrom , courseRegisterFrom = cfRegFrom
, courseRegisterTo = cfRegTo , courseRegisterTo = cfRegTo
, courseDeregisterUntil = cfDeRegUntil , courseDeregisterUntil = cfDeRegUntil
, courseDeregisterNoShow = maybe False acfDeregisterNoShow cfAllocation
} }
whenIsJust insertOkay $ \cid -> do whenIsJust insertOkay $ \cid -> do
let (invites, adds) = partitionEithers $ cfLecturers res let (invites, adds) = partitionEithers $ cfLecturers res
insertMany_ $ map (\(lid, lty) -> Lecturer lid cid lty) adds insertMany_ $ map (\(lid, lty) -> Lecturer lid cid lty) adds
sinkInvitationsF lecturerInvitationConfig $ map (\(lEmail, mLty) -> (lEmail, cid, (InvDBDataLecturer mLty, InvTokenDataLecturer))) invites sinkInvitationsF lecturerInvitationConfig $ map (\(lEmail, mLty) -> (lEmail, cid, (InvDBDataLecturer mLty, InvTokenDataLecturer))) invites
insert_ $ CourseEdit aid now cid insert_ $ CourseEdit aid now cid
upsertAllocationCourse cid $ cfAllocation res
memcachedByInvalidate AuthCacheLecturerList $ Proxy @(Set UserId) memcachedByInvalidate AuthCacheLecturerList $ Proxy @(Set UserId)
return insertOkay return insertOkay
case insertOkay of case insertOkay of
@ -552,17 +391,11 @@ courseEditHandler miButtonAction mbCourseForm = do
, courseCapacity = cfCapacity , courseCapacity = cfCapacity
, courseRegisterSecret = cfSecret , courseRegisterSecret = cfSecret
, courseMaterialFree = cfMatFree , courseMaterialFree = cfMatFree
, courseApplicationsRequired = cfAppRequired
, courseApplicationsInstructions = cfAppInstructions
, courseApplicationsText = cfAppText
, courseApplicationsFiles = cfAppFiles
, courseApplicationsRatingsVisible = cfAppRatingsVisible
, courseVisibleFrom = cfVisFrom , courseVisibleFrom = cfVisFrom
, courseVisibleTo = cfVisTo , courseVisibleTo = cfVisTo
, courseRegisterFrom = cfRegFrom , courseRegisterFrom = cfRegFrom
, courseRegisterTo = cfRegTo , courseRegisterTo = cfRegTo
, courseDeregisterUntil = cfDeRegUntil , courseDeregisterUntil = cfDeRegUntil
, courseDeregisterNoShow = maybe False acfDeregisterNoShow cfAllocation
} }
case updOkay of case updOkay of
(Just _) -> addMessageI Warning (MsgCourseEditDupShort tid ssh csh) $> False (Just _) -> addMessageI Warning (MsgCourseEditDupShort tid ssh csh) $> False
@ -575,11 +408,6 @@ courseEditHandler miButtonAction mbCourseForm = do
insert_ $ CourseEdit aid now cid insert_ $ CourseEdit aid now cid
let mkFilter CourseAppInstructionFileResidual{..} = [ CourseAppInstructionFileCourse ==. courseAppInstructionFileResidualCourse ]
in void . replaceFileReferences mkFilter (CourseAppInstructionFileResidual cid) . sequence_ $ cfAppInstructionFiles res
upsertAllocationCourse cid $ cfAllocation res
memcachedByInvalidate AuthCacheLecturerList $ Proxy @(Set UserId) memcachedByInvalidate AuthCacheLecturerList $ Proxy @(Set UserId)
addMessageI Success $ MsgCourseEditOk tid ssh csh addMessageI Success $ MsgCourseEditOk tid ssh csh
@ -592,26 +420,3 @@ courseEditHandler miButtonAction mbCourseForm = do
{ formAction = Just $ SomeRoute actionUrl { formAction = Just $ SomeRoute actionUrl
, formEncoding = formEnctype , formEncoding = formEnctype
} }
upsertAllocationCourse :: CourseId -> Maybe AllocationCourseForm -> YesodJobDB UniWorX ()
upsertAllocationCourse cid = \case
Just AllocationCourseForm{..} -> do
prevAllocationCourse <- getBy $ UniqueAllocationCourse cid
void $ upsert AllocationCourse
{ allocationCourseAllocation = acfAllocation
, allocationCourseCourse = cid
, allocationCourseMinCapacity = acfMinCapacity
, allocationCourseAcceptSubstitutes = acfAcceptSubstitutes
, allocationCourseOverrideSumCapacity = Nothing
}
[ AllocationCourseAllocation =. acfAllocation
, AllocationCourseCourse =. cid
, AllocationCourseMinCapacity =. acfMinCapacity
, AllocationCourseAcceptSubstitutes =. acfAcceptSubstitutes
]
when (Just acfAllocation /= fmap (allocationCourseAllocation . entityVal) prevAllocationCourse) $
queueDBJob . JobQueueNotification $ NotificationAllocationNewCourse acfAllocation cid
Nothing ->
deleteWhere [ AllocationCourseCourse ==. cid ]

View File

@ -18,23 +18,17 @@ import Data.Maybe (fromJust)
import Utils.Course import Utils.Course
import Utils.Form import Utils.Form
-- import Utils.DB
import Handler.Utils hiding (colSchoolShort) import Handler.Utils hiding (colSchoolShort)
import qualified Database.Esqueleto.Legacy as E import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E import qualified Database.Esqueleto.Utils as E
import qualified Data.CaseInsensitive as CI
import qualified Data.Text as Text
import qualified Data.Set as Set
type CourseTableData = DBRow type CourseTableData = DBRow
( Entity Course ( Entity Course
, Bool -- isRegistered , Bool -- isRegistered
, Entity School , Entity School
, [Entity User] , [Entity User]
, Maybe (Entity Allocation)
, Bool -- mayEditCourse , Bool -- mayEditCourse
) )
@ -50,24 +44,17 @@ resultIsRegistered = _dbrOutput . _2
resultLecturers :: Traversal' CourseTableData (Entity User) resultLecturers :: Traversal' CourseTableData (Entity User)
resultLecturers = _dbrOutput . _4 . traverse resultLecturers = _dbrOutput . _4 . traverse
resultAllocation :: Traversal' CourseTableData (Entity Allocation)
resultAllocation = _dbrOutput . _5 . _Just
resultMayEditCourse :: Lens' CourseTableData Bool resultMayEditCourse :: Lens' CourseTableData Bool
resultMayEditCourse = _dbrOutput . _6 resultMayEditCourse = _dbrOutput . _5
type CourseTableExpr = (E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity School)) type CourseTableExpr = (E.SqlExpr (Entity Course) `E.InnerJoin` E.SqlExpr (Entity School))
`E.LeftOuterJoin` E.SqlExpr (Maybe (Entity Allocation))
queryCourse :: IndexPreservingGetter CourseTableExpr (E.SqlExpr (Entity Course)) queryCourse :: IndexPreservingGetter CourseTableExpr (E.SqlExpr (Entity Course))
queryCourse = to $ $(E.sqlIJproj 2 1) . $(E.sqlLOJproj 2 1) queryCourse = to $(E.sqlIJproj 2 1)
querySchool :: IndexPreservingGetter CourseTableExpr (E.SqlExpr (Entity School)) querySchool :: IndexPreservingGetter CourseTableExpr (E.SqlExpr (Entity School))
querySchool = to $ $(E.sqlIJproj 2 2) . $(E.sqlLOJproj 2 1) querySchool = to $(E.sqlIJproj 2 2)
queryAllocation :: IndexPreservingGetter CourseTableExpr (E.SqlExpr (Maybe (Entity Allocation)))
queryAllocation = to $(E.sqlLOJproj 2 2)
queryParticipants :: IndexPreservingGetter CourseTableExpr (E.SqlExpr (E.Value Int)) queryParticipants :: IndexPreservingGetter CourseTableExpr (E.SqlExpr (E.Value Int))
queryParticipants = queryCourse . to (E.^. CourseId) . to numCourseParticipants queryParticipants = queryCourse . to (E.^. CourseId) . to numCourseParticipants
@ -83,9 +70,7 @@ queryMayViewCourse :: Maybe UserId
-> IndexPreservingGetter CourseTableExpr (E.SqlExpr (E.Value Bool)) -> IndexPreservingGetter CourseTableExpr (E.SqlExpr (E.Value Bool))
queryMayViewCourse muid ata now = to . runReader $ do queryMayViewCourse muid ata now = to . runReader $ do
course <- view queryCourse course <- view queryCourse
allocation <- view queryAllocation return $ mayViewCourse muid ata now course
return $ mayViewCourse muid ata now course (allocation E.?. AllocationId)
queryIsEditor :: Maybe UserId queryIsEditor :: Maybe UserId
-> AuthTagActive -> AuthTagActive
@ -123,21 +108,6 @@ colRegistered :: IsDBTable m a => Colonnade Sortable CourseTableData (DBCell m a
colRegistered = sortable (Just "registered") (i18nCell MsgFilterRegistered) $ views resultIsRegistered tickmarkCell colRegistered = sortable (Just "registered") (i18nCell MsgFilterRegistered) $ views resultIsRegistered tickmarkCell
data AllocationSearch
= AllocationSearchNoAllocation
| AllocationSearchMatch TermId SchoolId AllocationShorthand
deriving (Eq, Ord, Read, Show, Generic, Typeable)
instance PathPiece AllocationSearch where
toPathPiece AllocationSearchNoAllocation = "no-allocation"
toPathPiece (AllocationSearchMatch tid ssh ash) = pathPieceJoined "-" # [toPathPiece tid, toPathPiece ssh, toPathPiece ash]
fromPathPiece t
| CI.mk (Text.strip t) == "no-allocation" = pure AllocationSearchNoAllocation
| Just [tid, ssh, ash] <- t ^? pathPieceJoined "-"
= AllocationSearchMatch <$> fromPathPiece tid <*> fromPathPiece ssh <*> fromPathPiece ash
| otherwise = mzero
makeCourseTable :: (ToSortable h, Functor h) makeCourseTable :: (ToSortable h, Functor h)
=> Colonnade h CourseTableData (DBCell Handler ()) -> PSValidator Handler () -> DB Widget => Colonnade h CourseTableData (DBCell Handler ()) -> PSValidator Handler () -> DB Widget
makeCourseTable colChoices psValidator' = do makeCourseTable colChoices psValidator' = do
@ -148,45 +118,17 @@ makeCourseTable colChoices psValidator' = do
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
ata <- getSessionActiveAuthTags ata <- getSessionActiveAuthTags
MsgRenderer mr <- getMsgRenderer
let allocSearchNoAllocOption = Option
{ optionDisplay = mr MsgFilterCourseAllocationNone
, optionInternalValue
, optionExternalValue = toPathPiece optionInternalValue
}
where optionInternalValue = AllocationSearchNoAllocation
allocationSearchOptions <- mkOptionList . (allocSearchNoAllocOption :) <$> do
allocs <- E.select . E.from $ \allocation -> do
E.orderBy [ E.desc $ allocation E.^. AllocationTerm
, E.asc $ allocation E.^. AllocationSchool
, E.asc $ allocation E.^. AllocationName
]
return ( allocation E.^. AllocationTerm, allocation E.^. AllocationSchool, allocation E.^. AllocationShorthand
, allocation E.^. AllocationName
)
return . flip map allocs $ \(E.Value tid, E.Value ssh, E.Value ash, E.Value aname)
-> let optionInternalValue = AllocationSearchMatch tid ssh ash
in Option
{ optionDisplay = mr $ MsgFilterCourseAllocationOption tid ssh aname
, optionInternalValue
, optionExternalValue = toPathPiece optionInternalValue
}
let dbtSQLQuery :: CourseTableExpr -> E.SqlQuery _ let dbtSQLQuery :: CourseTableExpr -> E.SqlQuery _
dbtSQLQuery = runReaderT $ do dbtSQLQuery = runReaderT $ do
course <- view queryCourse course <- view queryCourse
school <- view querySchool school <- view querySchool
allocation <- view queryAllocation
lift . E.on . E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. allocation E.?. AllocationId E.==. E.just (allocationCourse E.^. AllocationCourseAllocation)
lift . E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId lift . E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
registered <- view $ queryIsRegistered muid ata registered <- view $ queryIsRegistered muid ata
isEditor <- view $ queryIsEditor muid ata isEditor <- view $ queryIsEditor muid ata
return (course, registered, school, allocation, isEditor) return (course, registered, school, isEditor)
lecturerQuery cid (user `E.InnerJoin` lecturer) = do lecturerQuery cid (user `E.InnerJoin` lecturer) = do
E.on $ user E.^. UserId E.==. lecturer E.^. LecturerUser E.on $ user E.^. UserId E.==. lecturer E.^. LecturerUser
E.where_ $ cid E.==. lecturer E.^. LecturerCourse E.where_ $ cid E.==. lecturer E.^. LecturerCourse
@ -197,9 +139,9 @@ makeCourseTable colChoices psValidator' = do
E.where_ $ cid E.==. lecturer E.^. LecturerCourse E.where_ $ cid E.==. lecturer E.^. LecturerCourse
return user return user
dbtProj :: _ CourseTableData dbtProj :: _ CourseTableData
dbtProj = dbtProjSimple $ \(course, E.Value registered, school, allocation, E.Value isEditor) -> do dbtProj = dbtProjSimple $ \(course, E.Value registered, school, E.Value isEditor) -> do
lecturerList <- E.select $ E.from $ lecturerQuery $ E.val $ entityKey course lecturerList <- E.select $ E.from $ lecturerQuery $ E.val $ entityKey course
return (course, registered, school, lecturerList, allocation, isEditor) return (course, registered, school, lecturerList, isEditor)
dbTableWidget' psValidator DBTable dbTableWidget' psValidator DBTable
{ dbtSQLQuery { dbtSQLQuery
@ -229,25 +171,13 @@ makeCourseTable colChoices psValidator' = do
E.where_ $ user E.^. UserDisplayName `E.hasInfix` E.val c E.where_ $ user E.^. UserDisplayName `E.hasInfix` E.val c
, singletonMap "openregistration" . FilterColumn . E.mkExactFilterLast . runReader $ do , singletonMap "openregistration" . FilterColumn . E.mkExactFilterLast . runReader $ do
course <- view queryCourse course <- view queryCourse
allocation <- view queryAllocation
let regTo = course E.^. CourseRegisterTo let regTo = course E.^. CourseRegisterTo
regFrom = course E.^. CourseRegisterFrom regFrom = course E.^. CourseRegisterFrom
courseOpen = E.maybe E.false (\f -> f E.<=. E.val now) regFrom courseOpen = E.maybe E.false (\f -> f E.<=. E.val now) regFrom
E.&&. E.maybe E.true (\t -> E.val now E.<=. t) regTo E.&&. E.maybe E.true (\t -> E.val now E.<=. t) regTo
allocOpen = ( E.maybe E.false (\f -> f E.<=. E.val now) (E.joinV $ allocation E.?. AllocationRegisterFrom)
E.&&. E.maybe E.true (\t -> E.val now E.<=. t) (E.joinV $ allocation E.?. AllocationRegisterTo)
)
E.||. ( courseOpen
E.&&. E.maybe E.false (\f -> f E.<=. E.val now) (E.joinV $ allocation E.?. AllocationRegisterByCourse)
)
return $ ( courseOpen return courseOpen
E.&&. E.isNothing (allocation E.?. AllocationId)
)
E.||. ( allocOpen
E.&&. E.isJust (allocation E.?. AllocationId)
)
, singletonMap "registered" . FilterColumn . E.mkExactFilterLast . view $ queryIsRegistered muid ata , singletonMap "registered" . FilterColumn . E.mkExactFilterLast . view $ queryIsRegistered muid ata
, singletonMap "search" . FilterColumn $ \(view queryCourse -> course) criterion -> case getLast (criterion :: Last Text) of , singletonMap "search" . FilterColumn $ \(view queryCourse -> course) criterion -> case getLast (criterion :: Last Text) of
Nothing -> E.val True :: E.SqlExpr (E.Value Bool) Nothing -> E.val True :: E.SqlExpr (E.Value Bool)
@ -260,14 +190,6 @@ makeCourseTable colChoices psValidator' = do
, singletonMap "search-title" . FilterColumn $ \(view queryCourse -> course) criterion -> case getLast (criterion :: Last Text) of , singletonMap "search-title" . FilterColumn $ \(view queryCourse -> course) criterion -> case getLast (criterion :: Last Text) of
Nothing -> E.val True :: E.SqlExpr (E.Value Bool) Nothing -> E.val True :: E.SqlExpr (E.Value Bool)
Just needle -> E.castString (course E.^. CourseName) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%) Just needle -> E.castString (course E.^. CourseName) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%)
, singletonMap "allocation" . FilterColumn $ \row (criteria :: Set AllocationSearch) -> if
| Set.null criteria -> E.true
| otherwise -> flip E.any criteria $ \case
AllocationSearchNoAllocation -> E.isNothing $ view queryAllocation row E.?. AllocationId
AllocationSearchMatch tid ssh ash
-> view queryAllocation row E.?. AllocationTerm E.==. E.justVal tid
E.&&. view queryAllocation row E.?. AllocationSchool E.==. E.justVal ssh
E.&&. view queryAllocation row E.?. AllocationShorthand E.==. E.justVal ash
] ]
, dbtFilterUI = \mPrev -> mconcat $ catMaybes , dbtFilterUI = \mPrev -> mconcat $ catMaybes
[ pure $ prismAForm (singletonFilter "term" . maybePrism _PathPiece) mPrev $ aopt (hoistField lift termField) (fslI MsgTableTerm) [ pure $ prismAForm (singletonFilter "term" . maybePrism _PathPiece) mPrev $ aopt (hoistField lift termField) (fslI MsgTableTerm)
@ -279,11 +201,10 @@ makeCourseTable colChoices psValidator' = do
, pure $ prismAForm (singletonFilter "openregistration" . maybePrism _PathPiece) mPrev $ fmap (\x -> if isJust x && not (fromJust x) then Nothing else x) . aopt checkBoxField (fslI MsgFilterCourseRegisterOpen) , pure $ prismAForm (singletonFilter "openregistration" . maybePrism _PathPiece) mPrev $ fmap (\x -> if isJust x && not (fromJust x) then Nothing else x) . aopt checkBoxField (fslI MsgFilterCourseRegisterOpen)
, guardOn (is _Just muid) , guardOn (is _Just muid)
$ prismAForm (singletonFilter "registered" . maybePrism _PathPiece) mPrev (aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgFilterCourseRegistered)) $ prismAForm (singletonFilter "registered" . maybePrism _PathPiece) mPrev (aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgFilterCourseRegistered))
, pure $ prismAForm (singletonFilter "allocation" . maybePrism _PathPiece) mPrev $ aopt (selectField' (Just $ SomeMessage MsgTableNoFilter) $ return allocationSearchOptions) (fslI MsgFilterCourseAllocation)
] ]
, dbtStyle = def , dbtStyle = def
{ dbsFilterLayout = defaultDBSFilterLayout { dbsFilterLayout = defaultDBSFilterLayout
, dbsTemplate = DBSTCourse resultCourse resultLecturers resultIsRegistered resultSchool resultAllocation resultMayEditCourse , dbsTemplate = DBSTCourse resultCourse resultLecturers resultIsRegistered resultSchool resultMayEditCourse
} }
, dbtParams = def , dbtParams = def
, dbtIdent = "courses" :: Text , dbtIdent = "courses" :: Text

View File

@ -1,115 +1,112 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de> -- SPDX-FileCopyrightText: 2022 Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
-- --
-- SPDX-License-Identifier: AGPL-3.0-or-later -- SPDX-License-Identifier: AGPL-3.0-or-later
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Handler.Course.ParticipantInvite module Handler.Course.ParticipantInvite
( InvitableJunction(..), InvitationDBData(..), InvitationTokenData(..) ( getCAddUserR, postCAddUserR
, getCInviteR, postCInviteR
, getCAddUserR, postCAddUserR
, AddParticipantsResult(..)
, addParticipantsResultMessages
, registerUsers, registerUser
, registerUsers', registerUser'
) where ) where
import Import import Import
import Utils.Form
import Handler.Utils import Handler.Utils
import Handler.Utils.Invitations import Handler.Utils.Avs
import Handler.Utils.Course
import qualified Data.CaseInsensitive as CI
import qualified Data.Set as Set
import qualified Data.Map as Map
import Jobs.Queue import Jobs.Queue
import Data.Aeson hiding (Result(..)) import qualified Data.Aeson as Aeson
import qualified Data.CaseInsensitive as CI
import Data.Map ((!))
import qualified Data.Map as Map
import qualified Data.Text as Text
import qualified Data.Time.Zones as TZ
import qualified Data.Set as Set
import Control.Monad.Except (MonadError(..)) import Control.Monad.Except (MonadError(..))
import Generics.Deriving.Monoid (memptydefault, mappenddefault) import Generics.Deriving.Monoid (memptydefault, mappenddefault)
import qualified Data.HashSet as HashSet
type UserSearchKey = Text
type TutorialIdent = CI Text
-- Invitations for ordinary participants of this course data ButtonCourseRegisterMode = BtnCourseRegisterConfirm | BtnCourseRegisterAbort
instance IsInvitableJunction CourseParticipant where deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
type InvitationFor CourseParticipant = Course instance Universe ButtonCourseRegisterMode
data InvitableJunction CourseParticipant = JunctionParticipant instance Finite ButtonCourseRegisterMode
{ jParticipantRegistration :: UTCTime
, jParticipantAllocated :: Maybe AllocationId
, jParticipantState :: CourseParticipantState
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
data InvitationDBData CourseParticipant = InvDBDataParticipant
-- no data needed in DB to manage participant invitation
deriving (Eq, Ord, Read, Show, Generic, Typeable)
data InvitationTokenData CourseParticipant = InvTokenDataParticipant
{ invTokenParticipantSubmissionGroup :: Maybe SubmissionGroupName
}
deriving (Eq, Ord, Read, Show, Generic, Typeable)
_InvitableJunction = iso embedRenderMessage ''UniWorX ''ButtonCourseRegisterMode id
(\CourseParticipant{..} -> (courseParticipantUser, courseParticipantCourse, JunctionParticipant courseParticipantRegistration courseParticipantAllocated courseParticipantState))
(\(courseParticipantUser, courseParticipantCourse, JunctionParticipant courseParticipantRegistration courseParticipantAllocated courseParticipantState) -> CourseParticipant{..})
instance ToJSON (InvitableJunction CourseParticipant) where nullaryPathPiece ''ButtonCourseRegisterMode $ camelToPathPiece' 1
toJSON = genericToJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 1 }
toEncoding = genericToEncoding defaultOptions { fieldLabelModifier = camelToPathPiece' 1 }
instance FromJSON (InvitableJunction CourseParticipant) where
parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 1 }
instance ToJSON (InvitationDBData CourseParticipant) where instance Button UniWorX ButtonCourseRegisterMode where
toJSON = genericToJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 3 } btnLabel x = [whamlet|_{x}|]
toEncoding = genericToEncoding defaultOptions { fieldLabelModifier = camelToPathPiece' 3 } btnClasses BtnCourseRegisterConfirm = [BCIsButton, BCPrimary]
instance FromJSON (InvitationDBData CourseParticipant) where btnClasses BtnCourseRegisterAbort = [BCIsButton, BCDanger]
parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 3 } btnValidate _ BtnCourseRegisterAbort = False
btnValidate _ _ = True
instance ToJSON (InvitationTokenData CourseParticipant) where
toJSON = genericToJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 3, omitNothingFields = True }
toEncoding = genericToEncoding defaultOptions { fieldLabelModifier = camelToPathPiece' 3, omitNothingFields = True }
instance FromJSON (InvitationTokenData CourseParticipant) where
parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 3, omitNothingFields = True }
participantInvitationConfig :: InvitationConfig CourseParticipant data CourseRegisterAction
participantInvitationConfig = InvitationConfig{..} = CourseRegisterActionAddParticipant
where | CourseRegisterActionAddTutorialMember
invitationRoute (Entity _ Course{..}) _ = return $ CourseR courseTerm courseSchool courseShorthand CInviteR -- | CourseRegisterActionUnknownPerson
invitationResolveFor _ = do deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
cRoute <- getCurrentRoute instance Universe CourseRegisterAction
case cRoute of instance Finite CourseRegisterAction
Just (CourseR tid csh ssh CInviteR) ->
getKeyBy404 $ TermSchoolCourseShort tid csh ssh data CourseRegisterActionData
_other -> = CourseRegisterActionAddParticipantData
error "participantInvitationConfig called from unsupported route" { crActIdent :: UserSearchKey
invitationSubject (Entity _ Course{..}) _ = return . SomeMessage $ MsgMailSubjectParticipantInvitation courseTerm courseSchool courseShorthand , crActUser :: (UserId, User)
invitationHeading (Entity _ Course{..}) _ = return . SomeMessage $ MsgCourseParticipantInviteHeading $ CI.original courseName }
invitationExplanation _ _ = return [ihamlet|_{SomeMessage MsgCourseParticipantInviteExplanation}|] | CourseRegisterActionAddTutorialMemberData
invitationTokenConfig _ _ = do { crActIdent :: UserSearchKey
itAuthority <- HashSet.singleton . Right <$> liftHandler requireAuthId , crActUser :: (UserId, User)
return $ InvitationTokenConfig itAuthority Nothing Nothing Nothing , crActTutorial :: TutorialIdent
invitationRestriction _ _ = return Authorized }
invitationForm _ _ _ = hoistAForm lift . wFormToAForm $ do -- | CourseRegisterActionUnknownPersonData -- pseudo-action; just for display
now <- liftIO getCurrentTime -- { crActUnknownPersonIdent :: Text
return . pure . (, ()) $ JunctionParticipant now Nothing CourseParticipantActive -- }
invitationInsertHook _ (Entity _ Course{..}) (_, InvTokenDataParticipant{..}) CourseParticipant{..} _ act = do deriving (Eq, Ord, Show, Generic, Typeable)
deleteBy $ UniqueParticipant courseParticipantUser courseParticipantCourse -- there are no foreign key references to @{CourseParticipant}; therefor we can delete and recreate to simulate upsert
res <- act -- insertUnique makeLenses_ ''CourseRegisterActionData
audit $ TransactionCourseParticipantEdit courseParticipantCourse courseParticipantUser makePrisms ''CourseRegisterActionData
void $ setUserSubmissionGroup courseParticipantCourse courseParticipantUser invTokenParticipantSubmissionGroup
memcachedByInvalidate (AuthCacheCourseRegisteredList courseTerm courseSchool courseShorthand) (Proxy @(Set UserId)) instance Aeson.FromJSON CourseRegisterActionData where
return res parseJSON = Aeson.genericParseJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 2 }
invitationSuccessMsg (Entity _ Course{..}) _ =
return . SomeMessage $ MsgCourseParticipantInvitationAccepted (CI.original courseName) instance Aeson.ToJSON CourseRegisterActionData where
invitationUltDest (Entity _ Course{..}) _ = return . SomeRoute $ CourseR courseTerm courseSchool courseShorthand CShowR toJSON = Aeson.genericToJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 2 }
toEncoding = Aeson.genericToEncoding defaultOptions { fieldLabelModifier = camelToPathPiece' 2 }
_classifyRegisterAction :: CourseRegisterActionData -> CourseRegisterAction
_classifyRegisterAction = \case
CourseRegisterActionAddParticipantData{} -> CourseRegisterActionAddParticipant
CourseRegisterActionAddTutorialMemberData{} -> CourseRegisterActionAddTutorialMember
--CourseRegisterActionUnknownPersonData{} -> CourseRegisterActionUnknownPerson
courseRegisterRenderActionClass :: CourseRegisterAction -> Widget
courseRegisterRenderActionClass = \case
CourseRegisterActionAddParticipant -> [whamlet|_{MsgCourseParticipantsRegisterActionAddParticipants}|]
CourseRegisterActionAddTutorialMember -> [whamlet|_{MsgCourseParticipantsRegisterActionAddTutorialMembers}|]
courseRegisterRenderAction :: CourseRegisterActionData -> Widget
courseRegisterRenderAction act = [whamlet|^{userWidget (view _2 (crActUser act))} (#{crActIdent act})|]
data AddUserRequest = AddUserRequest
{ auReqUsers :: Set UserSearchKey
, auReqTutorial :: Maybe TutorialIdent
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
data AddParticipantsResult = AddParticipantsResult data AddParticipantsResult = AddParticipantsResult
{ aurAlreadyRegistered { aurNotFound :: Set UserSearchKey
, aurSuccess :: Set UserId , aurAlreadyRegistered
, aurAlreadyTutorialMember
, aurRegisterSuccess
, aurTutorialSuccess :: Set UserId
} deriving (Read, Show, Generic, Typeable) } deriving (Read, Show, Generic, Typeable)
instance Semigroup AddParticipantsResult where instance Semigroup AddParticipantsResult where
@ -119,101 +116,184 @@ instance Monoid AddParticipantsResult where
mempty = memptydefault mempty = memptydefault
mappend = (<>) mappend = (<>)
getCAddUserR, postCAddUserR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCAddUserR, postCAddUserR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCAddUserR = postCAddUserR getCAddUserR = postCAddUserR
postCAddUserR tid ssh csh = do postCAddUserR tid ssh csh = do
cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh
((usersToEnlist,formWgt),formEncoding) <- runFormPost . renderWForm FormStandard $ do currentRoute <- fromMaybe (error "postCAddUserR called from 404-handler") <$> getCurrentRoute
enlist <- wreq checkBoxField (fslI MsgCourseParticipantEnlistDirectly) (Just False)
let submissionGroupOpts = optionsPersist [SubmissionGroupCourse ==. cid] [Asc SubmissionGroupName] submissionGroupName <&> fmap (submissionGroupName . entityVal) confirmedActs :: Set CourseRegisterActionData <- fmap Set.fromList . throwExceptT . mapMM encodedSecretBoxOpen . lookupPostParams $ toPathPiece PostCourseUserAddConfirmAction
mbGrp <- wopt (textField & cfStrip & cfCI & addDatalist submissionGroupOpts) (fslI MsgCourseSubmissionGroup & setTooltip MsgSubmissionGroupEmptyIsUnsetTip) Nothing $logDebugS "CAddUserR confirmedActs" . tshow $ Set.map Aeson.encode confirmedActs
unless (Set.null confirmedActs) $ do -- TODO: check that all acts are member of availableActs
let
users = Map.fromList . fmap (\act -> (crActIdent act, Just . view _1 $ crActUser act)) $ Set.toList confirmedActs
tutActs = Set.filter (is _CourseRegisterActionAddTutorialMemberData) confirmedActs
actTutorial = crActTutorial <$> Set.lookupMin tutActs -- tutorial ident must be the same for every added member!
registeredUsers <- registerUsers cid users
forM_ actTutorial $ \tutName -> do
tutId <- upsertNewTutorial cid tutName
registerTutorialMembers tutId registeredUsers
mr <- getMessageRender if
users <- wreq (multiUserInvitationField . maybe MUIAlwaysInvite (const $ MUILookupAnyUser Nothing) $ formResultToMaybe enlist) | Just tutName <- actTutorial
(fslpI MsgCourseParticipantInviteField (mr MsgLdapIdentificationOrEmail) & setTooltip MsgMultiEmailFieldTip) Nothing , Set.size tutActs == Set.size confirmedActs
-> redirect $ CTutorialR tid ssh csh tutName TUsersR
| otherwise
-> redirect $ CourseR tid ssh csh CUsersR
return $ Map.fromSet . const <$> mbGrp <*> users ((usersToAdd :: FormResult AddUserRequest, formWgt), formEncoding) <- runFormPost . renderWForm FormStandard $ do
today <- localDay . TZ.utcToLocalTimeTZ appTZ <$> liftIO getCurrentTime
let
cfCommaSeparatedSet :: forall m. Functor m => Field m Text -> Field m (Set Text)
cfCommaSeparatedSet = guardField (not . Set.null) . convertField (Set.fromList . mapMaybe (assertM' (not . Text.null) . Text.strip) . Text.splitOn ",") (Text.intercalate ", " . Set.toList)
auReqUsers <- wreq (textField & cfCommaSeparatedSet) (fslI MsgCourseParticipantsRegisterUsersField & setTooltip MsgCourseParticipantsRegisterUsersFieldTip) mempty
auReqTutorial <- optionalActionW
( areq (textField & cfCI) (fslI MsgCourseParticipantsRegisterTutorialField & setTooltip MsgCourseParticipantsRegisterTutorialFieldTip) (Just . CI.mk $ tshow today) ) -- TODO: use user date display setting
( fslI MsgCourseParticipantsRegisterTutorialOption )
( Just True )
return $ AddUserRequest <$> auReqUsers <*> auReqTutorial
formResultModal usersToEnlist (CourseR tid ssh csh CUsersR) $ formResult usersToAdd $ \AddUserRequest{..} -> do
hoist runDBJobs . registerUsers' cid avsUsers :: Map UserSearchKey (Maybe UserId) <- sequenceA $ Map.fromSet upsertAvsUser auReqUsers
let (usersFound, usersNotFound) = partition (is _Just . view _2) $ Map.toList avsUsers
unless (null usersNotFound) $
let msgContent = [whamlet|
$newline never
<ul>
$forall (usr,_) <- usersNotFound
<li>#{usr}
|]
in addMessageModal Error (i18n . MsgCourseParticipantsRegisterNotFoundInAvs $ length usersNotFound) (Right msgContent)
when (null usersFound) $
redirect currentRoute
liftHandler . (>>= sendResponse) $
siteLayoutMsg MsgCourseParticipantsRegisterHeading $ do
setTitleI MsgCourseParticipantsRegisterHeading
actionMap :: Map CourseRegisterAction (Set CourseRegisterActionData) <- fmap (Map.unionsWith Set.union) . forM usersFound $ \case
(_, Nothing) -> error "Found user in AVS, but response is Nothing!" -- this should not be possible
(ukey, Just uid) -> do
-- isParticipant <- exists [CourseParticipantCourse ==. cid, CourseParticipantUser ==. uid, CourseParticipantState ==. CourseParticipantActive]
user <- liftHandler . runDBRead $ get404 uid
case auReqTutorial of
Nothing -> return . Map.singleton CourseRegisterActionAddParticipant . Set.singleton $ CourseRegisterActionAddParticipantData ukey (uid,user)
Just crActAddTutorialMemberTutorial -> return . Map.singleton CourseRegisterActionAddTutorialMember . Set.singleton $ CourseRegisterActionAddTutorialMemberData ukey (uid,user) crActAddTutorialMemberTutorial
let
precomputeIdents :: forall f m. (Eq (Element f), MonoFoldable f, MonadHandler m) => f -> m (Element f -> Text)
precomputeIdents = foldM (\f act -> (\id' x -> bool (f x) id' $ act == x) <$> newIdent) (\_ -> error "No id precomputed")
actionClassIdent <- precomputeIdents $ Map.keys actionMap
actionIdent <- precomputeIdents . Set.unions $ Map.elems actionMap
let
confirmCheckBox :: [(Text,Text)] -> CourseRegisterActionData -> Widget
confirmCheckBox vAttrs act = do
let
sJsonField :: Field (HandlerFor UniWorX) CourseRegisterActionData
sJsonField = secretJsonField' $ \theId name attrs val _isReq ->
[whamlet|
$newline never
<input id=#{theId} *{attrs} type=checkbox name=#{name} value=#{either id id val} checked>
|]
fieldView sJsonField (actionIdent act) (toPathPiece PostCourseUserAddConfirmAction) vAttrs (Right act) False
availableActs :: Widget
availableActs = fieldView (secretJsonField :: Field Handler (Set CourseRegisterActionData)) "" (toPathPiece PostCourseUserAddConfirmAvailableActions) [] (Right . Set.unions $ Map.elems actionMap) False
(confirmForm', confirmEnctype) <- generateFormPost . withButtonForm' [BtnCourseRegisterConfirm, BtnCourseRegisterAbort] . identifyForm FIDCourseRegisterConfirm $ \csrf -> return (error "No meaningful FormResult", $(widgetFile "course/add-user/confirmation"))
let confirmForm = wrapForm confirmForm' FormSettings
{ formMethod = POST
, formAction = Just . SomeRoute $ CourseR tid ssh csh CAddUserR
, formEncoding = confirmEnctype
, formAttrs = []
, formSubmit = FormNoSubmit
, formAnchor = Nothing :: Maybe Text
}
$(widgetFile "course/add-user/confirmation-wrapper")
let heading = prependCourseTitle tid ssh csh MsgCourseParticipantsRegisterHeading
siteLayoutMsg heading $ do
setTitleI heading
wrapForm formWgt def
{ formEncoding
, formAction = Just . SomeRoute $ CourseR tid ssh csh CAddUserR
}
let heading = prependCourseTitle tid ssh csh MsgCourseParticipantsRegisterHeading registerUsers :: CourseId -> Map UserSearchKey (Maybe UserId) -> Handler (Set UserId)
registerUsers cid users
siteLayoutMsg heading $ do | Map.null users = do
setTitleI heading addMessageI Error MsgCourseParticipantsRegisterNoneGiven
wrapForm formWgt def return Set.empty
{ formEncoding | otherwise = do
, formAction = Just . SomeRoute $ CourseR tid ssh csh CAddUserR (mconcat -> AddParticipantsResult{..}) <- runDBJobs . mapM (registerUser cid) $ Map.toList users
} unless (Set.null aurRegisterSuccess) $
addMessageI Success . MsgCourseParticipantsRegistered $ Set.size aurRegisterSuccess
registerUsers :: CourseId -> Set (Either UserEmail UserId) -> WriterT [Message] (YesodJobDB UniWorX) () unless (Set.null aurAlreadyRegistered) $
registerUsers cid = registerUsers' cid . Map.fromSet (const Nothing) addMessageI Info . MsgCourseParticipantsAlreadyRegistered $ Set.size aurAlreadyRegistered
return $ aurRegisterSuccess `Set.union` aurAlreadyRegistered
registerUsers' :: CourseId -> Map (Either UserEmail UserId) (Maybe SubmissionGroupName) -> WriterT [Message] (YesodJobDB UniWorX) ()
registerUsers' cid users = do
let (emails,uids) = partitionKeysEither users
-- send Invitation eMails to unkown users
lift $ sinkInvitationsF participantInvitationConfig [(mail,cid,(InvDBDataParticipant,InvTokenDataParticipant{..})) | (mail, invTokenParticipantSubmissionGroup) <- Map.toList emails]
-- register known users
tell <=< lift . addParticipantsResultMessages <=< lift . execWriterT $ imapM_ (registerUser' cid) uids
unless (null emails) $
tell . pure <=< messageI Success . MsgCourseParticipantsInvited $ length emails
addParticipantsResultMessages :: (MonadHandler m, HandlerSite m ~ UniWorX)
=> AddParticipantsResult
-> ReaderT (YesodPersistBackend UniWorX) m [Message]
addParticipantsResultMessages AddParticipantsResult{..} = execWriterT $ do
aurAlreadyRegistered' <-
fmap sort (lift . mapM (fmap userEmail . getJust) $ Set.toList aurAlreadyRegistered)
unless (null aurAlreadyRegistered) $ do
let modalTrigger = [whamlet|_{MsgCourseParticipantsAlreadyRegistered (length aurAlreadyRegistered)}|]
modalContent = $(widgetFile "messages/courseInvitationAlreadyRegistered")
tell . pure <=< messageWidget Info $ msgModal modalTrigger (Right modalContent)
unless (null aurSuccess) $
tell . pure <=< messageI Success . MsgCourseParticipantsRegistered $ length aurSuccess
registerUser :: CourseId registerUser :: CourseId
-> UserId -> (UserSearchKey, Maybe UserId)
-> WriterT AddParticipantsResult (YesodJobDB UniWorX) () -> YesodJobDB UniWorX AddParticipantsResult
registerUser cid uid = registerUser' cid uid Nothing registerUser _cid ( avsIdent, Nothing ) = return $ mempty { aurNotFound = Set.singleton avsIdent }
registerUser cid (_avsIdent, Just uid) = exceptT return return $ do
registerUser' :: CourseId whenM (lift $ exists [CourseParticipantCourse ==. cid, CourseParticipantUser ==. uid, CourseParticipantState ==. CourseParticipantActive]) $
-> UserId
-> Maybe SubmissionGroupName
-> WriterT AddParticipantsResult (YesodJobDB UniWorX) ()
registerUser' cid uid mbGrp = exceptT tell tell $ do
whenM (lift . lift $ exists [CourseParticipantCourse ==. cid, CourseParticipantUser ==. uid, CourseParticipantState ==. CourseParticipantActive]) $
throwError $ mempty { aurAlreadyRegistered = Set.singleton uid } throwError $ mempty { aurAlreadyRegistered = Set.singleton uid }
courseParticipantRegistration <- liftIO getCurrentTime courseParticipantRegistration <- liftIO getCurrentTime
void . lift . lift $ upsert void . lift $ upsert
CourseParticipant CourseParticipant -- TODO: use participantId instead of userId for aurRegisterSuccess
{ courseParticipantCourse = cid { courseParticipantCourse = cid
, courseParticipantUser = uid , courseParticipantUser = uid
, courseParticipantAllocated = Nothing
, courseParticipantState = CourseParticipantActive , courseParticipantState = CourseParticipantActive
, .. , ..
} }
[ CourseParticipantRegistration =. courseParticipantRegistration [ CourseParticipantRegistration =. courseParticipantRegistration
, CourseParticipantAllocated =. Nothing
, CourseParticipantState =. CourseParticipantActive , CourseParticipantState =. CourseParticipantActive
] ]
lift . lift . audit $ TransactionCourseParticipantEdit cid uid lift . audit $ TransactionCourseParticipantEdit cid uid
lift . lift . queueDBJob . JobQueueNotification $ NotificationCourseRegistered uid cid lift . queueDBJob . JobQueueNotification $ NotificationCourseRegistered uid cid
void . lift . lift $ setUserSubmissionGroup cid uid mbGrp return $ mempty { aurRegisterSuccess = Set.singleton uid }
return $ mempty { aurSuccess = Set.singleton uid } upsertNewTutorial :: CourseId -> TutorialIdent -> Handler TutorialId
upsertNewTutorial cid tutorialName = do
now <- liftIO getCurrentTime
runDB $ do
Entity tutId _ <- upsert
Tutorial
{ tutorialCourse = cid
, tutorialType = CI.mk "Schulung"
, tutorialCapacity = Nothing
, tutorialRoom = Nothing
, tutorialRoomHidden = False
, tutorialTime = Occurrences mempty mempty
, tutorialRegGroup = Nothing -- TODO: remove
, tutorialRegisterFrom = Nothing
, tutorialRegisterTo = Nothing
, tutorialDeregisterUntil = Nothing
, tutorialLastChanged = now
, tutorialTutorControlled = False
, ..
}
[ TutorialName =. tutorialName
, TutorialType =. CI.mk "Schulung"
, TutorialLastChanged =. now
]
audit $ TransactionTutorialEdit tutId
return tutId
registerTutorialMembers :: TutorialId -> Set UserId -> Handler ()
getCInviteR, postCInviteR :: TermId -> SchoolId -> CourseShorthand -> Handler Html registerTutorialMembers tutId (Set.toList -> users) = runDB $ do
getCInviteR = postCInviteR prevParticipants <- Set.fromList . fmap entityKey <$> selectList [TutorialParticipantUser <-. users, TutorialParticipantTutorial ==. tutId] []
postCInviteR = invitationR participantInvitationConfig participants <- fmap Set.fromList . for users $ \tutorialParticipantUser -> do
Entity tutPartId _ <- upsert TutorialParticipant { tutorialParticipantTutorial = tutId, .. } []
audit $ TransactionTutorialParticipantEdit tutId tutPartId tutorialParticipantUser
return tutPartId
let newParticipants = participants Set.\\ prevParticipants
unless (Set.null newParticipants) $
addMessageI Success . MsgCourseParticipantsRegisteredTutorial $ Set.size newParticipants
unless (Set.null prevParticipants) $
addMessageI Info . MsgCourseParticipantsAlreadyTutorialMember $ length prevParticipants

View File

@ -17,17 +17,12 @@ import Handler.Utils.Exam
import Utils.Course import Utils.Course
import qualified Data.Text as Text
import qualified Data.Conduit.List as C
import Database.Persist.Sql (transactionUndo)
import qualified Database.Esqueleto.Legacy as E import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E import qualified Database.Esqueleto.Utils as E
-- Dedicated CourseRegistrationButton -- Dedicated CourseRegistrationButton
data ButtonCourseRegister = BtnCourseRegister | BtnCourseDeregister | BtnCourseApply | BtnCourseRetractApplication data ButtonCourseRegister = BtnCourseRegister | BtnCourseDeregister
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable) deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonCourseRegister instance Universe ButtonCourseRegister
instance Finite ButtonCourseRegister instance Finite ButtonCourseRegister
@ -36,19 +31,13 @@ embedRenderMessage ''UniWorX ''ButtonCourseRegister id
instance Button UniWorX ButtonCourseRegister where instance Button UniWorX ButtonCourseRegister where
btnClasses BtnCourseRegister = [BCIsButton, BCPrimary] btnClasses BtnCourseRegister = [BCIsButton, BCPrimary]
btnClasses BtnCourseDeregister = [BCIsButton, BCDanger] btnClasses BtnCourseDeregister = [BCIsButton, BCDanger]
btnClasses BtnCourseApply = [BCIsButton, BCPrimary]
btnClasses BtnCourseRetractApplication = [BCIsButton, BCDanger]
btnLabel BtnCourseRegister = [whamlet|#{iconEnrol True} _{MsgBtnCourseRegister}|] btnLabel BtnCourseRegister = [whamlet|#{iconEnrol True} _{MsgBtnCourseRegister}|]
btnLabel BtnCourseDeregister = [whamlet|#{iconEnrol False} _{MsgBtnCourseDeregister}|] btnLabel BtnCourseDeregister = [whamlet|#{iconEnrol False} _{MsgBtnCourseDeregister}|]
btnLabel BtnCourseApply = [whamlet|#{iconApply True} _{MsgBtnCourseApply}|]
btnLabel BtnCourseRetractApplication = [whamlet|#{iconApply False} _{MsgBtnCourseRetractApplication}|]
data CourseRegisterForm = CourseRegisterForm data CourseRegisterForm = CourseRegisterForm
{ crfApplicationText :: Maybe Text {}
, crfApplicationFiles :: Maybe FileUploads
}
courseRegisterForm :: (MonadHandler m, HandlerSite m ~ UniWorX) => Entity Course -> m (AForm Handler CourseRegisterForm, ButtonCourseRegister) courseRegisterForm :: (MonadHandler m, HandlerSite m ~ UniWorX) => Entity Course -> m (AForm Handler CourseRegisterForm, ButtonCourseRegister)
-- ^ `CourseRegisterForm` for current user -- ^ `CourseRegisterForm` for current user
@ -57,20 +46,13 @@ courseRegisterForm (Entity cid Course{..}) = liftHandler $ do
ata <- getSessionActiveAuthTags ata <- getSessionActiveAuthTags
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
(registration, application) <- runDB $ do registration <- runDB .
registration <- fmap join . for muid $ fmap (assertM . has $ _entityVal . _courseParticipantState . _CourseParticipantActive) . getBy . flip UniqueParticipant cid fmap join . for muid $ fmap (assertM . has $ _entityVal . _courseParticipantState . _CourseParticipantActive) . getBy . flip UniqueParticipant cid
application <- fmap (listToMaybe =<<) . for muid $ \uid -> selectList [CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid, CourseApplicationAllocation ==. Nothing] [] let btn | is _Just registration
return (registration, application)
let btn | courseApplicationsRequired
, is _Just application
= BtnCourseRetractApplication
| is _Just registration
= BtnCourseDeregister = BtnCourseDeregister
| courseApplicationsRequired
= BtnCourseApply
| otherwise | otherwise
= BtnCourseRegister = BtnCourseRegister
isRegistered = btn `elem` [BtnCourseRetractApplication, BtnCourseDeregister] isRegistered = btn == BtnCourseDeregister
return . (, btn) . wFormToAForm $ do return . (, btn) . wFormToAForm $ do
MsgRenderer mr <- getMsgRenderer MsgRenderer mr <- getMsgRenderer
@ -86,71 +68,11 @@ courseRegisterForm (Entity cid Course{..}) = liftHandler $ do
| otherwise | otherwise
-> return $ FormSuccess () -> return $ FormSuccess ()
appTextRes <- let fs | courseApplicationsRequired mayViewCourseAfterDeregistration <- liftHandler . runDB $ E.selectExists . E.from $ \course -> do
, is _Just courseApplicationsInstructions
= fslI MsgCourseApplicationText & setTooltip MsgCourseApplicationFollowInstructions
| courseApplicationsRequired
= fslI MsgCourseApplicationText
| is _Just courseApplicationsInstructions
= fslI MsgCourseRegistrationText & setTooltip MsgCourseRegistrationFollowInstructions
| otherwise
= fslI MsgCourseRegistrationText
textField' = convertField unTextarea Textarea textareaField
in if
| not courseApplicationsText
-> return $ FormSuccess Nothing
| is _Just muid
, isRegistered
-> wforced (convertField Just (fromMaybe Text.empty) textField') fs (application >>= courseApplicationText . entityVal)
| otherwise
-> fmap (assertM (not . Text.null) . fmap Text.strip) <$> wopt textField' fs (Just $ application >>= courseApplicationText . entityVal)
appFilesInfo <- for application $ \(Entity appId _) -> liftHandler . runDB $ do
hasFiles <- exists [ CourseApplicationFileApplication ==. appId ]
appCID <- encrypt appId
appFilesLink <- toTextUrl $ CApplicationR courseTerm courseSchool courseShorthand appCID CAFilesR
return (hasFiles, appFilesLink)
let hasFiles = maybe False (view _1) appFilesInfo
filesMsg = bool MsgCourseRegistrationFiles MsgCourseApplicationFiles courseApplicationsRequired
when (isn't _NoUpload courseApplicationsFiles || hasFiles) $
let filesLinkField = Field{..}
where
fieldParse _ _ = return $ Right Nothing
fieldEnctype = mempty
fieldView theId _ attrs _ _ =
[whamlet|
$newline never
$case appFilesInfo
$of Just (True, appFilesLink)
<a ##{theId} *{attrs} href=#{appFilesLink}>
_{filesMsg}
$of _
<span ##{theId} *{attrs}>
_{MsgCourseApplicationNoFiles}
|]
in void $ wforced filesLinkField (fslI filesMsg) Nothing
when (hasFiles && isn't _NoUpload courseApplicationsFiles) $
wformMessage <=< messageIconI Info IconFileUpload $ bool MsgCourseRegistrationFilesNeedReupload MsgCourseApplicationFilesNeedReupload courseApplicationsRequired
appFilesRes <- let mkFs | courseApplicationsRequired = bool MsgCourseApplicationFile MsgCourseApplicationArchive
| otherwise = bool MsgCourseRegistrationFile MsgCourseRegistrationArchive
prevAppFiles (Entity aId _) = runDBSource $ selectSource [CourseApplicationFileApplication ==. aId] [Asc CourseApplicationFileTitle] .| C.map (view $ _FileReference . _1)
in if
| isRegistered
-> return $ FormSuccess Nothing
| otherwise
-> aFormToWForm $ fileUploadForm False (fslI . mkFs) courseApplicationsFiles (prevAppFiles <$> application)
mayViewCourseAfterDeregistration <- liftHandler . runDB $ E.selectExists . E.from $ \(course `E.LeftOuterJoin` allocation) -> do
E.on . E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. E.just (allocationCourse E.^. AllocationCourseAllocation) E.==. allocation E.?. AllocationId
E.where_ $ course E.^. CourseId E.==. E.val cid E.where_ $ course E.^. CourseId E.==. E.val cid
E.&&. ( isSchoolAdminLike muid ata (course E.^. CourseSchool) E.&&. ( isSchoolAdminLike muid ata (course E.^. CourseSchool)
E.||. mayEditCourse muid ata course E.||. mayEditCourse muid ata course
E.||. courseIsVisible now course (allocation E.?. AllocationId) E.||. courseIsVisible now course
E.||. isCourseLecturer muid ata (course E.^. CourseId) E.||. isCourseLecturer muid ata (course E.^. CourseId)
E.||. isCourseTutor muid ata (course E.^. CourseId) E.||. isCourseTutor muid ata (course E.^. CourseId)
E.||. isCourseSheetCorrector muid ata (course E.^. CourseId) E.||. isCourseSheetCorrector muid ata (course E.^. CourseId)
@ -159,19 +81,12 @@ courseRegisterForm (Entity cid Course{..}) = liftHandler $ do
mayReRegister <- liftHandler . runDB . courseMayReRegister $ Entity cid Course{..} mayReRegister <- liftHandler . runDB . courseMayReRegister $ Entity cid Course{..}
when (is _Just $ registration >>= courseParticipantAllocated . entityVal) $
wformMessage =<< messageIconI Warning IconExamRegisterFalse MsgCourseDeregistrationAllocationLog
when (is _Just (registration >>= courseParticipantAllocated . entityVal) && courseDeregisterNoShow) $
wformMessage =<< messageIconI Warning IconEnrolFalse MsgCourseDeregistrationNoShow
when (isRegistered && not mayViewCourseAfterDeregistration) $ when (isRegistered && not mayViewCourseAfterDeregistration) $
wformMessage =<< messageIconI Warning IconEnrolFalse MsgCourseDeregistrationFromInvisibleCourse wformMessage =<< messageIconI Warning IconEnrolFalse MsgCourseDeregistrationFromInvisibleCourse
unless mayReRegister $ unless mayReRegister $
wformMessage =<< messageIconI Warning IconEnrolFalse MsgCourseDeregistrationNoReRegistration wformMessage =<< messageIconI Warning IconEnrolFalse MsgCourseDeregistrationNoReRegistration
return $ CourseRegisterForm return $ CourseRegisterForm <$ secretRes
<$ secretRes
<*> appTextRes
<*> appFilesRes
courseMayReRegister :: Entity Course -> DB Bool courseMayReRegister :: Entity Course -> DB Bool
courseMayReRegister (Entity cid Course{..}) = do courseMayReRegister (Entity cid Course{..}) = do
@ -200,101 +115,36 @@ postCRegisterR tid ssh csh = do
course@(Entity cid Course{..}) <- runDB . getBy404 $ TermSchoolCourseShort tid ssh csh course@(Entity cid Course{..}) <- runDB . getBy404 $ TermSchoolCourseShort tid ssh csh
(courseRegisterForm', courseRegisterButton) <- courseRegisterForm course (courseRegisterForm', courseRegisterButton) <- courseRegisterForm course
((regResult,_), _) <- runFormPost $ renderAForm FormStandard courseRegisterForm' ((regResult,_), _) <- runFormPost $ renderAForm FormStandard courseRegisterForm'
formResult regResult $ \CourseRegisterForm{..} -> do formResult regResult $ \CourseRegisterForm{} -> do
cTime <- liftIO getCurrentTime cTime <- liftIO getCurrentTime
let let
doApplication = courseApplicationsRequired || is _Just (void crfApplicationText <|> void crfApplicationFiles)
mkApplication
| doApplication
= void <$> do
appIds <- selectKeysList [ CourseApplicationAllocation ==. Nothing, CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid ] []
appRes <- case appIds of
[] -> insertUnique $ CourseApplication cid uid crfApplicationText False Nothing Nothing Nothing Nothing cTime Nothing
(prevId:ps) -> do
forM_ ps $ \appId -> do
deleteApplicationFiles appId
delete appId
audit $ TransactionCourseApplicationDeleted cid uid appId
deleteApplicationFiles prevId
update prevId [ CourseApplicationText =. crfApplicationText, CourseApplicationTime =. cTime ]
return $ Just prevId
whenIsJust appRes $
audit . TransactionCourseApplicationEdit cid uid
whenIsJust ((,) <$> appRes <*> crfApplicationFiles) $ \(appId, fSource) -> do
runConduit $ transPipe liftHandler fSource .| C.mapM_ (insert_ . review _FileReference . (, CourseApplicationFileResidual appId))
return appRes
| otherwise
= return $ Just ()
mkRegistration = do mkRegistration = do
audit $ TransactionCourseParticipantEdit cid uid audit $ TransactionCourseParticipantEdit cid uid
memcachedByInvalidate (AuthCacheCourseRegisteredList courseTerm courseSchool courseShorthand) (Proxy @(Set UserId)) memcachedByInvalidate (AuthCacheCourseRegisteredList courseTerm courseSchool courseShorthand) (Proxy @(Set UserId))
entityKey <$> upsert entityKey <$> upsert
(CourseParticipant cid uid cTime Nothing CourseParticipantActive) (CourseParticipant cid uid cTime CourseParticipantActive)
[ CourseParticipantRegistration =. cTime [ CourseParticipantRegistration =. cTime
, CourseParticipantAllocated =. Nothing
, CourseParticipantState =. CourseParticipantActive , CourseParticipantState =. CourseParticipantActive
] ]
case courseRegisterButton of case courseRegisterButton of
BtnCourseRegister -> runDB . bool id setSerializable doApplication $ do BtnCourseRegister -> runDB $ do
regOk <- (\app reg -> (, reg) <$> app) <$> mkApplication <*> mkRegistration void mkRegistration
case regOk of addMessageIconI Success IconEnrolTrue MsgCourseRegisterOk
Nothing -> transactionUndo
Just _ -> addMessageIconI Success IconEnrolTrue MsgCourseRegisterOk
BtnCourseDeregister -> runDB . setSerializable $ do BtnCourseDeregister -> runDB . setSerializable $ do
part <- fmap (assertM . has $ _entityVal . _courseParticipantState . _CourseParticipantActive) . getBy $ UniqueParticipant uid cid part <- fmap (assertM . has $ _entityVal . _courseParticipantState . _CourseParticipantActive) . getBy $ UniqueParticipant uid cid
forM_ part $ \(Entity _partId CourseParticipant{..}) -> do forM_ part . const $ do
deregisterParticipant uid course deregisterParticipant uid course
when (is _Just courseParticipantAllocated) $ do
updateBy (UniqueParticipant uid cid) [ CourseParticipantState =. CourseParticipantInactive courseDeregisterNoShow ]
now <- liftIO getCurrentTime
insert_ $ AllocationDeregister courseParticipantUser (Just courseParticipantCourse) now Nothing
let recordNoShow eId = do
didRecord <- is _Just <$> insertUnique ExamResult
{ examResultExam = eId
, examResultUser = uid
, examResultResult = ExamNoShow
, examResultLastChanged = now
}
when didRecord $
audit $ TransactionExamResultEdit eId uid
when courseDeregisterNoShow . runConduit $ selectKeys [ ExamCourse ==. cid ] [] .| C.mapM_ recordNoShow
addMessageIconI Info IconEnrolFalse MsgCourseDeregisterOk addMessageIconI Info IconEnrolFalse MsgCourseDeregisterOk
BtnCourseApply -> runDB . setSerializable $ do
regOk <- mkApplication
case regOk of
Nothing -> transactionUndo
Just _ -> addMessageIconI Success IconApplyTrue MsgCourseApplyOk
BtnCourseRetractApplication -> runDB $ do
deleteApplications uid cid
addMessageIconI Info IconApplyFalse MsgCourseRetractApplyOk
muid <- maybeAuthId muid <- maybeAuthId
ata <- getSessionActiveAuthTags ata <- getSessionActiveAuthTags
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
courseVisible <- runDB . E.selectExists . E.from $ \(course' `E.LeftOuterJoin` allocation) -> do courseVisible <- runDB . E.selectExists . E.from $ \course' -> do
E.on . E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course' E.^. CourseId
E.&&. E.just (allocationCourse E.^. AllocationCourseAllocation) E.==. allocation E.?. AllocationId
E.where_ $ course' E.^. CourseId E.==. E.val cid E.where_ $ course' E.^. CourseId E.==. E.val cid
E.&&. mayViewCourse muid ata now course' (allocation E.?. AllocationId) E.&&. mayViewCourse muid ata now course'
redirect $ bool NewsR (CourseR tid ssh csh CShowR) courseVisible redirect $ bool NewsR (CourseR tid ssh csh CShowR) courseVisible
deleteApplications :: UserId -> CourseId -> DB ()
deleteApplications uid cid = do
appIds <- selectKeysList [ CourseApplicationAllocation ==. Nothing, CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid ] []
forM_ appIds $ \appId -> do
deleteApplicationFiles appId
delete appId
audit $ TransactionCourseApplicationDeleted cid uid appId
deleteApplicationFiles :: CourseApplicationId -> DB ()
deleteApplicationFiles appId = deleteWhere [ CourseApplicationFileApplication ==. appId ]
deregisterParticipant :: UserId -> Entity Course -> DB () deregisterParticipant :: UserId -> Entity Course -> DB ()
deregisterParticipant uid (Entity cid Course{..}) = do deregisterParticipant uid (Entity cid Course{..}) = do

View File

@ -4,7 +4,6 @@
module Handler.Course.Show module Handler.Course.Show
( getCShowR ( getCShowR
, getCRegisterTemplateR, courseRegisterTemplateSource
) where ) where
import Import import Import
@ -25,8 +24,6 @@ import Database.Esqueleto.Utils.TH
import Handler.Course.Register import Handler.Course.Register
import qualified Data.Conduit.List as C
import Handler.Exam.List (mkExamTable) import Handler.Exam.List (mkExamTable)
@ -34,16 +31,13 @@ getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCShowR tid ssh csh = do getCShowR tid ssh csh = do
mbAid <- maybeAuthId mbAid <- maybeAuthId
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
(cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,mAllocation,mApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister,(mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) <- runDB . maybeT notFound $ do (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,news,events,submissionGroup,_mayReRegister,(mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) <- runDB . maybeT notFound $ do
[(E.Entity cid course, E.Value courseVisible, E.Value schoolName, E.Value participants, fmap entityVal -> registration, E.Value hasAllocationRegistrationOpen)] [(E.Entity cid course, E.Value courseVisible, E.Value schoolName, E.Value participants, fmap entityVal -> registration)]
<- lift . E.select . E.from $ <- lift . E.select . E.from $
\((school `E.InnerJoin` course) `E.LeftOuterJoin` allocation `E.LeftOuterJoin` participant) -> do \((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do
E.on $ E.just (course E.^. CourseId) E.==. participant E.?. CourseParticipantCourse E.on $ E.just (course E.^. CourseId) E.==. participant E.?. CourseParticipantCourse
E.&&. E.val mbAid E.==. participant E.?. CourseParticipantUser E.&&. E.val mbAid E.==. participant E.?. CourseParticipantUser
E.&&. participant E.?. CourseParticipantState E.==. E.just (E.val CourseParticipantActive) E.&&. participant E.?. CourseParticipantState E.==. E.just (E.val CourseParticipantActive)
E.on . E.exists . E.from $ \allocationCourse ->
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId
E.&&. E.just (allocationCourse E.^. AllocationCourseAllocation) E.==. allocation E.?. AllocationId
E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
E.where_ $ course E.^. CourseTerm E.==. E.val tid E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh E.&&. course E.^. CourseSchool E.==. E.val ssh
@ -54,11 +48,10 @@ getCShowR tid ssh csh = do
E.where_ $ part E.^. CourseParticipantCourse E.==. course E.^. CourseId E.where_ $ part E.^. CourseParticipantCourse E.==. course E.^. CourseId
E.&&. part E.^. CourseParticipantState E.==. E.val CourseParticipantActive E.&&. part E.^. CourseParticipantState E.==. E.val CourseParticipantActive
return ( course return ( course
, courseIsVisible now course $ allocation E.?. AllocationId , courseIsVisible now course
, school E.^. SchoolName , school E.^. SchoolName
, numParticipants , numParticipants
, participant , participant
, courseAllocationRegistrationOpen now (course E.^. CourseId) $ allocation E.?. AllocationId
) )
staff <- lift . E.select $ E.from $ \(lecturer `E.InnerJoin` user) -> do staff <- lift . E.select $ E.from $ \(lecturer `E.InnerJoin` user) -> do
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
@ -82,17 +75,6 @@ getCShowR tid ssh csh = do
E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid
E.orderBy [ E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName ] E.orderBy [ E.asc $ user E.^. UserSurname, E.asc $ user E.^. UserDisplayName ]
return ( user E.^. UserDisplayEmail, user E.^. UserDisplayName, user E.^. UserSurname ) return ( user E.^. UserDisplayEmail, user E.^. UserDisplayName, user E.^. UserSurname )
mAllocation <- fmap (fmap entityVal . listToMaybe) . lift . E.select . E.from $ \(allocation `E.InnerJoin` allocationCourse) -> do
E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation
E.where_ $ allocationCourse E.^. AllocationCourseCourse E.==. E.val cid
E.limit 1
return allocation
hasApplicationTemplate <- lift . E.selectExists . E.from $ \courseAppInstructionFile ->
E.where_ $ courseAppInstructionFile E.^. CourseAppInstructionFileCourse E.==. E.val cid
mApplicationTemplate <- runMaybeT $ do
guard hasApplicationTemplate
lift . lift . toTextUrl $ CourseR tid ssh csh CRegisterTemplateR
mApplication <- lift . fmap (listToMaybe =<<) . for mbAid $ \uid -> selectList [CourseApplicationCourse ==. cid, CourseApplicationUser ==. uid, CourseApplicationAllocation ==. Nothing] []
news' <- lift $ selectList [ CourseNewsCourse ==. cid ] [ Desc CourseNewsVisibleFrom, Desc CourseNewsTitle, Desc CourseNewsSummary, Desc CourseNewsContent ] news' <- lift $ selectList [ CourseNewsCourse ==. cid ] [ Desc CourseNewsVisibleFrom, Desc CourseNewsTitle, Desc CourseNewsSummary, Desc CourseNewsContent ]
cTime <- NTop . Just <$> liftIO getCurrentTime cTime <- NTop . Just <$> liftIO getCurrentTime
news <- forMaybeM news' $ \(Entity nId n@CourseNews{..}) -> do news <- forMaybeM news' $ \(Entity nId n@CourseNews{..}) -> do
@ -146,14 +128,10 @@ getCShowR tid ssh csh = do
return $ material E.^. MaterialName return $ material E.^. MaterialName
mayViewAnyMaterial <- lift . anyM materials $ \(E.Value mnm) -> hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR mayViewAnyMaterial <- lift . anyM materials $ \(E.Value mnm) -> hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR
return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,mAllocation,mApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister, (mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,administrators,correctors,tutors,news,events,submissionGroup,mayReRegister, (mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial))
let mDereg' = maybe id min (allocationOverrideDeregister =<< mAllocation) <$> courseDeregisterUntil course mDereg <- traverse (formatTime SelFormatDateTime) $ courseDeregisterUntil course
mDereg <- traverse (formatTime SelFormatDateTime) mDereg'
cID <- encrypt cid :: Handler CryptoUUIDCourse
mAllocation' <- for mAllocation $ \alloc@Allocation{..} -> (alloc, )
<$> toTextUrl (AllocationR allocationTerm allocationSchool allocationShorthand AShowR :#: cID)
regForm <- if regForm <- if
| is _Just mbAid -> do | is _Just mbAid -> do
(courseRegisterForm', regButton) <- courseRegisterForm (Entity cid course) (courseRegisterForm', regButton) <- courseRegisterForm (Entity cid course)
@ -282,16 +260,3 @@ getCShowR tid ssh csh = do
siteLayout heading $ do siteLayout heading $ do
setTitleI $ prependCourseTitle tid ssh csh (""::Text) setTitleI $ prependCourseTitle tid ssh csh (""::Text)
$(widgetFile "course") $(widgetFile "course")
courseRegisterTemplateSource :: TermId -> SchoolId -> CourseShorthand -> ConduitT () CourseAppInstructionFile (YesodDB UniWorX) ()
courseRegisterTemplateSource tid ssh csh = (.| C.map entityVal) . E.selectSource . E.from $ \(courseAppInstructionFile `E.InnerJoin` course) -> do
E.on $ course E.^. CourseId E.==. courseAppInstructionFile E.^. CourseAppInstructionFileCourse
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
return courseAppInstructionFile
getCRegisterTemplateR :: TermId -> SchoolId -> CourseShorthand -> Handler TypedContent
getCRegisterTemplateR tid ssh csh = do
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack). ap getMessageRender . pure $ MsgCourseApplicationTemplateArchiveName tid ssh csh
serveSomeFiles archiveName $ courseRegisterTemplateSource tid ssh csh

View File

@ -32,7 +32,6 @@ import qualified Data.Map as Map
import qualified Data.Text as Text import qualified Data.Text as Text
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import qualified Data.Conduit.Combinators as C
import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy as LT
@ -113,16 +112,7 @@ courseUserProfileSection course@(Entity cid Course{..}) (Entity uid User{ userSh
let regButton let regButton
| is _Just mRegistration = BtnCourseDeregister | is _Just mRegistration = BtnCourseDeregister
| otherwise = BtnCourseRegister | otherwise = BtnCourseRegister
((regButtonRes, regButtonView), regButtonEnctype) <- lift . runFormPost . identifyForm FIDcRegButton $ ((regButtonRes, regButtonView), regButtonEnctype) <- lift . runFormPost . identifyForm FIDcRegButton $ \csrf -> pure (FormSuccess (regButton, Nothing), toWidget csrf)
if | is _Just $ courseParticipantAllocated . entityVal =<< mRegistration
-> renderWForm FormStandard $ fmap (regButton, )
<$ (wformMessage =<< messageIconI Warning IconEnrolFalse MsgCourseDeregistrationAllocationShouldLogTip)
<*> optionalActionW ((,)
<$> areq (textField & cfStrip & guardField (not . null)) (fslI MsgCourseDeregistrationAllocationReason & setTooltip MsgCourseDeregistrationAllocationReasonTip) Nothing
<*> apopt checkBoxField (fslI MsgCourseDeregistrationAllocationNoShow & setTooltip MsgCourseDeregistrationAllocationNoShowTip) Nothing
) (fslI MsgCourseDeregistrationAllocationShouldLog) (Just True)
| otherwise
-> \csrf -> pure (FormSuccess (regButton, Nothing), toWidget csrf)
let registrationButtonFrag :: Text let registrationButtonFrag :: Text
registrationButtonFrag = "registration-button" registrationButtonFrag = "registration-button"
@ -145,21 +135,8 @@ courseUserProfileSection course@(Entity cid Course{..}) (Entity uid User{ userSh
unless (courseParticipantCourse == cid) $ error "courseParticipantCourse does not match cid" unless (courseParticipantCourse == cid) $ error "courseParticipantCourse does not match cid"
deregisterParticipant courseParticipantUser course deregisterParticipant courseParticipantUser course
whenIsJust mbReason $ \(reason, noShow) -> do whenIsJust mbReason $ \(_reason, noShow) -> do
updateBy (UniqueParticipant uid cid) [ CourseParticipantState =. CourseParticipantInactive noShow ] updateBy (UniqueParticipant uid cid) [ CourseParticipantState =. CourseParticipantInactive noShow ]
now <- liftIO getCurrentTime
insert_ $ AllocationDeregister courseParticipantUser (Just cid) now (Just reason)
let recordNoShow eId = do
didRecord <- is _Just <$> insertUnique ExamResult
{ examResultExam = eId
, examResultUser = uid
, examResultResult = ExamNoShow
, examResultLastChanged = now
}
when didRecord $
audit $ TransactionExamResultEdit eId uid
when noShow . runConduit $ selectKeys [ ExamCourse ==. cid ] [] .| C.mapM_ recordNoShow
addMessageIconI Info IconEnrolFalse MsgCourseDeregisterOk addMessageIconI Info IconEnrolFalse MsgCourseDeregisterOk
redirect $ CourseR courseTerm courseSchool courseShorthand CUsersR redirect $ CourseR courseTerm courseSchool courseShorthand CUsersR
| otherwise | otherwise
@ -168,16 +145,14 @@ courseUserProfileSection course@(Entity cid Course{..}) (Entity uid User{ userSh
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
lift . runDBJobs $ do lift . runDBJobs $ do
void $ upsert void $ upsert
(CourseParticipant cid uid now Nothing CourseParticipantActive) (CourseParticipant cid uid now CourseParticipantActive)
[ CourseParticipantRegistration =. now [ CourseParticipantRegistration =. now
, CourseParticipantAllocated =. Nothing
, CourseParticipantState =. CourseParticipantActive , CourseParticipantState =. CourseParticipantActive
] ]
queueDBJob . JobQueueNotification $ NotificationCourseRegistered uid cid queueDBJob . JobQueueNotification $ NotificationCourseRegistered uid cid
audit $ TransactionCourseParticipantEdit cid uid audit $ TransactionCourseParticipantEdit cid uid
addMessageIconI Success IconEnrolTrue MsgCourseRegisterOk addMessageIconI Success IconEnrolTrue MsgCourseRegisterOk
redirect currentRoute redirect currentRoute
_other -> error "Invalid @regButton@"
mRegAt <- for (courseParticipantRegistration . entityVal <$> mRegistration) $ formatTime SelFormatDateTime mRegAt <- for (courseParticipantRegistration . entityVal <$> mRegistration) $ formatTime SelFormatDateTime

View File

@ -8,7 +8,7 @@ module Handler.Course.Users
( queryUser ( queryUser
, makeCourseUserTable , makeCourseUserTable
, postCUsersR, getCUsersR , postCUsersR, getCUsersR
, colUserSex', _userStudyFeatures , colUserSex', colUserQualifications, _userQualifications
) where ) where
import Import import Import
@ -20,7 +20,6 @@ import qualified Database.Esqueleto.Utils as E
import Database.Esqueleto.Utils.TH import Database.Esqueleto.Utils.TH
import Handler.Course.Register (deregisterParticipant) import Handler.Course.Register (deregisterParticipant)
import Handler.Utils.StudyFeatures
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Data.Map as Map import qualified Data.Map as Map
@ -86,6 +85,7 @@ userTableQuery cid ((user `E.InnerJoin` participant) `E.LeftOuterJoin` note `E.L
E.where_ $ participant E.^. CourseParticipantCourse E.==. E.val cid E.where_ $ participant E.^. CourseParticipantCourse E.==. E.val cid
return (user, participant, note E.?. CourseUserNoteId, subGroup) return (user, participant, note E.?. CourseUserNoteId, subGroup)
type UserTableQualifications = [Entity Qualification]
type UserTableData = DBRow ( Entity User type UserTableData = DBRow ( Entity User
, Entity CourseParticipant , Entity CourseParticipant
@ -94,7 +94,7 @@ type UserTableData = DBRow ( Entity User
, [Entity Exam] , [Entity Exam]
, Maybe (Entity SubmissionGroup) , Maybe (Entity SubmissionGroup)
, Map SheetName (SheetType SqlBackendKey, Maybe Points) , Map SheetName (SheetType SqlBackendKey, Maybe Points)
, UserTableStudyFeatures , UserTableQualifications
) )
instance HasEntity UserTableData User where instance HasEntity UserTableData User where
@ -124,8 +124,8 @@ _userSubmissionGroup = _dbrOutput . _6 . _Just
_userSheets :: Lens' UserTableData (Map SheetName (SheetType SqlBackendKey, Maybe Points)) _userSheets :: Lens' UserTableData (Map SheetName (SheetType SqlBackendKey, Maybe Points))
_userSheets = _dbrOutput . _7 _userSheets = _dbrOutput . _7
_userStudyFeatures :: Lens' UserTableData UserTableStudyFeatures _userQualifications :: Lens' UserTableData UserTableQualifications
_userStudyFeatures = _dbrOutput . _8 _userQualifications = _dbrOutput . _8
colUserComment :: IsDBTable m c => TermId -> SchoolId -> CourseShorthand -> Colonnade Sortable UserTableData (DBCell m c) colUserComment :: IsDBTable m c => TermId -> SchoolId -> CourseShorthand -> Colonnade Sortable UserTableData (DBCell m c)
@ -173,6 +173,12 @@ colUserSheets shns = cap (Sortable Nothing caption) $ foldMap userSheetCol shns
Just (preview _grading -> Just grading', Just points) -> i18nCell . bool MsgTableNotPassed MsgTablePassed $ Just True == gradingPassed grading' points Just (preview _grading -> Just grading', Just points) -> i18nCell . bool MsgTableNotPassed MsgTablePassed $ Just True == gradingPassed grading' points
_other -> mempty _other -> mempty
colUserQualifications :: forall m c. IsDBTable m c => Colonnade Sortable UserTableData (DBCell m c)
colUserQualifications = sortable (Just "qualifications") (i18nCell MsgTableQualifications) $
\(view _userQualifications -> qualis') ->
let qualis = sortOn (qualificationName . entityVal) qualis'
in (cellAttrs <>~ [("class", "list--inline list--comma-separated list--iconless")]) . listCell qualis $ qualificationCell . entityVal
data UserTableCsv = UserTableCsv data UserTableCsv = UserTableCsv
{ csvUserSurname :: UserSurname { csvUserSurname :: UserSurname
@ -182,7 +188,7 @@ data UserTableCsv = UserTableCsv
, csvUserMatriculation :: Maybe UserMatriculation , csvUserMatriculation :: Maybe UserMatriculation
, csvUserEPPN :: Maybe UserEduPersonPrincipalName , csvUserEPPN :: Maybe UserEduPersonPrincipalName
, csvUserEmail :: UserEmail , csvUserEmail :: UserEmail
, csvUserStudyFeatures :: UserTableStudyFeatures , csvUserQualifications :: [QualificationName]
, csvUserSubmissionGroup :: Maybe SubmissionGroupName , csvUserSubmissionGroup :: Maybe SubmissionGroupName
, csvUserRegistration :: UTCTime , csvUserRegistration :: UTCTime
, csvUserNote :: Maybe StoredMarkup , csvUserNote :: Maybe StoredMarkup
@ -201,7 +207,7 @@ instance Csv.ToNamedRecord UserTableCsv where
, "matriculation" Csv..= csvUserMatriculation , "matriculation" Csv..= csvUserMatriculation
, "eduPersonPrincipalName" Csv..= csvUserEPPN , "eduPersonPrincipalName" Csv..= csvUserEPPN
, "email" Csv..= csvUserEmail , "email" Csv..= csvUserEmail
, "study-features" Csv..= csvUserStudyFeatures , "qualifications" Csv..= CsvSemicolonList csvUserQualifications
, "submission-group" Csv..= csvUserSubmissionGroup , "submission-group" Csv..= csvUserSubmissionGroup
, "tutorial" Csv..= CsvSemicolonList (csvUserTutorials ^. _1) , "tutorial" Csv..= CsvSemicolonList (csvUserTutorials ^. _1)
] ++ ] ++
@ -224,7 +230,6 @@ instance CsvColumnsExplained UserTableCsv where
, single "sex" MsgCsvColumnUserSex , single "sex" MsgCsvColumnUserSex
, single "matriculation" MsgCsvColumnUserMatriculation , single "matriculation" MsgCsvColumnUserMatriculation
, single "email" MsgCsvColumnUserEmail , single "email" MsgCsvColumnUserEmail
, single "study-features" MsgCsvColumnUserCourseStudyFeatures
, single "submission-group" MsgCsvColumnUserSubmissionGroup , single "submission-group" MsgCsvColumnUserSubmissionGroup
, single "tutorial" MsgCsvColumnUserTutorial , single "tutorial" MsgCsvColumnUserTutorial
, single "exams" MsgCsvColumnUserExam , single "exams" MsgCsvColumnUserExam
@ -263,7 +268,7 @@ data UserTableJson = UserTableJson
, jsonUserMatriculation :: Maybe UserMatriculation , jsonUserMatriculation :: Maybe UserMatriculation
, jsonUserEPPN :: Maybe UserEduPersonPrincipalName , jsonUserEPPN :: Maybe UserEduPersonPrincipalName
, jsonUserEmail :: UserEmail , jsonUserEmail :: UserEmail
, jsonUserStudyFeatures :: UserTableStudyFeatures , jsonUserQualifications :: Set QualificationName
, jsonUserSubmissionGroup :: Maybe SubmissionGroupName , jsonUserSubmissionGroup :: Maybe SubmissionGroupName
, jsonUserRegistration :: UTCTime , jsonUserRegistration :: UTCTime
, jsonUserNote :: Maybe Lazy.Text , jsonUserNote :: Maybe Lazy.Text
@ -300,7 +305,7 @@ instance ToJSON UserTableJson where
, ("matriculation" JSON..=) <$> jsonUserMatriculation , ("matriculation" JSON..=) <$> jsonUserMatriculation
, ("eduPersonPrincipalName" JSON..=) <$> jsonUserEPPN , ("eduPersonPrincipalName" JSON..=) <$> jsonUserEPPN
, pure $ "email" JSON..= jsonUserEmail , pure $ "email" JSON..= jsonUserEmail
, ("study-features" JSON..=) <$> assertM' (views _Wrapped $ not . onull) jsonUserStudyFeatures , ("qualifications" JSON..=) <$> assertM' (not . onull) jsonUserQualifications
, ("submission-group" JSON..=) <$> jsonUserSubmissionGroup , ("submission-group" JSON..=) <$> jsonUserSubmissionGroup
, pure $ "registration" JSON..= jsonUserRegistration , pure $ "registration" JSON..= jsonUserRegistration
, ("note" JSON..=) <$> jsonUserNote , ("note" JSON..=) <$> jsonUserNote
@ -392,14 +397,17 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
, submission , submission
) )
) )
feats <- courseUserStudyFeatures (participant ^. _entityVal . _courseParticipantCourse) (participant ^. _entityVal . _courseParticipantUser) qualis <- E.select . E.from $ \(qualification `E.InnerJoin` qualificationUser) -> do
E.on $ qualification E.^. QualificationId E.==. qualificationUser E.^. QualificationUserQualification
E.where_ $ qualificationUser E.^. QualificationUserUser E.==. E.val (entityKey user)
return qualification
let let
regGroups = setOf (folded . _entityVal . _tutorialRegGroup . _Just) tutorials regGroups = setOf (folded . _entityVal . _tutorialRegGroup . _Just) tutorials
tuts' = filter (\(Entity tutId _) -> any ((== tutId) . tutorialParticipantTutorial . entityVal) tuts'') tutorials tuts' = filter (\(Entity tutId _) -> any ((== tutId) . tutorialParticipantTutorial . entityVal) tuts'') tutorials
tuts = foldr (\tut@(Entity _ Tutorial{..}) -> maybe (over _1 $ cons tut) (over _2 . flip (Map.insertWith (<|>)) (Just tut)) tutorialRegGroup) ([], Map.fromSet (const Nothing) regGroups) tuts' tuts = foldr (\tut@(Entity _ Tutorial{..}) -> maybe (over _1 $ cons tut) (over _2 . flip (Map.insertWith (<|>)) (Just tut)) tutorialRegGroup) ([], Map.fromSet (const Nothing) regGroups) tuts'
exs = filter (\(Entity eId _) -> any ((== eId) . examRegistrationExam . entityVal) exams') exams exs = filter (\(Entity eId _) -> any ((== eId) . examRegistrationExam . entityVal) exams') exams
subs = Map.fromList $ map (over (_2 . _2) (views _entityVal submissionRatingPoints <=< assertM (views _entityVal submissionRatingDone)) . over _1 E.unValue . over (_2 . _1) E.unValue) subs' subs = Map.fromList $ map (over (_2 . _2) (views _entityVal submissionRatingPoints <=< assertM (views _entityVal submissionRatingDone)) . over _1 E.unValue . over (_2 . _1) E.unValue) subs'
return (user, participant, userNoteId, tuts, exs, subGroup, subs, feats) return (user, participant, userNoteId, tuts, exs, subGroup, subs, qualis)
dbtColonnade = colChoices dbtColonnade = colChoices
dbtSorting = mconcat dbtSorting = mconcat
[ single $ sortUserNameLink queryUser -- slower sorting through clicking name column header [ single $ sortUserNameLink queryUser -- slower sorting through clicking name column header
@ -474,18 +482,18 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
E.&&. sheet E.^. SheetName E.==. E.val shn E.&&. sheet E.^. SheetName E.==. E.val shn
) )
, fltrRelevantStudyFeaturesTerms (to $ --, fltrRelevantStudyFeaturesTerms (to $
\t -> ( E.subSelectForeign (queryParticipant t) CourseParticipantCourse (E.^. CourseTerm) -- \t -> ( E.subSelectForeign (queryParticipant t) CourseParticipantCourse (E.^. CourseTerm)
, queryUser t E.^. UserId -- , queryUser t E.^. UserId
)) -- ))
, fltrRelevantStudyFeaturesDegree (to $ --, fltrRelevantStudyFeaturesDegree (to $
\t -> ( E.subSelectForeign (queryParticipant t) CourseParticipantCourse (E.^. CourseTerm) -- \t -> ( E.subSelectForeign (queryParticipant t) CourseParticipantCourse (E.^. CourseTerm)
, queryUser t E.^. UserId -- , queryUser t E.^. UserId
)) -- ))
, fltrRelevantStudyFeaturesSemester (to $ --, fltrRelevantStudyFeaturesSemester (to $
\t -> ( E.subSelectForeign (queryParticipant t) CourseParticipantCourse (E.^. CourseTerm) -- \t -> ( E.subSelectForeign (queryParticipant t) CourseParticipantCourse (E.^. CourseTerm)
, queryUser t E.^. UserId -- , queryUser t E.^. UserId
)) -- ))
] ]
where single = uncurry Map.singleton where single = uncurry Map.singleton
dbtFilterUI mPrev = mconcat $ dbtFilterUI mPrev = mconcat $
@ -497,9 +505,9 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
[ prismAForm (singletonFilter "submission-group") mPrev $ aopt textField (fslI MsgTableSubmissionGroup) [ prismAForm (singletonFilter "submission-group") mPrev $ aopt textField (fslI MsgTableSubmissionGroup)
, prismAForm (singletonFilter "tutorial") mPrev $ aopt textField (fslI MsgCourseUserTutorial) , prismAForm (singletonFilter "tutorial") mPrev $ aopt textField (fslI MsgCourseUserTutorial)
, prismAForm (singletonFilter "exam") mPrev $ aopt textField (fslI MsgCourseUserExam) , prismAForm (singletonFilter "exam") mPrev $ aopt textField (fslI MsgCourseUserExam)
, fltrRelevantStudyFeaturesDegreeUI mPrev --, fltrRelevantStudyFeaturesDegreeUI mPrev
, fltrRelevantStudyFeaturesTermsUI mPrev --, fltrRelevantStudyFeaturesTermsUI mPrev
, fltrRelevantStudyFeaturesSemesterUI mPrev --, fltrRelevantStudyFeaturesSemesterUI mPrev
] ++ ] ++
[ prismAForm (singletonFilter "has-personalised-sheet-files". maybePrism _PathPiece) mPrev $ aopt (selectField' (Just $ SomeMessage MsgTableNoFilter) . optionsF $ map E.unValue personalisedSheets) (fslI MsgCourseUserHasPersonalisedSheetFilesFilter) [ prismAForm (singletonFilter "has-personalised-sheet-files". maybePrism _PathPiece) mPrev $ aopt (selectField' (Just $ SomeMessage MsgTableNoFilter) . optionsF $ map E.unValue personalisedSheets) (fslI MsgCourseUserHasPersonalisedSheetFilesFilter)
| not $ null personalisedSheets | not $ null personalisedSheets
@ -533,12 +541,11 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
<*> view (hasUser . _userMatrikelnummer) <*> view (hasUser . _userMatrikelnummer)
<*> view (hasUser . _userLdapPrimaryKey) <*> view (hasUser . _userLdapPrimaryKey)
<*> view (hasUser . _userEmail) <*> view (hasUser . _userEmail)
<*> view _userStudyFeatures <*> (over traverse (qualificationName . entityVal) <$> view _userQualifications)
<*> preview (_userSubmissionGroup . _entityVal . _submissionGroupName) <*> preview (_userSubmissionGroup . _entityVal . _submissionGroupName)
<*> view _userTableRegistration <*> view _userTableRegistration
<*> userNote <*> userNote
<*> (over (_2.traverse._Just) (tutorialName . entityVal) . over (_1.traverse) (tutorialName . entityVal) <$> view _userTutorials) <*> (over (_2.traverse._Just) (tutorialName . entityVal) . over (_1.traverse) (tutorialName . entityVal) <$> view _userTutorials)
-- <*> (over (_2.traverse._Just) (examName . entityVal) . over (_1.traverse) (examName . entityVal) <$> view _userExams)
<*> (over traverse (examName . entityVal) <$> view _userExams) <*> (over traverse (examName . entityVal) <$> view _userExams)
<*> views _userSheets (set (mapped . _1 . mapped) ()) <*> views _userSheets (set (mapped . _1 . mapped) ())
, dbtCsvName, dbtCsvSheetName , dbtCsvName, dbtCsvSheetName
@ -566,7 +573,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
<*> view (hasUser . _userMatrikelnummer) <*> view (hasUser . _userMatrikelnummer)
<*> view (hasUser . _userLdapPrimaryKey) <*> view (hasUser . _userLdapPrimaryKey)
<*> view (hasUser . _userEmail) <*> view (hasUser . _userEmail)
<*> view _userStudyFeatures <*> view (_userQualifications . folded . to (Set.singleton . qualificationName . entityVal))
<*> preview (_userSubmissionGroup . _entityVal . _submissionGroupName) <*> preview (_userSubmissionGroup . _entityVal . _submissionGroupName)
<*> view _userTableRegistration <*> view _userTableRegistration
<*> (fmap markupInput <$> userNote) <*> (fmap markupInput <$> userNote)
@ -588,18 +595,7 @@ makeCourseUserTable cid acts restrict colChoices psValidator csvColumns = do
return (act, usrSet) return (act, usrSet)
courseUserDeregisterForm :: CourseId -> AForm Handler CourseUserActionData courseUserDeregisterForm :: CourseId -> AForm Handler CourseUserActionData
courseUserDeregisterForm cid = wFormToAForm $ do courseUserDeregisterForm _cid = wFormToAForm . pure . pure $ CourseUserDeregisterData Nothing
allocated <- liftHandler . runDB . E.selectExists . E.from $ \participant ->
E.where_ $ participant E.^. CourseParticipantCourse E.==. E.val cid
E.&&. E.not_ (E.isNothing $ participant E.^. CourseParticipantAllocated)
E.&&. participant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
if | allocated -> do
wformMessage =<< messageIconI Warning IconEnrolFalse MsgCourseDeregistrationAllocationShouldLogTip
let selfImposedForm = (,)
<$> apreq (textField & cfStrip & guardField (not . null)) (fslI MsgCourseDeregistrationAllocationReason & setTooltip MsgCourseDeregistrationAllocationReasonTip) Nothing
<*> apopt checkBoxField (fslI MsgCourseDeregistrationAllocationNoShow & setTooltip MsgCourseDeregistrationAllocationNoShowTip) Nothing
fmap CourseUserDeregisterData <$> optionalActionW selfImposedForm (fslI MsgCourseDeregistrationAllocationShouldLog) (Just True)
| otherwise -> pure . pure $ CourseUserDeregisterData Nothing
getCUsersR, postCUsersR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCUsersR, postCUsersR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCUsersR = postCUsersR getCUsersR = postCUsersR
@ -636,7 +632,7 @@ postCUsersR tid ssh csh = do
, guardOn showSex . cap' $ colUserSex' , guardOn showSex . cap' $ colUserSex'
, pure . cap' $ colUserEmail , pure . cap' $ colUserEmail
, pure . cap' $ colUserMatriclenr , pure . cap' $ colUserMatriclenr
, pure . cap' $ colStudyFeatures _userStudyFeatures , pure . cap' $ colUserQualifications
, guardOn hasSubmissionGroups $ cap' colUserSubmissionGroup , guardOn hasSubmissionGroups $ cap' colUserSubmissionGroup
, guardOn hasTutorials . cap' $ colUserTutorials tid ssh csh , guardOn hasTutorials . cap' $ colUserTutorials tid ssh csh
, guardOn hasExams . cap' $ colUserExams tid ssh csh , guardOn hasExams . cap' $ colUserExams tid ssh csh
@ -699,28 +695,11 @@ postCUsersR tid ssh csh = do
(CourseUserSendMailData, selectedUsers) -> do (CourseUserSendMailData, selectedUsers) -> do
cids <- traverse encrypt $ Set.toList selectedUsers :: Handler [CryptoUUIDUser] cids <- traverse encrypt $ Set.toList selectedUsers :: Handler [CryptoUUIDUser]
redirect (CourseR tid ssh csh CCommR, [(toPathPiece GetRecipient, toPathPiece cID) | cID <- cids]) redirect (CourseR tid ssh csh CCommR, [(toPathPiece GetRecipient, toPathPiece cID) | cID <- cids])
(CourseUserDeregisterData{..}, selectedUsers) -> do (CourseUserDeregisterData{}, selectedUsers) -> do
Sum nrDel <- fmap mconcat . runDB . forM (Set.toList selectedUsers) $ \uid -> fmap (maybe mempty Sum) . runMaybeT $ do Sum nrDel <- fmap mconcat . runDB . forM (Set.toList selectedUsers) $ \uid -> fmap (maybe mempty Sum) . runMaybeT $ do
now <- liftIO getCurrentTime
Entity _ CourseParticipant{..} <- MaybeT . fmap (assertM . has $ _entityVal . _courseParticipantState . _CourseParticipantActive) . getBy $ UniqueParticipant uid cid Entity _ CourseParticipant{..} <- MaybeT . fmap (assertM . has $ _entityVal . _courseParticipantState . _CourseParticipantActive) . getBy $ UniqueParticipant uid cid
unless (courseParticipantCourse == cid) $ error "courseParticipantCourse does not match cid" unless (courseParticipantCourse == cid) $ error "courseParticipantCourse does not match cid"
lift $ deregisterParticipant courseParticipantUser course lift $ deregisterParticipant courseParticipantUser course
case deregisterSelfImposed of
Just (reason, noShow)
| is _Just courseParticipantAllocated -> lift $ do
insert_ $ AllocationDeregister uid (Just cid) now (Just reason)
updateBy (UniqueParticipant uid cid) [ CourseParticipantState =. CourseParticipantInactive noShow ]
let recordNoShow eId = do
didRecord <- is _Just <$> insertUnique ExamResult
{ examResultExam = eId
, examResultUser = uid
, examResultResult = ExamNoShow
, examResultLastChanged = now
}
when didRecord $
audit $ TransactionExamResultEdit eId uid
when noShow . runConduit $ selectKeys [ ExamCourse ==. cid ] [] .| C.mapM_ recordNoShow
_other -> return ()
return 1 return 1
addMessageI Success $ MsgCourseUsersDeregistered nrDel addMessageI Success $ MsgCourseUsersDeregistered nrDel
redirect $ CourseR tid ssh csh CUsersR redirect $ CourseR tid ssh csh CUsersR
@ -766,10 +745,8 @@ postCUsersR tid ssh csh = do
] ]
[ CourseParticipantState =. CourseParticipantActive [ CourseParticipantState =. CourseParticipantActive
, CourseParticipantRegistration =. now , CourseParticipantRegistration =. now
, CourseParticipantAllocated =. Nothing
] ]
guard $ didUpdate > 0 guard $ didUpdate > 0
lift $ deleteWhere [ AllocationDeregisterCourse ==. Just cid, AllocationDeregisterUser ==. uid ]
lift . audit $ TransactionCourseParticipantEdit cid uid lift . audit $ TransactionCourseParticipantEdit cid uid
return $ Sum didUpdate return $ Sum didUpdate
addMessageI Success $ MsgCourseUsersStateSet nrSet addMessageI Success $ MsgCourseUsersStateSet nrSet

View File

@ -141,12 +141,10 @@ postEAddUserR tid ssh csh examn = do
{ courseParticipantCourse = cid { courseParticipantCourse = cid
, courseParticipantUser = uid , courseParticipantUser = uid
, courseParticipantRegistration = now , courseParticipantRegistration = now
, courseParticipantAllocated = Nothing
, courseParticipantState = CourseParticipantActive , courseParticipantState = CourseParticipantActive
, .. , ..
} }
[ CourseParticipantRegistration =. now [ CourseParticipantRegistration =. now
, CourseParticipantAllocated =. Nothing
, CourseParticipantState =. CourseParticipantActive , CourseParticipantState =. CourseParticipantActive
] ]
lift . lift . audit $ TransactionCourseParticipantEdit cid uid lift . lift . audit $ TransactionCourseParticipantEdit cid uid

Some files were not shown because too many files have changed in this diff Show More