feat(allocations): create & edit, list & download matching logs

This commit is contained in:
Gregor Kleen 2021-06-08 00:47:47 +02:00
parent 0d6b1921fb
commit 5320a4fe98
21 changed files with 604 additions and 7 deletions

View File

@ -761,7 +761,7 @@ section
background-color: hsla($hue, 75%, 50%, $opacity) !important
.uuid, .pseudonym, .ldap-primary-key, .email, .file-path, .metric-value, .metric-label, .workflow-payload--text, .cryptoid
.uuid, .pseudonym, .ldap-primary-key, .email, .file-path, .metric-value, .metric-label, .workflow-payload--text, .cryptoid, .allocation-matching-fingerprint
font-family: var(--font-monospace)
.shown

View File

@ -168,3 +168,69 @@ 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
AllocationFormTerm: Semester
AllocationFormSchool: Institut
AllocationFormShorthand: Kürzel
AllocationFormName !ident-ok: Name
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 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

View File

@ -167,3 +167,69 @@ 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
AllocationFormTerm: Term
AllocationFormSchool: Department
AllocationFormShorthand: Shorthand
AllocationFormName: Name
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

View File

@ -16,6 +16,10 @@ BreadcrumbCourseFavourite: Favorisieren
BreadcrumbCourse: Kurs
BreadcrumbAllocationRegister: Teilnahme registrieren
BreadcrumbAllocation: Zentralanmeldung
BreadcrumbAllocationNew: Neue Zentralanmeldung
BreadcrumbAllocationEdit: Bearbeiten
BreadcrumbAllocationMatchings: Verteilungen
BreadcrumbAllocationMatchingLog: Verteilungs-Protokoll
BreadcrumbTerm !ident-ok: Semester
BreadcrumbSchool: Institut
BreadcrumbUser: Benutzer:in

View File

@ -16,6 +16,10 @@ BreadcrumbCourseFavourite: Favourite
BreadcrumbCourse: Course
BreadcrumbAllocationRegister: Register participation
BreadcrumbAllocation: Central allocation
BreadcrumbAllocationNew: New Allocation
BreadcrumbAllocationEdit: Edit
BreadcrumbAllocationMatchings: Matchings
BreadcrumbAllocationMatchingLog: Matching log
BreadcrumbTerm: Semester
BreadcrumbSchool: Department
BreadcrumbUser: User
@ -178,4 +182,4 @@ BreadcrumbSheetNew: Create new exercise sheet
BreadcrumbSheetCurrent: Current exercise sheet
BreadcrumbSheetOldUnassigned: Submissions without corrector
BreadcrumbLogin: Login
BreadcrumbNews: News
BreadcrumbNews: News

View File

@ -25,6 +25,9 @@ MenuProfile: Anpassen
MenuLogin !ident-ok: Login
MenuLogout !ident-ok: Logout
MenuAllocationList: Zentralanmeldungen
MenuAllocationNew: Neue Zentralanmeldung anlegen
MenuAllocationEdit: Zentralanmeldung bearbeiten
MenuAllocationMatchings: Verteilungen
MenuCourseList: Kurse
MenuCourseMembers: Kursteilnehmer:innen
MenuCourseAddMembers: KursteilnehmerInnen hinzufügen

View File

@ -25,6 +25,9 @@ MenuProfile: Settings
MenuLogin: Login
MenuLogout: Logout
MenuAllocationList: Central allocations
MenuAllocationNew: Create new allocation
MenuAllocationEdit: Edit allocation
MenuAllocationMatchings: Matchings
MenuCourseList: Courses
MenuCourseMembers: Participants
MenuCourseAddMembers: Add participants

5
routes
View File

@ -154,8 +154,10 @@
/delete SWWDeleteR 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
@ -163,6 +165,9 @@
/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/#TermId/#SchoolId ParticipantsR GET !evaluation

View File

@ -56,6 +56,7 @@ decCryptoIDs [ ''SubmissionId
, ''WorkflowInstanceId
, ''WorkflowWorkflowId
, ''MaterialFileId
, ''AllocationMatchingId
]
type instance CryptoIDNamespace a WorkflowStateIndex = "WorkflowStateIndex"

