131 lines
5.4 KiB
Haskell
131 lines
5.4 KiB
Haskell
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 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.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)
|
|
|
|
|
|
restrictCourses :: (MonadHandler m, HandlerSite m ~ UniWorX) => AllocationId -> AForm m (Maybe (Set CourseId))
|
|
restrictCourses aId = hoistAForm liftHandler $
|
|
optionalActionA selectCourses (fslI MsgAllocationRestrictCourses & setTooltip MsgAllocationRestrictCoursesTip) (Just False)
|
|
where
|
|
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, allocMatching, allocLog) <- computeAllocation aId acfRestrictCourses
|
|
tellSessionJson SessionAllocationResults . SessionDataAllocationResults $
|
|
Map.singleton (tid, ssh, ash) (now, allocFp, allocMatching, allocLog)
|
|
addMessageI Success MsgAllocationComputed
|
|
redirect $ AllocationR tid ssh ash AUsersR -- Redirect aborts transaction for safety
|
|
|
|
return (aEnt, formRes)
|
|
|
|
siteLayoutMsg MsgMenuAllocationCompute $ do
|
|
setTitleI MsgMenuAllocationCompute
|
|
|
|
wrapForm' BtnAllocationCompute computeFormView def
|
|
{ formEncoding = computeFormEnctype
|
|
}
|