chore(tutorial): assigning exam occurrences may check duplicate examiners

towards #2347
This commit is contained in:
Steffen Jost 2025-02-20 14:54:29 +01:00
parent 10a4d11022
commit bc0dbd26fc
5 changed files with 43 additions and 21 deletions

View File

@ -138,7 +138,8 @@ CourseUserNoTutorialsDeregistered: Teilnehmer:in ist zu keinem der gewählten Ku
CourseUserTutorials: Angemeldete Kurse CourseUserTutorials: Angemeldete Kurse
CourseUserExams: Angemeldete Prüfungen CourseUserExams: Angemeldete Prüfungen
CourseUserExamOccurrences: Prüfungstermin CourseUserExamOccurrences: Prüfungstermin
CourseUserExamOccurrenceOverride: Ggf. vorhanden Prüfungstermin überschreiben CourseUserExamOccurrenceOverride: Ggf. vorhandenen Prüfungstermin überschreiben
CourseUserExamOccurrenceAgainExaminer: Ggf. vorherige Prüfer erneut erlauben
CourseUserSheets: Übungsblätter CourseUserSheets: Übungsblätter
CsvColumnUserName: Voller Name des/der Teilnehmers/Teilnehmerin CsvColumnUserName: Voller Name des/der Teilnehmers/Teilnehmerin
CsvColumnUserMatriculation: AVS Nummer des/der Teilnehmers/Teilnehmerin CsvColumnUserMatriculation: AVS Nummer des/der Teilnehmers/Teilnehmerin

View File

@ -139,6 +139,7 @@ CourseUserTutorials: Registered courses
CourseUserExams: Registered exams CourseUserExams: Registered exams
CourseUserExamOccurrences: Exam occurrence CourseUserExamOccurrences: Exam occurrence
CourseUserExamOccurrenceOverride: Override other registrations for this exam, if any CourseUserExamOccurrenceOverride: Override other registrations for this exam, if any
CourseUserExamOccurrenceAgainExaminer: Possibly allow previous examiners again
CourseUserSheets: Exercise sheets CourseUserSheets: Exercise sheets
CsvColumnUserName: Participant's full name CsvColumnUserName: Participant's full name
CsvColumnUserMatriculation: Participant's AVS number CsvColumnUserMatriculation: Participant's AVS number

View File

@ -87,7 +87,7 @@ ExamRoomAlreadyExists: Prüfung ist bereits eingetragen
ExamRoomName: Interne Bezeichnung ExamRoomName: Interne Bezeichnung
ExamRoomCapacity: Kapazität ExamRoomCapacity: Kapazität
ExamRoomCapacityNegative: Kapazität darf nicht negativ sein ExamRoomCapacityNegative: Kapazität darf nicht negativ sein
ExamRommCapacityInsufficient n@Int: Kapazität reicht nicht aus, nur noch #{n} Plätze verfügbar ExamRoomCapacityInsufficient n@Int: Kapazität reicht nicht aus, #{noneOneMoreDE n "keine Plätze" "nur noch ein Platz" ("nur noch " <> tshow n <> " Plätze")} verfügbar
ExamRoomTime: Termin ExamRoomTime: Termin
ExamRoomStart: Beginn ExamRoomStart: Beginn
ExamRoomEnd: Ende ExamRoomEnd: Ende

View File

@ -87,7 +87,7 @@ ExamRoomAlreadyExists: Occurrence already configured
ExamRoomName: Internal name ExamRoomName: Internal name
ExamRoomCapacity: Capacity ExamRoomCapacity: Capacity
ExamRoomCapacityNegative: Capacity may not be negative ExamRoomCapacityNegative: Capacity may not be negative
ExamRommCapacityInsufficient n@Int: Insufficient capacity, only #{n} remaining ExamRoomCapacityInsufficient n@Int: Insufficient capacity, #{noneOneMoreEN n "none" "just one" ("only " <> tshow n)} remaining
ExamRoomTime: Time ExamRoomTime: Time
ExamRoomStart: Start ExamRoomStart: Start
ExamRoomEnd: End ExamRoomEnd: End

View File

