diff --git a/messages/uniworx/de-de-formal.msg b/messages/uniworx/de-de-formal.msg index 3b5a526a4..6d7a40635 100644 --- a/messages/uniworx/de-de-formal.msg +++ b/messages/uniworx/de-de-formal.msg @@ -1186,7 +1186,7 @@ MenuTutorialEdit: Tutorium editieren MenuTutorialComm: Mitteilung an Teilnehmer MenuExamList: Prüfungen MenuExamNew: Neue Prüfung anlegen -MenuExamEdit: Bearbeiten +MenuExamEdit: Prüfung bearbeiten MenuExamUsers: Teilnehmer MenuExamGrades: Prüfungsleistungen MenuExamAddMembers: Prüfungsteilnehmer hinzufügen @@ -1402,7 +1402,6 @@ ExamCorrectHeading examname@Text: Prüfungsergebnisse für #{examname} eintragen ExamCorrectHeadDate: Zeit ExamCorrectHeadParticipant: Teilnehmer -ExamCorrectHeadParticipantTooltip: ExamCorrectHeadPart exampartnum@ExamPartNumber: #{exampartnum} ExamCorrectHeadPartName exampartname@ExamPartName: #{exampartname} ExamCorrectHeadStatus: Status @@ -1411,6 +1410,8 @@ ExamCorrectButtonSend: Senden ExamCorrectErrorMultipleMatchingParticipants: Dem Identifikator konnten mehrere Prüfungsteilnehmer zugeordnet werden. ExamCorrectErrorNoMatchingParticipants: Dem Identifikator konnte kein Prüfungsteilnehmer zugeordnet werden. +ExamCorrectErrorPartResultOutOfBounds examPartNumber@ExamPartNumber: Prüfungsergebnis für Teil #{examPartNumber} ist nicht größer Null. +ExamCorrectErrorPartResultOutOfBoundsMax examPartNumber@ExamPartNumber maxPoints@Points: Prüfungsergebnis für Teil #{examPartNumber} liegt nicht zwischen 0 und #{maxPoints}. SubmissionUserInvitationAccepted shn@SheetName: Sie wurden als Mitabgebende(r) für eine Abgabe zu #{shn} eingetragen SubmissionUserInvitationDeclined shn@SheetName: Sie haben die Einladung, Mitabgebende(r) für #{shn} zu werden, abgelehnt diff --git a/messages/uniworx/en-eu.msg b/messages/uniworx/en-eu.msg index e0ae2c93b..12f3e62eb 100644 --- a/messages/uniworx/en-eu.msg +++ b/messages/uniworx/en-eu.msg @@ -1185,7 +1185,7 @@ MenuTutorialEdit: Edit tutorial MenuTutorialComm: Send course message MenuExamList: Exams MenuExamNew: Create new exam -MenuExamEdit: Edit +MenuExamEdit: Edit exam MenuExamUsers: Participants MenuExamGrades: Exam results MenuExamAddMembers: Add exam participants @@ -1408,6 +1408,8 @@ ExamCorrectButtonSend: Submit ExamCorrectErrorMultipleMatchingParticipants: This identifier matches on multiple exam participants. ExamCorrectErrorNoMatchingParticipants: This identifier does not match on any exam participant. +ExamCorrectErrorPartResultOutOfBounds examPartNumber: Exam part result for #{examPartNumber} ist not greater zero. +ExamCorrectErrorPartResultOutOfBoundsMax examPartNumber maxPoints: Exam part result for #{examPartNumber} is not between 0 and #{maxPoints}. SubmissionUserInvitationAccepted shn: You now participate in a submission for #{shn} SubmissionUserInvitationDeclined shn: You have declined the invitation to participate in a submission for #{shn} @@ -1629,6 +1631,7 @@ ExamFormCorrection: Correction ExamFormParts: Exam parts ExamCorrectors: Correctors +ExamCorrectorsTip: Correctors configured here may, after the start of the exam and until "Results visible from", enter exam part results for all exam parts and participants. ExamCorrectorAlreadyAdded: A corrector with this email address already exists ExamParts: Exam parts/questions diff --git a/src/Handler/Exam/Correct.hs b/src/Handler/Exam/Correct.hs index 37c2a24c4..35736c4c8 100644 --- a/src/Handler/Exam/Correct.hs +++ b/src/Handler/Exam/Correct.hs @@ -38,7 +38,8 @@ data CorrectInterfaceResponse , ciraMessage :: Text } | CorrectInterfaceResponseFailure - { cirfMessage :: Text + { cirfUser :: Maybe CorrectInterfaceUser + , cirfMessage :: Text } | CorrectInterfaceResponseNoOp { cirnUsers :: Set CorrectInterfaceUser @@ -103,11 +104,11 @@ postECorrectR tid ssh csh examn = do CorrectInterfaceRequest{..} <- requireCheckJsonBody - response <- runDB $ do - Entity eId Exam{..} <- fetchExam tid ssh csh examn + response <- exceptT return return . hoist runDB $ do + Entity eId Exam{..} <- lift $ fetchExam tid ssh csh examn euid <- traverse decrypt ciqUser - participantMatches <- E.select . E.from $ \(examRegistration `E.InnerJoin` user) -> do + participantMatches <- lift . E.select . E.from $ \(examRegistration `E.InnerJoin` user) -> do E.on $ examRegistration E.^. ExamRegistrationUser E.==. user E.^. UserId E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId @@ -150,10 +151,10 @@ postECorrectR tid ssh csh examn = do now <- liftIO getCurrentTime newExamPartResults <- if | Just results <- ciqResults -> iforM (toNullable results) $ \partNumber mPartResult -> do - (Entity examPartId ExamPart{..}) <- getBy404 $ UniqueExamPartNumber eId partNumber - mOldResult <- getBy $ UniqueExamPartResult examPartId uid + (Entity examPartId ExamPart{..}) <- lift . getBy404 $ UniqueExamPartNumber eId partNumber + mOldResult <- lift . getBy $ UniqueExamPartResult examPartId uid if - | Just (Entity oldId _) <- mOldResult, is _Nothing mPartResult -> do + | Just (Entity oldId _) <- mOldResult, is _Nothing mPartResult -> lift $ do delete oldId audit $ TransactionExamPartResultDeleted examPartId uid return Nothing @@ -162,29 +163,36 @@ postECorrectR tid ssh csh examn = do mNew = ExamAttended <$> mPartResult resultVal = _entityVal . _examPartResultResult in if - | mOld /= mNew -> let - -- cut off part results that exceed the maximum number of points for this exam part for now - -- TODO answer with new failure response type instead - partResult' = if - | Just maxPts <- examPartMaxPoints, maxPts < partResult -> maxPts - | otherwise -> partResult - in do - newExamPartResult <- upsert ExamPartResult + | mOld /= mNew -> do + let + partResultAcceptable = 0 <= partResult + && maybe True (partResult <=) examPartMaxPoints + guardMExceptT partResultAcceptable $ + let + msg | Just maxPoints <- examPartMaxPoints + = MsgExamCorrectErrorPartResultOutOfBoundsMax partNumber maxPoints + | otherwise + = MsgExamCorrectErrorPartResultOutOfBounds partNumber + in CorrectInterfaceResponseFailure + <$> (Just <$> userToResponse match) + <*> (getMessageRender <*> pure msg) + + newExamPartResult <- lift $ upsert ExamPartResult { examPartResultExamPart = examPartId , examPartResultUser = uid - , examPartResultResult = ExamAttended partResult' + , examPartResultResult = ExamAttended partResult , examPartResultLastChanged = now } - [ ExamPartResultResult =. ExamAttended partResult' + [ ExamPartResultResult =. ExamAttended partResult , ExamPartResultLastChanged =. now ] - audit $ TransactionExamPartResultEdit examPartId uid + lift . audit $ TransactionExamPartResultEdit examPartId uid return $ newExamPartResult ^? resultVal | otherwise -> return $ mOldResult ^? _Just . resultVal | otherwise -> return Nothing | otherwise -> return mempty - newExamResult <- do + newExamResult <- lift $ do mOldResult <- getBy $ UniqueExamResult eId uid if | Just (Entity oldId _) <- mOldResult, is _Nothing ciqGrade -> do @@ -222,7 +230,8 @@ postECorrectR tid ssh csh examn = do -- on match with no exam participant, answer with 400 | [] <- participantMatches -> return CorrectInterfaceResponseFailure - { cirfMessage = mr MsgExamCorrectErrorNoMatchingParticipants + { cirfUser = Nothing + , cirfMessage = mr MsgExamCorrectErrorNoMatchingParticipants } -- on match with multiple exam participants, answer with 400 and a set of all matches