feat(exams): accept/reset computed results
This commit is contained in:
parent
ea5a398bab
commit
72342f1393
@ -1449,8 +1449,13 @@ ExamSynchronised: Synchronisiert
|
|||||||
ExamUsersHeading: Prüfungsteilnehmer
|
ExamUsersHeading: Prüfungsteilnehmer
|
||||||
ExamUserDeregister: Teilnehmer von Prüfung abmelden
|
ExamUserDeregister: Teilnehmer von Prüfung abmelden
|
||||||
ExamUserAssignOccurrence: Termin/Raum zuweisen
|
ExamUserAssignOccurrence: Termin/Raum zuweisen
|
||||||
|
ExamUserAcceptComputedResult: Berechnetes Prüfungsergebnis übernehmen
|
||||||
|
ExamUserResetToComputedResult: Prüfungsergebnis zurücksetzen
|
||||||
|
ExamUserResetBonus: Auch Bonuspunkte zurücksetzen
|
||||||
ExamUsersDeregistered count@Int64: #{show count} Teilnehmer von der Prüfung abgemeldet
|
ExamUsersDeregistered count@Int64: #{show count} Teilnehmer von der Prüfung abgemeldet
|
||||||
ExamUsersOccurrenceUpdated count@Int64: Termin/Raum für #{show count} Teilnehmer gesetzt
|
ExamUsersOccurrenceUpdated count@Int64: Termin/Raum für #{show count} Teilnehmer gesetzt
|
||||||
|
ExamUsersResultsAccepted count@Int64: Prüfungsergebnis für #{show count} Teilnehmer übernommen
|
||||||
|
ExamUsersResultsReset count@Int64: Prüfungsergebnis für #{show count} Teilnehmer zurückgesetzt
|
||||||
|
|
||||||
ExamUserSynchronised: Synchronisiert
|
ExamUserSynchronised: Synchronisiert
|
||||||
ExamUserSyncOfficeName: Name
|
ExamUserSyncOfficeName: Name
|
||||||
|
|||||||
@ -295,6 +295,8 @@ examUserTableCsvHeader allBoni doBonus pNames = Csv.header $
|
|||||||
|
|
||||||
data ExamUserAction = ExamUserDeregister
|
data ExamUserAction = ExamUserDeregister
|
||||||
| ExamUserAssignOccurrence
|
| ExamUserAssignOccurrence
|
||||||
|
| ExamUserAcceptComputedResult
|
||||||
|
| ExamUserResetToComputedResult
|
||||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
instance Universe ExamUserAction
|
instance Universe ExamUserAction
|
||||||
@ -304,6 +306,10 @@ embedRenderMessage ''UniWorX ''ExamUserAction id
|
|||||||
|
|
||||||
data ExamUserActionData = ExamUserDeregisterData
|
data ExamUserActionData = ExamUserDeregisterData
|
||||||
| ExamUserAssignOccurrenceData (Maybe ExamOccurrenceId)
|
| ExamUserAssignOccurrenceData (Maybe ExamOccurrenceId)
|
||||||
|
| ExamUserAcceptComputedResultData
|
||||||
|
| ExamUserResetToComputedResultData
|
||||||
|
{ examUserResetBonus :: Bool
|
||||||
|
}
|
||||||
|
|
||||||
data ExamUserCsvActionClass
|
data ExamUserCsvActionClass
|
||||||
= ExamUserCsvCourseRegister
|
= ExamUserCsvCourseRegister
|
||||||
@ -380,7 +386,7 @@ embedRenderMessage ''UniWorX ''ExamUserCsvException id
|
|||||||
getEUsersR, postEUsersR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
|
getEUsersR, postEUsersR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
|
||||||
getEUsersR = postEUsersR
|
getEUsersR = postEUsersR
|
||||||
postEUsersR tid ssh csh examn = do
|
postEUsersR tid ssh csh examn = do
|
||||||
((registrationResult, examUsersTable), Entity eId _) <- runDB $ do
|
(((Any computedValues, registrationResult), examUsersTable), Entity eId examVal, bonus) <- runDB $ do
|
||||||
exam@(Entity eid examVal@Exam{..}) <- fetchExam tid ssh csh examn
|
exam@(Entity eid examVal@Exam{..}) <- fetchExam tid ssh csh examn
|
||||||
examParts <- selectList [ExamPartExam ==. eid] [Asc ExamPartName]
|
examParts <- selectList [ExamPartExam ==. eid] [Asc ExamPartName]
|
||||||
bonus <- examBonus exam
|
bonus <- examBonus exam
|
||||||
@ -403,10 +409,12 @@ postEUsersR tid ssh csh examn = do
|
|||||||
resultAutomaticExamResult' :: Fold ExamUserTableData ExamResultGrade
|
resultAutomaticExamResult' :: Fold ExamUserTableData ExamResultGrade
|
||||||
resultAutomaticExamResult' = resultAutomaticExamResult examVal bonus
|
resultAutomaticExamResult' = resultAutomaticExamResult examVal bonus
|
||||||
|
|
||||||
automaticCell :: forall msg m a r.
|
automaticCell :: forall msg m a b r.
|
||||||
( RenderMessage UniWorX msg
|
( RenderMessage UniWorX msg
|
||||||
, IsDBTable m a
|
, IsDBTable m a
|
||||||
, Eq msg
|
, Eq msg
|
||||||
|
, Monoid b
|
||||||
|
, a ~ (Any, b)
|
||||||
)
|
)
|
||||||
=> Getting (Endo [Either msg msg]) r (Either msg msg)
|
=> Getting (Endo [Either msg msg]) r (Either msg msg)
|
||||||
-> r
|
-> r
|
||||||
@ -414,12 +422,12 @@ postEUsersR tid ssh csh examn = do
|
|||||||
automaticCell l r = case toListOf l r of
|
automaticCell l r = case toListOf l r of
|
||||||
[] -> mempty
|
[] -> mempty
|
||||||
(Left auto : _)
|
(Left auto : _)
|
||||||
-> i18nCell auto & cellAttrs <>~ [("class", "table__td--automatic")]
|
-> i18nCell auto & cellAttrs <>~ [("class", "table__td--automatic")] & tellCell (Any True, mempty)
|
||||||
(Right man : others)
|
(Right man : others)
|
||||||
| all ((== man) . either id id) others
|
| all ((== man) . either id id) others
|
||||||
-> i18nCell man
|
-> i18nCell man
|
||||||
| otherwise
|
| otherwise
|
||||||
-> i18nCell man & cellAttrs <>~ [("class", "table__td--overriden")]
|
-> i18nCell man & cellAttrs <>~ [("class", "table__td--overriden")] & tellCell (Any True, mempty)
|
||||||
|
|
||||||
csvName <- getMessageRender <*> pure (MsgExamUserCsvName tid ssh csh examn)
|
csvName <- getMessageRender <*> pure (MsgExamUserCsvName tid ssh csh examn)
|
||||||
|
|
||||||
@ -478,7 +486,7 @@ postEUsersR tid ssh csh examn = do
|
|||||||
]
|
]
|
||||||
|
|
||||||
dbtColonnade = mconcat $ catMaybes
|
dbtColonnade = mconcat $ catMaybes
|
||||||
[ pure $ dbSelect (applying _2) id $ return . view (resultExamRegistration . _entityKey)
|
[ pure $ dbSelect (_2 . applying _2) _1 $ return . view (resultExamRegistration . _entityKey)
|
||||||
, pure $ colUserNameLink (CourseR tid ssh csh . CUserR)
|
, pure $ colUserNameLink (CourseR tid ssh csh . CUserR)
|
||||||
, pure colUserMatriclenr
|
, pure colUserMatriclenr
|
||||||
, pure $ colField resultStudyField
|
, pure $ colField resultStudyField
|
||||||
@ -562,20 +570,26 @@ postEUsersR tid ssh csh examn = do
|
|||||||
, dbParamsFormAdditional = \csrf -> do
|
, dbParamsFormAdditional = \csrf -> do
|
||||||
let
|
let
|
||||||
actionMap :: Map ExamUserAction (AForm Handler ExamUserActionData)
|
actionMap :: Map ExamUserAction (AForm Handler ExamUserActionData)
|
||||||
actionMap = Map.fromList
|
actionMap = mconcat
|
||||||
[ ( ExamUserDeregister
|
[ singletonMap ExamUserDeregister $
|
||||||
, pure ExamUserDeregisterData
|
pure ExamUserDeregisterData
|
||||||
)
|
, singletonMap ExamUserAssignOccurrence $
|
||||||
, ( ExamUserAssignOccurrence
|
ExamUserAssignOccurrenceData
|
||||||
, ExamUserAssignOccurrenceData
|
|
||||||
<$> aopt (examOccurrenceField eid) (fslI MsgExamOccurrence) (Just Nothing)
|
<$> aopt (examOccurrenceField eid) (fslI MsgExamOccurrence) (Just Nothing)
|
||||||
)
|
, bool mempty computeActionMap $ is _Just examGradingRule
|
||||||
|
]
|
||||||
|
computeActionMap = mconcat
|
||||||
|
[ singletonMap ExamUserAcceptComputedResult $
|
||||||
|
pure ExamUserAcceptComputedResultData
|
||||||
|
, singletonMap ExamUserResetToComputedResult $
|
||||||
|
ExamUserResetToComputedResultData
|
||||||
|
<$> bool (pure True) (apopt checkBoxField (fslI MsgExamUserResetBonus) (Just True)) (is _Just examBonusRule)
|
||||||
]
|
]
|
||||||
(res, formWgt) <- multiActionM actionMap (fslI MsgAction) Nothing csrf
|
(res, formWgt) <- multiActionM actionMap (fslI MsgAction) Nothing csrf
|
||||||
let formRes = (, mempty) . First . Just <$> res
|
let formRes = (, mempty) . First . Just <$> res
|
||||||
return (formRes, formWgt)
|
return (formRes, formWgt)
|
||||||
, dbParamsFormEvaluate = liftHandlerT . runFormPost
|
, dbParamsFormEvaluate = liftHandlerT . runFormPost
|
||||||
, dbParamsFormResult = id
|
, dbParamsFormResult = _2
|
||||||
, dbParamsFormIdent = def
|
, dbParamsFormIdent = def
|
||||||
}
|
}
|
||||||
dbtIdent :: Text
|
dbtIdent :: Text
|
||||||
@ -992,21 +1006,21 @@ postEUsersR tid ssh csh examn = do
|
|||||||
examUsersDBTableValidator = def & defaultSorting [SortAscBy "user-name"]
|
examUsersDBTableValidator = def & defaultSorting [SortAscBy "user-name"]
|
||||||
& defaultPagesize PagesizeAll
|
& defaultPagesize PagesizeAll
|
||||||
|
|
||||||
postprocess :: FormResult (First ExamUserActionData, DBFormResult ExamRegistrationId Bool ExamUserTableData) -> FormResult (ExamUserActionData, Set ExamRegistrationId)
|
postprocess :: FormResult (First ExamUserActionData, DBFormResult ExamRegistrationId (Bool, ExamUserTableData) ExamUserTableData) -> FormResult (ExamUserActionData, Map ExamRegistrationId ExamUserTableData)
|
||||||
postprocess inp = do
|
postprocess inp = do
|
||||||
(First (Just act), regMap) <- inp
|
(First (Just act), regMap) <- inp
|
||||||
let regSet = Map.keysSet . Map.filter id $ getDBFormResult (const False) regMap
|
let regMap' = Map.mapMaybe (uncurry guardOn) $ getDBFormResult (False,) regMap
|
||||||
return (act, regSet)
|
return (act, regMap')
|
||||||
(, exam) . over _1 postprocess <$> dbTable examUsersDBTableValidator examUsersDBTable
|
(, exam, bonus) . over (_1 . _2) postprocess <$> dbTable examUsersDBTableValidator examUsersDBTable
|
||||||
|
|
||||||
formResult registrationResult $ \case
|
formResult registrationResult $ \case
|
||||||
(ExamUserDeregisterData, selectedRegistrations) -> do
|
(ExamUserDeregisterData, Map.keysSet -> selectedRegistrations) -> do
|
||||||
nrDel <- runDB $ deleteWhereCount
|
nrDel <- runDB $ deleteWhereCount
|
||||||
[ ExamRegistrationId <-. Set.toList selectedRegistrations
|
[ ExamRegistrationId <-. Set.toList selectedRegistrations
|
||||||
]
|
]
|
||||||
addMessageI Success $ MsgExamUsersDeregistered nrDel
|
addMessageI Success $ MsgExamUsersDeregistered nrDel
|
||||||
redirect $ CExamR tid ssh csh examn EUsersR
|
redirect $ CExamR tid ssh csh examn EUsersR
|
||||||
(ExamUserAssignOccurrenceData occId, selectedRegistrations) -> do
|
(ExamUserAssignOccurrenceData occId, Map.keysSet -> selectedRegistrations) -> do
|
||||||
nrUpdated <- runDB $ updateWhereCount
|
nrUpdated <- runDB $ updateWhereCount
|
||||||
[ ExamRegistrationId <-. Set.toList selectedRegistrations
|
[ ExamRegistrationId <-. Set.toList selectedRegistrations
|
||||||
]
|
]
|
||||||
@ -1014,9 +1028,67 @@ postEUsersR tid ssh csh examn = do
|
|||||||
]
|
]
|
||||||
addMessageI Success $ MsgExamUsersOccurrenceUpdated nrUpdated
|
addMessageI Success $ MsgExamUsersOccurrenceUpdated nrUpdated
|
||||||
redirect $ CExamR tid ssh csh examn EUsersR
|
redirect $ CExamR tid ssh csh examn EUsersR
|
||||||
|
(ExamUserAcceptComputedResultData, Map.elems -> rows) -> do
|
||||||
|
nrAccepted <- fmap (getSum . fold) . runDB . forM rows . runReaderT $ do
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
uid <- view $ resultUser . _entityKey
|
||||||
|
hasResult <- asks $ has resultExamResult
|
||||||
|
hasBonus <- asks $ has resultExamBonus
|
||||||
|
autoResult <- preview $ resultAutomaticExamResult examVal bonus
|
||||||
|
autoBonus <- preview $ resultAutomaticExamBonus examVal bonus
|
||||||
|
lift $ if
|
||||||
|
| not hasResult
|
||||||
|
, Just examResultResult <- autoResult
|
||||||
|
-> do
|
||||||
|
if
|
||||||
|
| Just examBonusBonus <- autoBonus
|
||||||
|
, not hasBonus
|
||||||
|
-> do
|
||||||
|
insert_ ExamBonus
|
||||||
|
{ examBonusExam = eId
|
||||||
|
, examBonusUser = uid
|
||||||
|
, examBonusLastChanged = now
|
||||||
|
, ..
|
||||||
|
}
|
||||||
|
audit $ TransactionExamBonusEdit eId uid
|
||||||
|
| otherwise
|
||||||
|
-> return ()
|
||||||
|
|
||||||
|
insert_ ExamResult
|
||||||
|
{ examResultExam = eId
|
||||||
|
, examResultUser = uid
|
||||||
|
, examResultLastChanged = now
|
||||||
|
, ..
|
||||||
|
}
|
||||||
|
audit $ TransactionExamResultEdit eId uid
|
||||||
|
return $ Sum 1
|
||||||
|
| otherwise
|
||||||
|
-> return mempty
|
||||||
|
addMessageI Success $ MsgExamUsersResultsAccepted nrAccepted
|
||||||
|
redirect $ CExamR tid ssh csh examn EUsersR
|
||||||
|
(ExamUserResetToComputedResultData{..}, Map.elems -> rows) -> do
|
||||||
|
nrReset <- fmap (getSum . fold) . runDB . forM rows . runReaderT $ do
|
||||||
|
uid <- view $ resultUser . _entityKey
|
||||||
|
lift $ do
|
||||||
|
when examUserResetBonus $ do
|
||||||
|
bonusId' <- getKeyBy $ UniqueExamBonus eId uid
|
||||||
|
whenIsJust bonusId' $ \bonusId -> do
|
||||||
|
delete bonusId
|
||||||
|
audit $ TransactionExamBonusDeleted eId uid
|
||||||
|
|
||||||
|
result <- getKeyBy $ UniqueExamResult eId uid
|
||||||
|
case result of
|
||||||
|
Just resId -> do
|
||||||
|
delete resId
|
||||||
|
audit $ TransactionExamResultDeleted eId uid
|
||||||
|
return $ Sum 1
|
||||||
|
Nothing -> return mempty
|
||||||
|
addMessageI Success $ MsgExamUsersResultsReset nrReset
|
||||||
|
redirect $ CExamR tid ssh csh examn EUsersR
|
||||||
|
|
||||||
closeWgt <- examCloseWidget (SomeRoute $ CExamR tid ssh csh examn EUsersR) eId
|
closeWgt <- examCloseWidget (SomeRoute $ CExamR tid ssh csh examn EUsersR) eId
|
||||||
|
|
||||||
siteLayoutMsg (prependCourseTitle tid ssh csh MsgExamUsersHeading) $ do
|
siteLayoutMsg (prependCourseTitle tid ssh csh MsgExamUsersHeading) $ do
|
||||||
setTitleI $ prependCourseTitle tid ssh csh MsgExamUsersHeading
|
setTitleI $ prependCourseTitle tid ssh csh MsgExamUsersHeading
|
||||||
|
let computedValuesTip = $(i18nWidgetFile "exam-users/computed-values-tip")
|
||||||
$(widgetFile "exam-users")
|
$(widgetFile "exam-users")
|
||||||
|
|||||||
@ -596,7 +596,7 @@ section {
|
|||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 10px 20px 20px;
|
padding: 10px 20px 20px;
|
||||||
margin: 40px 0;
|
margin: 40px auto;
|
||||||
box-shadow: 0 0 4px 2px inset currentColor;
|
box-shadow: 0 0 4px 2px inset currentColor;
|
||||||
padding-left: 100px;
|
padding-left: 100px;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
@ -608,7 +608,7 @@ section {
|
|||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
font-family: "Font Awesome 5 Free";
|
font-family: "Font Awesome 5 Free";
|
||||||
font-weight: 900;
|
font-weight: 600;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -623,6 +623,12 @@ section {
|
|||||||
.notification__content {
|
.notification__content {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
|
color: var(--color-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.notification--broad {
|
||||||
|
max-width: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,4 +2,6 @@ $newline never
|
|||||||
<section>
|
<section>
|
||||||
^{closeWgt}
|
^{closeWgt}
|
||||||
<section>
|
<section>
|
||||||
|
$if computedValues
|
||||||
|
^{computedValuesTip}
|
||||||
^{examUsersTable}
|
^{examUsersTable}
|
||||||
|
|||||||
24
templates/i18n/exam-users/computed-values-tip/de.hamlet
Normal file
24
templates/i18n/exam-users/computed-values-tip/de.hamlet
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
$newline never
|
||||||
|
<div .notification .notification-warning .fa-exclamation-triangle .notification--broad>
|
||||||
|
<div .notification__content>
|
||||||
|
<p>
|
||||||
|
Die Tabelle enthält Werte, die automatisch berechnet wurden.
|
||||||
|
<p>
|
||||||
|
Automatisch berechnete Werte (Bonus und Prüfungsergebnis) werden weder dem #
|
||||||
|
entsprechenden Teilnehmer angezeigt, noch an das Prüfungsamt gemeldet #
|
||||||
|
bevor sie manuell übernommen wurden.<br />
|
||||||
|
Hierzu können Sie die Aktion „Berechnetes Prüfungsergebnis übernehmen“ #
|
||||||
|
verwenden.
|
||||||
|
<p>
|
||||||
|
Sie können die automatisch berechneten Werte auch manuell (via CSV-Import) #
|
||||||
|
überschreiben.<br />
|
||||||
|
Wenn die so gesetzten Werte nicht den automatisch Berechneten entsprechen #
|
||||||
|
sind sie <i>inkonsistent</i>.
|
||||||
|
<p>
|
||||||
|
Automatisch berechnete Werte sind gekennzeichnet wie folgt:
|
||||||
|
|
||||||
|
<table style="font-weight: normal">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatisch berechnet
|
||||||
|
<td style="padding: 0 7px" .table__td>Normaler Wert
|
||||||
|
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inkonsistent
|
||||||
Reference in New Issue
Block a user