@ -32,6 +32,7 @@ import qualified Data.ByteString.Lazy as LBS
import Database.Esqueleto.Experimental ((:&)(..)) import Database.Esqueleto.Experimental ((:&)(..))
import qualified Database.Esqueleto.Experimental as E -- needs TypeApplications Lang-Pragma import qualified Database.Esqueleto.Experimental as E -- needs TypeApplications Lang-Pragma
import qualified Database.Esqueleto.Utils as E
import Handler.Course.Users import Handler.Course.Users
@ -61,8 +62,9 @@ data TutorialUserActionData
| TutorialUserSendMailData | TutorialUserSendMailData
| TutorialUserDeregisterData | TutorialUserDeregisterData
| TutorialUserAssignExamData | TutorialUserAssignExamData
{ tuOccurrenceId :: ExamOccurrenceId { tuOccurrenceId :: ExamOccurrenceId
, tuReassign :: Bool , tuExaminerAgain :: Bool
, tuReassign :: Bool
} }
deriving (Eq, Ord, Read, Show, Generic) deriving (Eq, Ord, Read, Show, Generic)
@ -160,7 +162,8 @@ postTUsersR tid ssh csh tutn = do
( TutorialUserAssignExam ( TutorialUserAssignExam
, TutorialUserAssignExamData , TutorialUserAssignExamData
<$> apopt (selectField $ pure $ mkExamOccurrenceOptions exOccs) (fslI MsgCourseUserExamOccurrences) Nothing <$> apopt (selectField $ pure $ mkExamOccurrenceOptions exOccs) (fslI MsgCourseUserExamOccurrences) Nothing
<*> apopt checkBoxField (fslI MsgCourseUserExamOccurrenceOverride) (Just False) <*> apopt checkBoxField (fslI MsgCourseUserExamOccurrenceAgainExaminer) (Just False)
<*> apopt checkBoxField (fslI MsgCourseUserExamOccurrenceOverride) (Just False)
) $ ) $
(if null qualifications then mempty else (if null qualifications then mempty else
[ ( TutorialUserRenewQualification [ ( TutorialUserRenewQualification
@ -232,23 +235,40 @@ postTUsersR tid ssh csh tutn = do
] ]
addMessageI Success $ MsgTutorialUsersDeregistered nrDel addMessageI Success $ MsgTutorialUsersDeregistered nrDel
reloadKeepGetParams croute reloadKeepGetParams croute
(TutorialUserAssignExamData{..}, selectedUsers) (TutorialUserAssignExamData{..}, setSelectedUsers)
| (Just (ExamOccurrence{..}, _, (eid,_))) <- Map.lookup tuOccurrenceId exOccs -> do | (Just (ExamOccurrence{..}, _, (eid,_))) <- Map.lookup tuOccurrenceId exOccs -> do
let n = Set.size selectedUsers assignRes <- runDB $ do
capOk <- ifNothing examOccurrenceCapacity (pure True) $ \(fromIntegral -> totalCap) -> do (Set.toList &&& Set.size -> (selectedUsers, nr_usrs)) <- if -- remove duplicate examiners, if desired
usedCap <- runDBRead $ count [ExamRegistrationOccurrence ==. Just tuOccurrenceId, ExamRegistrationUser /<-. Set.toList selectedUsers] | isJust examOccurrenceExaminer && not tuExaminerAgain -> do
let ok = totalCap - usedCap >= n conflictingUsers <- E.select $ do
unless ok $ addMessageI Error $ MsgExamRommCapacityInsufficient $ totalCap - usedCap reg :& occ <- E.from $ E.table @ExamRegistration
pure ok `E.innerJoin` E.table @ExamOccurrence
when capOk do `E.on` (\(reg :& occ) -> occ E.^. ExamOccurrenceId E.=?. reg E.^. ExamRegistrationOccurrence)
let regTemplate uid = ExamRegistration eid uid (Just tuOccurrenceId) now E.where_ $ occ E.^. ExamOccurrenceExaminer E.==. E.val examOccurrenceExaminer
nrOk <- runDB $ if tuReassign E.&&. occ E.^. ExamOccurrenceExam E.!=. E.val examOccurrenceExam
then putMany [regTemplate uid | uid <- Set.toList selectedUsers] >> pure n E.&&. (reg E.^. ExamRegistrationUser `E.in_` E.vals setSelectedUsers)
else forM (Set.toList selectedUsers) (insertUnique . regTemplate) <&> (length . catMaybes) E.orderBy [E.asc $ reg E.^. ExamRegistrationUser]
let allok = bool Warning Success $ nrOk == n E.distinct $ pure $ reg E.^. ExamRegistrationUser
addMessageI allok $ MsgTutorialUserExamAssignedFor nrOk n $ ciOriginal examOccurrenceName return $ setSelectedUsers `Set.difference` Set.fromAscList (E.unValue <$> conflictingUsers)
reloadKeepGetParams croute | otherwise -> return setSelectedUsers
runExceptT $ do
whenIsJust examOccurrenceCapacity $ \(fromIntegral -> totalCap) -> do
usedCap <- lift $ count [ExamRegistrationOccurrence ==. Just tuOccurrenceId, ExamRegistrationUser /<-. selectedUsers]
let remCap = totalCap - usedCap
when (nr_usrs > remCap) $ throwE $ MsgExamRoomCapacityInsufficient remCap
let regTemplate uid = ExamRegistration eid uid (Just tuOccurrenceId) now
lift $ if tuReassign
then putMany [regTemplate uid | uid <- selectedUsers] >> pure nr_usrs
else forM selectedUsers (insertUnique . regTemplate) <&> (length . catMaybes)
case assignRes of
Left errm -> do
addMessageI Error errm
return Nothing return Nothing
Right nrOk -> do
let total = Set.size setSelectedUsers
allok = bool Warning Success $ nrOk == total
addMessageI allok $ MsgTutorialUserExamAssignedFor nrOk total $ ciOriginal examOccurrenceName
reloadKeepGetParams croute
_other -> addMessageI Error MsgErrorUnknownFormAction >> return Nothing _other -> addMessageI Error MsgErrorUnknownFormAction >> return Nothing
case tcontent of case tcontent of