View File

@ -140,6 +140,11 @@ maybeToMessage :: ToMessage m => Text -> Maybe m -> Text -> Text
maybeToMessage _ Nothing _ = mempty
maybeToMessage before (Just x) after = before <> toMessage x <> after
newtype ShortTermIdentifier = ShortTermIdentifier TermIdentifier
deriving stock (Eq, Ord, Read, Show)
-- Messages creates type UniWorXMessage and RenderMessage UniWorX instance
mkMessage ''UniWorX "messages/uniworx/misc" "de-de-formal"
mkMessageAddition ''UniWorX "Test" "messages/uniworx/test" "de-de-formal"
@ -190,8 +195,6 @@ instance RenderMessage UniWorX TermIdentifier where
Winter -> renderMessage' $ MsgWinterTerm year
where renderMessage' = renderMessage foundation ls
newtype ShortTermIdentifier = ShortTermIdentifier TermIdentifier
deriving stock (Eq, Ord, Read, Show)
instance RenderMessage UniWorX ShortTermIdentifier where
renderMessage foundation ls (ShortTermIdentifier TermIdentifier{..}) = case season of
Summer -> renderMessage' $ MsgSummerTermShort year

View File

@ -191,11 +191,15 @@ breadcrumb (TermSchoolCourseListR tid ssh) = useRunDB . maybeT (i18nCrumb MsgBre
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
@ -1444,6 +1448,19 @@ pageActions TermShowR = do
, 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
@ -1478,6 +1495,26 @@ pageActions (AllocationR tid ssh ash AShowR) = return
}
, 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

View File

@ -44,6 +44,7 @@ deriving instance Generic GlobalWorkflowInstanceR
deriving instance Generic GlobalWorkflowWorkflowR
deriving instance Generic SchoolWorkflowInstanceR
deriving instance Generic SchoolWorkflowWorkflowR
deriving instance Generic AMatchingR
deriving instance Generic (Route UniWorX)
instance Ord (Route Auth) where
@ -70,6 +71,7 @@ deriving instance Ord GlobalWorkflowInstanceR
deriving instance Ord GlobalWorkflowWorkflowR
deriving instance Ord SchoolWorkflowInstanceR
deriving instance Ord SchoolWorkflowWorkflowR
deriving instance Ord AMatchingR
deriving instance Ord (Route UniWorX)
data RouteChildren

View File

@ -225,6 +225,7 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
, verifyCourseNews
, verifyWorkflowWorkflow
, verifyMaterialVideo
, verifyAllocationMatchingLog
]
where
normalizeRender :: Route UniWorX -> WriterT Any (ReaderT SqlReadBackend (HandlerFor UniWorX)) (Route UniWorX)
@ -359,6 +360,14 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .
MaterialFile{materialFileMaterial} <- lift . lift $ get404 mfId
Material{materialName, materialCourse} <- lift . lift $ get404 materialFileMaterial
Course{courseTerm, courseSchool, courseShorthand} <- lift . lift $ get404 materialCourse
let newRoute = CMaterialR courseTerm courseSchool courseShorthand materialName (MVideoR cID)
let newRoute = CMaterialR courseTerm courseSchool courseShorthand materialName $ MVideoR cID
tell . Any $ route /= 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

@ -12,3 +12,6 @@ import Handler.Allocation.AddUser 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

@ -0,0 +1,76 @@
module Handler.Allocation.Edit
( getAEditR, postAEditR
) where
import Import
import Handler.Utils
import Handler.Allocation.Form
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
, 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
, 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

@ -0,0 +1,171 @@
module Handler.Allocation.Form
( AllocationForm(..)
, allocationForm
) where
import Import
import Handler.Utils
import qualified Database.Esqueleto as E
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Control.Monad.State.Class as State
data AllocationForm = AllocationForm
{ afTerm :: TermId
, afSchool :: SchoolId
, afShorthand :: AllocationShorthand
, afName :: AllocationName
, 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
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.||. t E.^. TermActive
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)
muid <- maybeAuthId
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
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)
<* 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
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 dayOffset = (diffDays `on` termLectureStart) newTerm oldTerm
addTime = addLocalDays dayOffset
return AllocationForm
{ afTerm = tid
, afSchool = ssh
, afShorthand = ash
, afName = allocationName
, 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

@ -0,0 +1,39 @@
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

@ -0,0 +1,60 @@
module Handler.Allocation.New
( getAllocationNewR, postAllocationNewR
) where
import Import
import Handler.Utils
import Handler.Allocation.Form
import qualified Crypto.Random as Crypto
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
, 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

@ -12,6 +12,7 @@ module Handler.Utils.DateTime
, getTimeLocale, getDateTimeFormat
, getDateTimeFormatter
, validDateTimeFormats, dateTimeFormatOptions
, addLocalDays
, addOneWeek, addWeeks
, weeksToAdd
, setYear
@ -222,11 +223,14 @@ addOneWeek :: UTCTime -> UTCTime
addOneWeek = addWeeks 1
addWeeks :: Integer -> UTCTime -> UTCTime
addWeeks n utct = localTimeToUTCTZ appTZ newLocal
addWeeks = addLocalDays . (* 7)
addLocalDays :: Integer -> UTCTime -> UTCTime
addLocalDays n utct = localTimeToUTCTZ appTZ newLocal
where
oldLocal = utcToLocalTime utct
oldDay = localDay oldLocal
newDay = addDays (7*n) oldDay
newDay = addDays n oldDay
newLocal = oldLocal { localDay = newDay }
weeksToAdd :: UTCTime -> UTCTime -> Integer

View File

@ -38,8 +38,10 @@ import qualified Data.Conduit.List as C (mapMaybe, mapMaybeM)
import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
import qualified Database.Esqueleto.Internal.Internal as E (SqlSelect)
import qualified Data.Set as Set
import qualified Data.Sequence as Seq
import Data.Map ((!), (!?))
import qualified Data.Map as Map
@ -1515,6 +1517,21 @@ optionsPersistCryptoId filts ords toDisplay = do
ents <- runDB $ selectList filts ords
optionsCryptoIdF ents (return . entityKey) (return . toDisplay . entityVal)
mkOptionsE :: forall a r b msg.
( RenderMessage UniWorX msg
, E.SqlSelect a r
)
=> E.SqlQuery a
-> (r -> YesodDB UniWorX Text)
-> (r -> YesodDB UniWorX msg)
-> (r -> YesodDB UniWorX b)
-> YesodDB UniWorX (OptionList b)
mkOptionsE query toExternal toDisplay toInternal = do
mr <- getMessageRender
let toOption x = Option <$> (mr <$> toDisplay x) <*> toInternal x <*> toExternal x
fmap (mkOptionList . toList) . runConduit $
E.selectSource query .| C.mapM toOption .| C.foldMap Seq.singleton
optionsCryptoIdE :: forall backend a msg.
( HasCryptoUUID (Key a) (HandlerFor UniWorX)
, RenderMessage UniWorX msg

View File

@ -0,0 +1,24 @@
$if null matchings
<p .explanation>
_{MsgAllocationMatchingsNone}
$else
<div .scrolltable .scrolltable--bordered>
<table .table .table--hover .table--striped>
<thead>
<tr .table__row--head>
<th .table__th>
_{MsgAllocationMatchingsTime}
<th .table__th>
_{MsgAllocationMatchingsFingerprint}
<th .table__th>
_{MsgAllocationMatchingsLog}
<tbody>
$forall (cID, AllocationMatching{allocationMatchingTime, allocationMatchingFingerprint}) <- matchings
<tr .table__row>
<td .table__td>
^{formatTimeW SelFormatDateTime allocationMatchingTime}
<td .table__td .allocation-matching-fingerprint>
#{showFingerprint allocationMatchingFingerprint}
<td .table__td>
<a href=@{AllocationR tid ssh ash (AMatchingR cID AMLogR)}>
_{MsgAllocationMatchingsLog}