diff --git a/messages/uniworx/categories/I18n/de-de-formal.msg b/messages/uniworx/categories/I18n/de-de-formal.msg index e3300f6aa..37d97184e 100644 --- a/messages/uniworx/categories/I18n/de-de-formal.msg +++ b/messages/uniworx/categories/I18n/de-de-formal.msg @@ -1,11 +1,3 @@ -Quarter1st year@Integer: Erstes Quartal #{year} -Quarter2nd year@Integer: Zweites Quartal #{year} -Quarter3rd year@Integer: Drittes Quartal #{year} -Quarter4th year@Integer: Viertes Quartal #{year} -Quarter1stShort year@Integer: #{year}/Q1 -Quarter2ndShort year@Integer: #{year}/Q2 -Quarter3rdShort year@Integer: #{year}/Q3 -Quarter4thShort year@Integer: #{year}/Q4 CorByProportionOnly proportion@Rational: #{rationalToFixed3 proportion} Anteile CorByProportionIncludingTutorial proportion@Rational: #{rationalToFixed3 proportion} Anteile - Tutorium CorByProportionExcludingTutorial proportion@Rational: #{rationalToFixed3 proportion} Anteile + Tutorium diff --git a/messages/uniworx/categories/I18n/en-eu.msg b/messages/uniworx/categories/I18n/en-eu.msg index f18480470..e88bf6691 100644 --- a/messages/uniworx/categories/I18n/en-eu.msg +++ b/messages/uniworx/categories/I18n/en-eu.msg @@ -1,11 +1,3 @@ -Quarter1st year@Integer: First Quarter of #{year} -Quarter2nd year@Integer: Second Quarter of #{year} -Quarter3rd year@Integer: Third Quarter of #{year} -Quarter4th year@Integer: Last Quarter of #{year} -Quarter1stShort year@Integer: #{year}/Q1st -Quarter2ndShort year@Integer: #{year}/Q2nd -Quarter3rdShort year@Integer: #{year}/Q3rd -Quarter4thShort year@Integer: #{year}/Q4th CorByProportionOnly proportion: #{rationalToFixed3 proportion} parts CorByProportionIncludingTutorial proportion: #{rationalToFixed3 proportion} parts - tutorials CorByProportionExcludingTutorial proportion: #{rationalToFixed3 proportion} parts + tutorials diff --git a/nix/docker/default.nix b/nix/docker/default.nix index 4f7f7455d..143d36402 100644 --- a/nix/docker/default.nix +++ b/nix/docker/default.nix @@ -21,7 +21,11 @@ let contents = with final; [ uniworx.uniworx.components.exes.uniworx prev.dockerTools.binSh findutils coreutils - curl wget + cups # needed for interface with print center + texlive.combined.scheme-medium # probably needed by pandoc library to produce PDFs? + # For manual testing, maybe remove for production? + curl wget pandoc + ] ++ optionals isDemo [ postgresql_12 memcached uniworx.uniworx.components.exes.uniworxdb ]; runAsRoot = '' diff --git a/routes b/routes index cfefc9671..40f7529bc 100644 --- a/routes +++ b/routes @@ -69,6 +69,7 @@ /info/glossary GlossaryR GET !free /info/faq FaqR GET !free /version VersionR GET !free +/status StatusR GET !free /help HelpR GET POST !free diff --git a/src/Application.hs b/src/Application.hs index c0f54303f..623d702a8 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -1,6 +1,6 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} -module Application +module Application ( getAppSettings, getAppDevSettings , appMain , develMain diff --git a/src/Foundation/I18n.hs b/src/Foundation/I18n.hs index 6f111b616..9378f3839 100644 --- a/src/Foundation/I18n.hs +++ b/src/Foundation/I18n.hs @@ -195,20 +195,10 @@ mkMessageVariant ''UniWorX ''ButtonMessage "messages/button" "de" mkMessageVariant ''UniWorX ''FrontendMessage "messages/frontend" "de-de-formal" instance RenderMessage UniWorX TermIdentifier where - renderMessage foundation ls TermIdentifier{..} = case season of - Q1 -> renderMessage' $ MsgQuarter1st year - Q2 -> renderMessage' $ MsgQuarter2nd year - Q3 -> renderMessage' $ MsgQuarter3rd year - Q4 -> renderMessage' $ MsgQuarter4th year - where renderMessage' = renderMessage foundation ls + renderMessage _foundation _ls = termToText -- TODO: respect user selected Datetime Format instance RenderMessage UniWorX ShortTermIdentifier where - renderMessage foundation ls (ShortTermIdentifier TermIdentifier{..}) = case season of - Q1 -> renderMessage' $ MsgQuarter1stShort year - Q2 -> renderMessage' $ MsgQuarter2ndShort year - Q3 -> renderMessage' $ MsgQuarter3rdShort year - Q4 -> renderMessage' $ MsgQuarter4thShort year - where renderMessage' = renderMessage foundation ls + renderMessage _foundation _ls (ShortTermIdentifier tid) = termToText tid -- TODO: implement shorttermidentifier properly instance RenderMessage UniWorX String where renderMessage f ls str = renderMessage f ls $ Text.pack str diff --git a/src/Foundation/Navigation.hs b/src/Foundation/Navigation.hs index 6aa443c3a..ebed962d2 100644 --- a/src/Foundation/Navigation.hs +++ b/src/Foundation/Navigation.hs @@ -132,6 +132,7 @@ breadcrumb HelpR = i18nCrumb MsgMenuHelp Nothing breadcrumb HealthR = i18nCrumb MsgMenuHealth Nothing breadcrumb InstanceR = i18nCrumb MsgMenuInstance Nothing +breadcrumb StatusR = i18nCrumb MsgMenuHealth Nothing -- never displayed breadcrumb ProfileR = i18nCrumb MsgBreadcrumbProfile Nothing breadcrumb SetDisplayEmailR = i18nCrumb MsgUserDisplayEmail $ Just ProfileR diff --git a/src/Handler/Allocation/Form.hs b/src/Handler/Allocation/Form.hs index 8b3ba800e..833d0464d 100644 --- a/src/Handler/Allocation/Form.hs +++ b/src/Handler/Allocation/Form.hs @@ -159,8 +159,7 @@ suggestAllocationForm (Set.fromList . map optionInternalValue . olOptions -> ter newTerm <- MaybeT $ get tid Entity _ Allocation{..} <- MaybeT . getBy $ TermSchoolAllocationShort oldTid ssh ash - let dayOffset = (diffDays `on` termLectureStart) newTerm oldTerm - addTime = addLocalDays dayOffset + let addTime = addLocalDays $ (diffDays `on` termLectureStart) newTerm oldTerm return AllocationForm { afTerm = tid diff --git a/src/Handler/Course/Edit.hs b/src/Handler/Course/Edit.hs index c9a5e572d..b39286c65 100644 --- a/src/Handler/Course/Edit.hs +++ b/src/Handler/Course/Edit.hs @@ -435,7 +435,7 @@ getCourseNewR = do let newTemplate = courseToForm oldTemplate mempty mempty Nothing in return $ Just $ newTemplate { cfCourseId = Nothing - , cfTerm = TermKey $ TermIdentifier 0 Q1 -- invalid, will be ignored; undefined won't work due to strictness + , cfTerm = TermKey $ termFromRational 0 -- invalid, will be ignored; undefined won't work due to strictness , cfRegFrom = Nothing , cfRegTo = Nothing , cfDeRegUntil = Nothing diff --git a/src/Handler/Health.hs b/src/Handler/Health.hs index 050644330..0bb11765b 100644 --- a/src/Handler/Health.hs +++ b/src/Handler/Health.hs @@ -11,6 +11,9 @@ import qualified Data.Set as Set import Control.Concurrent.STM.Delay +import Language.Haskell.TH (stringE,runIO) + +-- import Data.FileEmbed (embedStringFile) getHealthR :: Handler TypedContent getHealthR = do @@ -94,3 +97,31 @@ getInstanceR = do |] provideJson instanceInfo provideRep . return $ tshow instanceInfo + + +-- Most simple page for simple liveness checks +getStatusR :: Handler Html +getStatusR = withUrlRenderer + [hamlet| + $doctype 5 + + + Alive + <body> + <p> + Compile Time {#{comptime}} + |] + {- + <p> + Alive #{vnr_full} + <p> + Demo #{vnr_demo} + <p> + CI #{vnr_ci} + -} + where + -- vnr_full :: Text = $(embedStringFile "./nix/docker/version.json") + -- vnr_demo :: Text = $(embedStringFile "./nix/docker/demo-version.json") + -- vnr_ci :: Text = $(embedStringFile "./nix/docker/ci-version.json") + comptime :: Text = $(stringE =<< runIO (show <$> getCurrentTime)) + \ No newline at end of file diff --git a/src/Handler/Term.hs b/src/Handler/Term.hs index 45c820818..03b52a21f 100644 --- a/src/Handler/Term.hs +++ b/src/Handler/Term.hs @@ -25,8 +25,7 @@ import qualified Control.Monad.State.Class as State validateTerm :: (MonadHandler m, HandlerSite m ~ UniWorX) => FormValidator TermForm m () validateTerm = do - TermForm{..} <- State.get - guardValidation MsgTermStartMustMatchName $ tfStart `withinTermYear` tfName + TermForm{..} <- State.get guardValidation MsgTermEndMustBeAfterStart $ tfStart < tfEnd guardValidation MsgTermLectureEndMustBeAfterStart $ tfLectureStart < tfLectureEnd guardValidation MsgTermStartMustBeBeforeLectureStart $ tfStart <= tfLectureStart diff --git a/src/Handler/Utils/DateTime.hs b/src/Handler/Utils/DateTime.hs index c39d24103..1c752536e 100644 --- a/src/Handler/Utils/DateTime.hs +++ b/src/Handler/Utils/DateTime.hs @@ -16,6 +16,7 @@ module Handler.Utils.DateTime , addOneWeek, addWeeks , weeksToAdd , setYear, getYear + , firstDayOfWeekOnAfter , ceilingQuarterHour , formatGregorianW ) where @@ -224,6 +225,14 @@ getYear date = y where (y,_,_) = toGregorian date +dayOfWeekDiff :: DayOfWeek -> DayOfWeek -> Int +dayOfWeekDiff a b = mod (fromEnum a - fromEnum b) 7 + +-- | The first day-of-week on or after some day +-- | from time-compat-1.9.5, not included +firstDayOfWeekOnAfter :: DayOfWeek -> Day -> Day +firstDayOfWeekOnAfter dw d = addDays (toInteger $ dayOfWeekDiff dw $ dayOfWeek d) d + addOneWeek :: UTCTime -> UTCTime addOneWeek = addWeeks 1 diff --git a/src/Model/Types/DateTime.hs b/src/Model/Types/DateTime.hs index c5f7324a4..a03928b78 100644 --- a/src/Model/Types/DateTime.hs +++ b/src/Model/Types/DateTime.hs @@ -12,11 +12,10 @@ module Model.Types.DateTime import Import.NoModel import qualified Data.Text as Text -import qualified Data.CaseInsensitive as CI import Data.Either.Combinators (maybeToRight) -import Text.Read (readMaybe) import Data.Time.Calendar.WeekDate +import Data.Time.Format.ISO8601 import Database.Persist.Sql @@ -26,45 +25,13 @@ import Data.Aeson.Types as Aeson ---- --- Terms, Seaons, anything loosely related to time +-- Terms and anything loosely related to time -data Season = Q1 | Q2 | Q3 | Q4 - deriving (Show, Read, Eq, Ord, Enum, Bounded, Generic, Typeable) - deriving anyclass (Binary, Universe, Finite, NFData) - -numSeasons :: Int -- to be flexible -numSeasons = succ $ fromEnum(maxBound::Season) - -seasonFromText' :: Text -> Either Text Season -seasonFromText' t = maybeToRight errmsg (readMaybe $ Text.unpack $ Text.toUpper t) - where - errmsg = "Invalid season: ‘" <> tshow t <> "’" - -seasonFromText :: Text -> Either Text Season -seasonFromText t - | Just (q, ne) <- Text.uncons t - , q ~= 'Q' - , Just (n, e) <- Text.uncons ne - , Text.null e = case n of '1' -> Right Q1 - '2' -> Right Q2 - '3' -> Right Q3 - '4' -> Right Q4 - _ -> Left $ "Invalid quarter number: ‘" <> tshow t <> "’" - | otherwise = Left $ "Invalid season: ‘" <> tshow t <> "’" - where - (~=) :: Char -> Char -> Bool - (~=) = (==) `on` CI.mk - -data TermIdentifier = TermIdentifier - { year :: Integer -- ^ Using 'Integer' to model years is consistent with 'Data.Time.Calendar' - , season :: Season - } deriving (Show, Read, Eq, Ord, Generic, Typeable) - deriving anyclass (Binary, NFData) - -instance Enum TermIdentifier where - -- ^ Do not use for conversion – Enumeration only - toEnum int = let (toInteger -> year, toEnum -> season) = int `divMod` numSeasons in TermIdentifier{..} - fromEnum TermIdentifier{..} = fromInteger year * numSeasons + fromEnum season +newtype TermIdentifier = TermIdentifier { getTermDay :: Day } + deriving (Show, Read, Eq, Ord, Generic, Typeable, Enum) + deriving newtype (Binary, ISO8601, PersistField, PersistFieldSql) + deriving anyclass (NFData) + -- ought to be equivalent to deriving stock (Show, Read, Eq, Ord, Generic, Typeable, Enum, Binary, NFData) -- Conversion TermId <-> TermIdentifier:: -- from_TermId_to_TermIdentifier = unTermKey @@ -94,40 +61,56 @@ shortened = iso shorten expand , year < $currentYear + 50 = year `mod` 100 | otherwise = year +-- Option 1: date in iso8601 termToText :: TermIdentifier -> Text -termToText TermIdentifier{..} = Text.pack $ show (year ^. shortened) ++ show season +termToText = Text.pack . iso8601Show -- also see Hander.Utils.tidFromText termFromText :: Text -> Either Text TermIdentifier -termFromText t - | (ys,s) <- Text.break (~= 'Q') t - , Right season <- seasonFromText s - , Just (review shortened -> year) <- readMaybe $ Text.unpack ys - = Right TermIdentifier{..} - | otherwise = Left $ "Invalid TermIdentifier: “" <> t <> "”" -- TODO: Could be improved, I.e. say "W"/"S" from Number - where - (~=) :: Char -> Char -> Bool - (~=) = (==) `on` CI.mk +termFromText t = maybeToRight errm $ iso8601ParseM $ Text.unpack t + where + errm = "Invalid TermIdentifier: “" <> t <> "”" -termToRational :: TermIdentifier -> Rational -termToRational TermIdentifier{..} = toRational year + seasonOffset - where - seasonOffset = fromIntegral (fromEnum season) % fromIntegral numSeasons +-- Option 2: show as WeekNr-DayOfWeek-Year, e.g. 22Mon2021? +termToText' :: TermIdentifier -> Text +termToText' TermIdentifier{..} = Text.pack $ show weeknr ++ wd ++ show year + where + wd = take 3 $ show $ dayOfWeek getTermDay + (year,weeknr,_wd_) = toWeekDate getTermDay + +{- TODO +termFromText' :: Text -> Either Text TermIdentifier +termFromText' t = error "not implemented" + where + errm = "Invalid TermIdentifier: “" <> t <> "”" +-} + +daysPerYear :: Rational +daysPerYear = 365 + (97 % 400) + +dayOffset :: Rational +dayOffset = fromIntegral yearzero + (fromIntegral diffstart / daysPerYear) + where + dayzero = toEnum 0 + yearzero = fst3 $ toGregorian dayzero + diffstart = diffDays dayzero $ fromGregorian yearzero 1 1 + +-- Attempt to ensure that ``truncate . termToRational == fst3 . toGregorian . getTermDay´´ holds +termToRational :: TermIdentifier -> Rational +termToRational = (dayOffset +) . (/ daysPerYear) . fromIntegral . fromEnum termFromRational :: Rational -> TermIdentifier -termFromRational n = TermIdentifier{..} - where - year = floor n - remainder = n - fromInteger (floor n) -- properFraction problematic for negative year values - season = toEnum $ floor $ remainder * fromIntegral numSeasons +termFromRational = toEnum . round . (daysPerYear *) . subtract dayOffset +{- -- For newtype Day, PersistField instance can be derived automatically instance PersistField TermIdentifier where toPersistValue = PersistRational . termToRational fromPersistValue (PersistRational t) = Right $ termFromRational t fromPersistValue x = Left $ "Expected TermIdentifier, received: " <> tshow x instance PersistFieldSql TermIdentifier where - sqlType _ = SqlNumeric 5 1 + sqlType _ = SqlNumeric 9 5 -- total significant digits; significant digits after decimal point +-} instance ToHttpApiData TermIdentifier where toUrlPiece = termToText @@ -162,27 +145,16 @@ data TermDay guessDay :: TermIdentifier -> TermDay -> Day -guessDay TermIdentifier{ year, season = Q1 } TermDayStart = fromGregorian year 1 1 -guessDay TermIdentifier{ year, season = Q2 } TermDayStart = fromGregorian year 4 1 -guessDay TermIdentifier{ year, season = Q3 } TermDayStart = fromGregorian year 7 1 -guessDay TermIdentifier{ year, season = Q4 } TermDayStart = fromGregorian year 10 1 -guessDay tid TermDayEnd = pred $ guessDay (succ tid) TermDayStart -guessDay tid TermDayLectureStart = fromWeekDate year weekStart 1 -- first Monday within Quarter - where ( year, weekStart, _) = toWeekDate $ guessDay tid TermDayStart -guessDay tid TermDayLectureEnd = fromWeekDate year weekStart 5 -- Friday of last week within Quarter - where ( year, weekStart, _) = toWeekDate $ guessDay tid TermDayEnd +guessDay TermIdentifier{..} TermDayLectureStart = getTermDay +guessDay TermIdentifier{..} TermDayLectureEnd = addDays 8 getTermDay -- courses last only a week +guessDay tid TermDayStart = fromWeekDate year week 1 -- Monday before lecture time + where ( year, week, _) = toWeekDate $ addDays (-7*4*3) $ guessDay tid TermDayLectureStart +guessDay tid TermDayEnd = fromWeekDate year week 7 -- Sunday after lecture time + where ( year, week, _) = toWeekDate $ addDays (7*3) $ guessDay tid TermDayLectureEnd withinTerm :: Day -> TermIdentifier -> Bool withinTerm d tid = guessDay tid TermDayStart <= d && d <= guessDay tid TermDayEnd --- | Check only if last two digits within the year numbers match -withinTermYear :: Day -> TermIdentifier -> Bool -time `withinTermYear` term = timeYear `mod` 100 == termYear `mod` 100 - where - timeYear = fst3 $ toGregorian time - termYear = year term - - data OccurrenceSchedule = ScheduleWeekly { scheduleDayOfWeek :: WeekDay , scheduleStart :: TimeOfDay diff --git a/src/Utils/Holidays.hs b/src/Utils/Holidays.hs index 5b8d317a3..fb9bc1a08 100644 --- a/src/Utils/Holidays.hs +++ b/src/Utils/Holidays.hs @@ -10,6 +10,8 @@ module Utils.Holidays , feiertage , bankHolidays, bankHolidaysArea, bankHolidaysAreaSet , isBankHoliday, isBankHolidayArea + , isWeekend + , addBusinessDays ) where import Import.NoModel @@ -107,13 +109,16 @@ isBankHolidayArea land dd = dd `Set.member` holidays -- | Returns whether a day is a bank holiday for years >= 1995 -- | Repeated calls are handled efficiently using lazy memoization isBankHolidayArea :: Feiertagsgebiet -> Day -> Bool -isBankHolidayArea land dd = dd `Set.member` holidays +isBankHolidayArea land = ibha where - (year, _, _) = toGregorian dd - holidays - | (Just hys) <- Map.lookup land memoHolidays - , (Just hds) <- index hys $ fromInteger $ year2index year = hds - | otherwise = bankHolidaysAreaSet land year + landHoliday = Map.lookup land memoHolidays + ibha dd = dd `Set.member` holidays + where + (year, _, _) = toGregorian dd + holidays + | (Just hys) <- landHoliday + , (Just hds) <- index hys $ fromInteger $ year2index year = hds + | otherwise = bankHolidaysAreaSet land year -- memoize holidays memoHolidays :: Map.Map Feiertagsgebiet [Set.Set Day] @@ -137,3 +142,27 @@ index2year y = result (x,r) = y `divMod` 2 result | r == 0 = memoTip + x | otherwise = memoTip - x - 1 + +-- | Test for Saturday/Sunday +isWeekend :: Day -> Bool +isWeekend = isWeekend' . dayOfWeek + where + isWeekend' :: WeekDay -> Bool + isWeekend' Sunday = True + isWeekend' Saturday = True + isWeekend' _ = False + +-- | Always returns a business day. +-- | Saturday/Sunday/Holiday treated like next (n>=0) or previous (n<0) working day +addBusinessDays :: Feiertagsgebiet -> Integer -> Day -> Day +addBusinessDays land = abd + where + ibhal = isBankHolidayArea land + freeday dd = isWeekend dd || ibhal dd + abd n = abd' n + where + (fwd, bwd) | n >= 0 = (succ, pred) + | otherwise = (pred, succ) + abd' m dd | freeday dd = abd' m (fwd dd) + | m == 0 = dd + | otherwise = abd' (bwd m) (fwd dd) diff --git a/test/Database/Fill.hs b/test/Database/Fill.hs index ed4c8c55e..b47723cb4 100644 --- a/test/Database/Fill.hs +++ b/test/Database/Fill.hs @@ -3,39 +3,40 @@ module Database.Fill ) where import "uniworx" Import hiding (Option(..), currentYear) -import Handler.Utils.Form (SheetGrading'(..), SheetGroup'(..)) +-- import Handler.Utils.Form (SheetGrading'(..), SheetGroup'(..)) import qualified Data.ByteString.Lazy as LBS -import qualified Data.Text as Text +-- import qualified Data.Text as Text -- import Data.Text.IO (hPutStrLn) import qualified Data.Set as Set import qualified Data.Map as Map -- import Data.Time.Calendar.OrdinalDate -import Data.Time.Calendar.WeekDate +-- import Data.Time.Calendar.WeekDate +import Utils.Holidays import Control.Applicative (ZipList(..)) import Handler.Utils.DateTime import Handler.Utils.AuthorshipStatement (insertAuthorshipStatement) -import Control.Monad.Random.Class (weighted) +-- import Control.Monad.Random.Class (weighted) import System.Random.Shuffle (shuffleM) import qualified Data.CaseInsensitive as CI import qualified Data.Csv as Csv -import Crypto.Random (getRandomBytes) -import Data.List (genericLength) -import qualified Data.List as List (splitAt) - -import qualified Data.Conduit.Combinators as C +-- import Crypto.Random (getRandomBytes) +import Data.List (foldl) +-- import qualified Data.List as List (splitAt) import System.Directory (getModificationTime, doesDirectoryExist) import System.FilePath.Glob (glob) +{- Needed for File Tests only +import qualified Data.Conduit.Combinators as C import Paths_uniworx (getDataFileName) testdataFile :: MonadIO m => FilePath -> m FilePath @@ -47,6 +48,8 @@ insertFile residual fileTitle = do let fileContent = Just $ C.sourceFile filepath fileModified <- liftIO getCurrentTime sinkFile' File{..} residual >>= insert +-} + fillDb :: DB () fillDb = do @@ -55,34 +58,22 @@ fillDb = do let insert' :: (PersistRecordBackend r (YesodPersistBackend UniWorX), AtLeastOneUniqueKey r) => r -> YesodDB UniWorX (Key r) insert' = fmap (either entityKey id) . insertBy + + addBDays = addBusinessDays Fraport -- holiday area to use + currentTerm = TermIdentifier $ utctDay now + -- (currentYear, currentMonth, currentDay) = toGregorian $ getTermDay currentTerm + nextTerm n = TermIdentifier $ addBDays n $ getTermDay currentTerm - (currentYear, currentMonth, _) = toGregorian $ utctDay now - currentTerm - | 3 >= currentMonth = TermIdentifier currentYear Q1 - | 6 >= currentMonth = TermIdentifier currentYear Q2 - | 9 >= currentMonth = TermIdentifier currentYear Q3 - | otherwise = TermIdentifier currentYear Q4 - nextTerm = succ currentTerm - prevTerm = pred currentTerm - prevPrevTerm = pred prevTerm - - seasonTerm next wSeason = until ((wSeason ==) . season) prog currentTerm - where prog | next = succ - | otherwise = pred - - termTime :: Bool -- ^ Next term? - -> Season - -> Rational - -> Bool -- ^ Relative to end of semester? - -> WeekDay - -> (Day -> UTCTime) -- ^ Add time to day + termTime :: TermIdentifier -- ^ Term + -> TermDay -- ^ Relative to which day? + -> Integer -- ^ Business Days Offset from Start/End of Term + -> Maybe WeekDay -- ^ Move to weekday + -> (Day -> UTCTime) -- ^ Add time to day -> UTCTime - termTime next gSeason weekOffset fromEnd d = ($ utctDay) - where - utctDay = fromWeekDate wYear wWeek $ fromEnum d - (wYear, wWeek, _) = toWeekDate . addDays (round $ 7 * weekOffset) $ fromGregorian rYear rMonth rDay - gTid = seasonTerm next gSeason - (rYear, rMonth, rDay) = toGregorian $ guessDay gTid $ bool TermDayLectureStart TermDayLectureEnd fromEnd + termTime gTid gTD gOff mbWeekDay = ($ utctDay) + where + gDay = addBDays gOff $ guessDay gTid gTD + utctDay = maybe gDay (`firstDayOfWeekOnAfter` gDay) mbWeekDay gkleen <- insert User { userIdent = "G.Kleen@campus.lmu.de" @@ -354,16 +345,27 @@ fillDb = do matrikel <- toMatrikel <$> getRandomRs (0 :: Int, 9 :: Int) manyUsers <- insertMany . getZipList $ manyUser <$> ZipList ((,,) <$> firstNames <*> middlenames <*> surnames) <*> ZipList matrikel - forM_ [(pred $ pred prevPrevTerm)..(succ $ succ $ succ $ succ nextTerm)] $ \tid -> do - let term = Term { termName = tid - , termStart = guessDay tid TermDayStart - , termEnd = guessDay tid TermDayEnd - , termHolidays = [] + let tmin = -8 + tmax = 29*6 + trange = [tmin..tmax] + dmin = guessDay (nextTerm tmin) TermDayStart + dmax = guessDay (nextTerm tmax) TermDayEnd + hdys = foldl (<>) mempty $ [bankHolidaysAreaSet Fraport y | y <- [getYear dmin..getYear dmax]] + terms <- forM trange $ \nr -> do + let tid = nextTerm nr + tk = TermKey tid + tStart = guessDay tid TermDayStart + tEnd = guessDay tid TermDayEnd + term = Term { termName = tid + , termStart = tStart + , termEnd = tEnd + , termHolidays = toList $ Set.filter (\d -> tStart <= d && d <= tEnd) hdys , termLectureStart = guessDay tid TermDayLectureStart , termLectureEnd = guessDay tid TermDayLectureEnd } - void $ repsert (TermKey tid) term - void . insert_ $ TermActive (TermKey tid) (toMidnight $ addDays (-60) $ termStart term) (Just . beforeMidnight $ addDays 60 $ termEnd term) Nothing + repsert tk term + insert_ $ TermActive tk (toMidnight $ addDays (-60) $ termStart term) (Just . beforeMidnight $ addDays 60 $ termEnd term) Nothing + return tk ifiAuthorshipStatement <- insertAuthorshipStatement I18n { i18nFallback = htmlToStoredMarkup @@ -583,574 +585,138 @@ fillDb = do -- Fahrschule F - fdf <- insert' Course - { courseName = "F - Vorfeldführerschein" - , courseDescription = Just $ htmlToStoredMarkup [shamlet| - <p> - Berechtigung zum Führen eines Fahrzeuges auf den Fahrstrassen des Vorfeldes. - <section> - <h3>Benötigte Unterlagen - <ul> - <li>Sehtest - <i>(Bitte vorab hochladen!) - <li>Regulärer Führerschein - |] - , courseLinkExternal = Nothing - , courseShorthand = "F" - , courseTerm = TermKey currentTerm - , courseSchool = avn - , courseCapacity = Nothing - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Just $ termTime True (season currentTerm) (-2) False Monday toMidnight - , courseRegisterTo = Just $ termTime True (season currentTerm) 0 True Saturday beforeMidnight - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = True - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = True - } - insert_ $ CourseEdit jost now fdf - void $ insert Sheet - { sheetCourse = fdf - , sheetName = "Sehtest" - , sheetDescription = Just $ htmlToStoredMarkup [shamlet|Bitte einen Scan ihres Sehtest hochladen!|] - , sheetType = NotGraded - , sheetGrouping = Arbitrary 3 - , sheetMarkingText = Nothing - , sheetVisibleFrom = Just $ termTime True (season currentTerm) (-2) False Monday toMidnight - , sheetActiveFrom = Just $ termTime True (season currentTerm) (-2) False Monday toMidnight - , sheetActiveTo = Just $ termTime True (season currentTerm) 0 True Saturday beforeMidnight - , sheetSubmissionMode = SubmissionMode False . Just $ UploadAny True Nothing False - , sheetHintFrom = Nothing - , sheetSolutionFrom = Nothing - , sheetAutoDistribute = False - , sheetAnonymousCorrection = True - , sheetRequireExamRegistration = Nothing - , sheetAllowNonPersonalisedSubmission = True - , sheetAuthorshipStatementMode = SheetAuthorshipStatementModeExam - , sheetAuthorshipStatementExam = Nothing - , sheetAuthorshipStatement = Nothing - } - forM_ [(Monday)..Thursday] $ \td -> do - forM_ [(1::Int)..(4*4)] $ \tw -> do - let firstTT = termTime True (season currentTerm) (toRational $ tw - 1) False td toMorning - secondTT = termTime True (season currentTerm) (toRational $ tw - 1) False (succ td) toMorning - regFrom = termTime True (season currentTerm) (toRational $ tw - 8) False td toMorning - regTo = termTime True (season currentTerm) (toRational $ tw - 2) False td toMorning - tut1 <- insert Tutorial - { tutorialName = CI.mk $ Text.pack $ "KW" ++ show (snd3 $ toWeekDate $ utctDay firstTT) ++ take 3 (show td) - , tutorialCourse = fdf - , tutorialType = "Schulung" - , tutorialCapacity = Just 16 - , tutorialRoom = Just $ case tw `mod` 4 of - 1 -> "A380" - 2 -> "B747" - 3 -> "MD11" - _ -> "B777" - , tutorialRoomHidden = False - , tutorialTime = Occurrences - { occurrencesScheduled = Set.empty - , occurrencesExceptions = Set.fromList - [ ExceptOccur - { exceptDay = utctDay firstTT - , exceptStart = TimeOfDay 8 30 0 - , exceptEnd = TimeOfDay 16 0 0 - } - , ExceptOccur - { exceptDay = utctDay secondTT - , exceptStart = TimeOfDay 9 0 0 - , exceptEnd = TimeOfDay 16 0 0 - } - ] + forM_ terms $ \tk -> do + let tid = unTermKey tk + jtt = (((Just .) .) .) . termTime tid + weekDay = dayOfWeek $ getTermDay tid + firstDay = utctDay $ termTime tid TermDayLectureStart 0 Nothing toMidnight + secondDay = utctDay $ termTime tid TermDayLectureStart 1 Nothing toMidnight + -- thirdDay = utctDay $ termTime tid TermDayLectureStart 2 Nothing toMidnight + capacity = Just 8 + mkName = CI.mk . (<> termToText' tid) . (<> "_") + if weekDay `elem` [Friday, Saturday, Sunday] + then return () + else do + c <- insert' Course + { courseName = mkName "Vorfeldführerschein" + , courseDescription = Just $ htmlToStoredMarkup [shamlet| + <p> + Berechtigung zum Führen eines Fahrzeuges auf den Fahrstrassen des Vorfeldes. + <section> + <h3>Benötigte Unterlagen + <ul> + <li>Sehtest, + <i>bitte vorab hochladen! + <li>Regulärer Führerschein, + <i>Bitte mitbringen. + |] + , courseLinkExternal = Nothing + , courseShorthand = "F" + , courseTerm = tk + , courseSchool = avn + , courseCapacity = capacity + , courseVisibleFrom = jtt TermDayStart 0 Nothing toMidnight + , courseVisibleTo = jtt TermDayEnd 0 Nothing beforeMidnight + , courseRegisterFrom = jtt TermDayStart 0 Nothing toMidnight + , courseRegisterTo = jtt TermDayLectureStart (-1) Nothing toMidnight + , courseDeregisterUntil = jtt TermDayLectureStart (-5) (Just Monday) toMidnight + , courseRegisterSecret = Nothing + , courseMaterialFree = True + , courseApplicationsRequired = False + , courseApplicationsInstructions = Nothing + , courseApplicationsText = False + , courseApplicationsFiles = NoUpload + , courseApplicationsRatingsVisible = False + , courseDeregisterNoShow = True } - , tutorialRegGroup = Just "schulung" - , tutorialRegisterFrom = Just regFrom - , tutorialRegisterTo = Just regTo - , tutorialDeregisterUntil = Nothing - , tutorialLastChanged = now - , tutorialTutorControlled = True - } - void . insert $ Tutor tut1 jost - void . insert' $ Exam - { examCourse = fdf - , examName = "Theorie" - , examGradingRule = Nothing - , examBonusRule = Nothing - , examOccurrenceRule = ExamRoomManual - , examExamOccurrenceMapping = Nothing - , examVisibleFrom = Just regFrom - , examRegisterFrom = Just firstTT - , examRegisterTo = Just $ toMidday $ utctDay secondTT - , examDeregisterUntil = Nothing - , examPublishOccurrenceAssignments = Nothing - , examStart = Just $ toTimeOfDay 15 30 0 $ utctDay secondTT - , examEnd = Just $ toTimeOfDay 16 30 0 $ utctDay secondTT - , examFinished = Nothing - , examPartsFrom = Nothing - , examClosed = Nothing - , examPublicStatistics = True - , examGradingMode = ExamGradingPass - , examDescription = Just $ htmlToStoredMarkup [shamlet|Theoretische Prüfung mit Fragebogen|] - , examExamMode = ExamMode - { examAids = Just $ ExamAidsPreset ExamClosedBook - , examOnline = Just $ ExamOnlinePreset ExamOffline - , examSynchronicity = Just $ ExamSynchronicityPreset ExamSynchronous - , examRequiredEquipment = Just $ ExamRequiredEquipmentPreset ExamRequiredEquipmentNone - } - , examStaff = Just "Jost" - , examAuthorshipStatement = Nothing - } - - -- FFP - let nbrs :: [Int] - nbrs = [1,2,3,27,7,1] - ffp <- insert' Course - { courseName = "Fortgeschrittene Funktionale Programmierung" - , courseDescription = Just $ htmlToStoredMarkup [shamlet| - <h2>It is fun! - <p>Come to where the functional is! - <section> - <h3>Functional programming can be done in Haskell! - <p>This is not a joke, this is serious! - <section> - <h3>Consider some numbers - <ul> - $forall n <- nbrs - <li>Number #{n} - |] - , courseLinkExternal = Nothing - , courseShorthand = "FFP" - , courseTerm = TermKey $ seasonTerm True Q1 - , courseSchool = ifi - , courseCapacity = Just 20 - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Just $ termTime True Q1 (-2) False Monday toMidnight - , courseRegisterTo = Just $ termTime True Q1 0 True Sunday beforeMidnight - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = True - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = True - } - insert_ $ CourseEdit jost now ffp - void . insert $ DegreeCourse ffp sdBsc sdInf - void . insert $ DegreeCourse ffp sdMst sdInf - -- void . insert $ Lecturer jost ffp CourseLecturer - void . insert $ Lecturer gkleen ffp CourseAssistant - adhoc <- insert Sheet - { sheetCourse = ffp - , sheetName = "Adhoc-Gruppen" - , sheetDescription = Nothing - , sheetType = NotGraded - , sheetGrouping = Arbitrary 3 - , sheetMarkingText = Nothing - , sheetVisibleFrom = Just $ termTime True Q1 0 False Monday toMidnight - , sheetActiveFrom = Just $ termTime True Q1 1 False Monday toMidnight - , sheetActiveTo = Just $ termTime True Q1 2 False Sunday beforeMidnight - , sheetSubmissionMode = SubmissionMode False . Just $ UploadAny True Nothing False - , sheetHintFrom = Nothing - , sheetSolutionFrom = Nothing - , sheetAutoDistribute = False - , sheetAnonymousCorrection = True - , sheetRequireExamRegistration = Nothing - , sheetAllowNonPersonalisedSubmission = True - , sheetAuthorshipStatementMode = SheetAuthorshipStatementModeExam - , sheetAuthorshipStatementExam = Nothing - , sheetAuthorshipStatement = Nothing - } - insert_ $ SheetEdit gkleen now adhoc - feste <- insert Sheet - { sheetCourse = ffp - , sheetName = "Feste Gruppen" - , sheetDescription = Nothing - , sheetType = NotGraded - , sheetGrouping = RegisteredGroups - , sheetMarkingText = Nothing - , sheetVisibleFrom = Just $ termTime True Q1 1 False Monday toMidnight - , sheetActiveFrom = Just $ termTime True Q1 2 False Monday toMidnight - , sheetActiveTo = Just $ termTime True Q1 3 False Sunday beforeMidnight - , sheetSubmissionMode = SubmissionMode False . Just $ UploadAny True Nothing False - , sheetHintFrom = Nothing - , sheetSolutionFrom = Nothing - , sheetAutoDistribute = False - , sheetAnonymousCorrection = True - , sheetRequireExamRegistration = Nothing - , sheetAllowNonPersonalisedSubmission = True - , sheetAuthorshipStatementMode = SheetAuthorshipStatementModeExam - , sheetAuthorshipStatementExam = Nothing - , sheetAuthorshipStatement = Nothing - } - insert_ $ SheetEdit gkleen now feste - keine <- insert Sheet - { sheetCourse = ffp - , sheetName = "Keine Gruppen" - , sheetDescription = Nothing - , sheetType = NotGraded - , sheetGrouping = NoGroups - , sheetMarkingText = Nothing - , sheetVisibleFrom = Just $ termTime True Q1 2 False Monday toMidnight - , sheetActiveFrom = Just $ termTime True Q1 3 False Monday toMidnight - , sheetActiveTo = Just $ termTime True Q1 4 False Sunday beforeMidnight - , sheetSubmissionMode = SubmissionMode False . Just $ UploadAny True Nothing False - , sheetHintFrom = Nothing - , sheetSolutionFrom = Nothing - , sheetAutoDistribute = False - , sheetAnonymousCorrection = True - , sheetRequireExamRegistration = Nothing - , sheetAllowNonPersonalisedSubmission = True - , sheetAuthorshipStatementMode = SheetAuthorshipStatementModeExam - , sheetAuthorshipStatementExam = Nothing - , sheetAuthorshipStatement = Nothing - } - insert_ $ SheetEdit gkleen now keine - void . insertMany $ map (\u -> CourseParticipant ffp u now Nothing CourseParticipantActive) - [ fhamann - , maxMuster - , tinaTester - ] - - examFFP <- insert' $ Exam - { examCourse = ffp - , examName = "Klausur" - , examGradingRule = Nothing - , examBonusRule = Nothing - , examOccurrenceRule = ExamRoomManual - , examExamOccurrenceMapping = Nothing - , examVisibleFrom = Just $ termTime True Q1 (-4) True Monday toMidnight - , examRegisterFrom = Just $ termTime True Q1 (-4) True Monday toMidnight - , examRegisterTo = Just $ termTime True Q1 1 True Sunday beforeMidnight - , examDeregisterUntil = Just $ termTime True Q1 2 True Wednesday beforeMidnight - , examPublishOccurrenceAssignments = Just $ termTime True Q1 3 True Monday toMidnight - , examStart = Just $ termTime True Q1 3 True Tuesday (toTimeOfDay 10 0 0) - , examEnd = Just $ termTime True Q1 3 True Tuesday (toTimeOfDay 12 0 0) - , examFinished = Just $ termTime True Q1 3 True Wednesday (toTimeOfDay 22 0 0) - , examPartsFrom = Just $ termTime True Q1 (-4) True Monday toMidnight - , examClosed = Nothing - , examPublicStatistics = True - , examGradingMode = ExamGradingGrades - , examDescription = Nothing - , examExamMode = ExamMode - { examAids = Just $ ExamAidsPreset ExamClosedBook - , examOnline = Just $ ExamOnlinePreset ExamOffline - , examSynchronicity = Just $ ExamSynchronicityPreset ExamSynchronous - , examRequiredEquipment = Just $ ExamRequiredEquipmentPreset ExamRequiredEquipmentNone - } - , examStaff = Just "Hofmann" - , examAuthorshipStatement = Nothing - } - _ <- insert' Material - { materialCourse = ffp - , materialName = "Material 1" - , materialType = Just "Typ 1" - , materialDescription = Just $ htmlToStoredMarkup [shamlet|<i>Folien</i> für die Zentralübung|] - , materialVisibleFrom = Just now - , materialLastEdit = now - } - - _ <- insert' Material - { materialCourse = ffp - , materialName = "Material 2" - , materialType = Just "Typ 2" - , materialDescription = Just $ htmlToStoredMarkup [shamlet|<i>Videos</i> für die Vorlesung|] - , materialVisibleFrom = Just now - , materialLastEdit = now - } - - void . insertMany $ map (\u -> ExamRegistration examFFP u Nothing now) - [ fhamann - , maxMuster - , tinaTester - ] - - -- EIP - eip <- insert' Course - { courseName = "Einführung in die Programmierung" - , courseDescription = Nothing - , courseLinkExternal = Nothing - , courseShorthand = "EIP" - , courseTerm = TermKey $ seasonTerm False Q4 - , courseSchool = ifi - , courseCapacity = Just 20 - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Just $ termTime False Q4 (-4) False Monday toMidnight - , courseRegisterTo = Nothing - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = True - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = False - } - insert_ $ CourseEdit fhamann now eip - void . insert' $ DegreeCourse eip sdBsc sdInf - void . insert' $ Lecturer fhamann eip CourseLecturer - -- interaction design - ixd <- insert' Course - { courseName = "Interaction Design (User Experience Design I & II)" - , courseDescription = Nothing - , courseLinkExternal = Nothing - , courseShorthand = "IXD" - , courseTerm = TermKey $ seasonTerm True Q1 - , courseSchool = ifi - , courseCapacity = Just 20 - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Just $ termTime True Q1 0 False Monday toMidnight - , courseRegisterTo = Just $ termTime True Q1 (-2) True Sunday beforeMidnight - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = True - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = False - } - insert_ $ CourseEdit fhamann now ixd - void . insert' $ DegreeCourse ixd sdBsc sdInf - void . insert' $ Lecturer fhamann ixd CourseAssistant - -- concept development - ux3 <- insert' Course - { courseName = "Concept Development (User Experience Design III)" - , courseDescription = Nothing - , courseLinkExternal = Nothing - , courseShorthand = "UX3" - , courseTerm = TermKey $ seasonTerm True Q4 - , courseSchool = ifi - , courseCapacity = Just 30 - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Nothing - , courseRegisterTo = Nothing - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = True - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = False - } - insert_ $ CourseEdit fhamann now ux3 - void . insert' $ DegreeCourse ux3 sdBsc sdInf - void . insert' $ Lecturer fhamann ux3 CourseAssistant - -- promo - pmo <- insert' Course - { courseName = "Programmierung und Modellierung" - , courseDescription = Nothing - , courseLinkExternal = Nothing - , courseShorthand = "ProMo" - , courseTerm = TermKey $ seasonTerm True Q1 - , courseSchool = ifi - , courseCapacity = Just 50 - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Just $ termTime True Q1 (-2) False Monday toMidnight - , courseRegisterTo = Nothing - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = True - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = False - } - insert_ $ CourseEdit jost now pmo - void . insert $ DegreeCourse pmo sdBsc sdInf - void . insert $ Lecturer jost pmo CourseAssistant - void . insertMany $ map (\u -> CourseParticipant pmo u now Nothing CourseParticipantActive) - [ fhamann - , maxMuster - , tinaTester - ] - - let shTypes = NotGraded : [ shType g | g <- shGradings, shType <- [ Normal, Bonus, Informational ] ] - where shGradings = [ Points 6, PassPoints 3 6, PassBinary, PassAlways ] - shGroupings = [ Arbitrary 3, RegisteredGroups, NoGroups ] - shSubModes = do - corrector <- universeF - [ SubmissionMode corrector Nothing - , SubmissionMode corrector $ Just NoUpload - , SubmissionMode corrector $ Just UploadSpecific - { uploadSpecificFiles = impureNonNull $ Set.fromList - [ UploadSpecificFile "Aufgabe 1" "exercise_2.1.hs" False False Nothing - , UploadSpecificFile "Aufgabe 2" "exercise_2.2.hs" False False Nothing - , UploadSpecificFile "Erklärung der Eigenständigkeit" "erklärung.txt" True True (Just 42) - ] + insert_ $ CourseEdit jost now c + insert_ Sheet + { sheetCourse = c + , sheetName = mkName "Sehtest" + , sheetDescription = Just $ htmlToStoredMarkup [shamlet|Bitte einen Scan ihres Sehtest hochladen!|] + , sheetType = NotGraded + , sheetGrouping = Arbitrary 3 + , sheetMarkingText = Nothing + , sheetVisibleFrom = jtt TermDayStart 0 Nothing toMidnight + , sheetActiveFrom = jtt TermDayStart 0 Nothing toMidnight + , sheetActiveTo = jtt TermDayLectureStart 0 Nothing toMorning + , sheetSubmissionMode = SubmissionMode False . Just $ UploadAny True Nothing False + , sheetHintFrom = Nothing + , sheetSolutionFrom = Nothing + , sheetAutoDistribute = False + , sheetAnonymousCorrection = True + , sheetRequireExamRegistration = Nothing + , sheetAllowNonPersonalisedSubmission = True + , sheetAuthorshipStatementMode = SheetAuthorshipStatementModeExam + , sheetAuthorshipStatementExam = Nothing + , sheetAuthorshipStatement = Nothing + } + -- TODO: Maybe split into to Tutorials with + -- occurrencesSchedule = Set.fromList [ ScheduleWeekly { scheduleDayOfWeek = weekDay, scheduleStart = TimeOfDay 8 30 0, scheduleEnd = TimeOfDay 16 0 0} ] + tut1 <- insert Tutorial + { tutorialName = mkName "Theorieschulung" + , tutorialCourse = c + , tutorialType = "Schulung" + , tutorialCapacity = capacity + , tutorialRoom = Just $ case weekDay of + Monday -> "A380" + Tuesday -> "B747" + Wednesday -> "MD11" + Thursday -> "A380" + _ -> "B777" + , tutorialRoomHidden = False + , tutorialTime = Occurrences + { occurrencesScheduled = Set.empty + , occurrencesExceptions = Set.fromList + [ ExceptOccur + { exceptDay = firstDay + , exceptStart = TimeOfDay 8 30 0 + , exceptEnd = TimeOfDay 16 0 0 + } + , ExceptOccur + { exceptDay = secondDay + , exceptStart = TimeOfDay 9 0 0 + , exceptEnd = TimeOfDay 16 0 0 + } + ] } - ] ++ [ SubmissionMode corrector $ Just UploadAny{..} - | uploadUnpackZips <- universeF - , uploadExtensionRestriction <- [ Nothing, Just . impureNonNull $ Set.fromList ["pdf", "txt", "jpeg", "hs"] ] - , let uploadEmptyOk = False - ] - - sheetCombinations = (,,) <$> shTypes <*> shGroupings <*> shSubModes - - forM_ (zip [0..] sheetCombinations) $ \(shNr, (sheetType, sheetGrouping, sheetSubmissionMode)) -> do - MsgRenderer mr <- getMsgRenderer - - let sheetSubmissionModeDescr - | Just userMode <- sheetSubmissionMode ^? _submissionModeUser . _Just - = let - extra = catMaybes - [ guardOn (fromMaybe False $ userMode ^? _uploadUnpackZips) $ mr MsgAutoUnzip - , guardOn (maybe False (is _Just) $ userMode ^? _uploadExtensionRestriction) $ mr MsgUploadModeExtensionRestriction - ] - in mr (classifySubmissionMode sheetSubmissionMode) <> " (" <> Text.intercalate ", " (mr (classifyUploadMode userMode) : extra) <> ")" - | Just userMode <- sheetSubmissionMode ^? _submissionModeUser . _Just - = mr (classifySubmissionMode sheetSubmissionMode) <> " (" <> mr (classifyUploadMode userMode) <> ")" - | otherwise - = mr (classifySubmissionMode sheetSubmissionMode) - sheetGroupingDescr = case sheetGrouping of - Arbitrary{} -> mr Arbitrary' - RegisteredGroups -> mr RegisteredGroups' - NoGroups -> mr NoGroups' - sheetTypeDescr - | Just g <- sheetType ^? _grading - = let sheetGrading' = case g of - Points{} -> Points' - PassPoints{} -> PassPoints' - PassBinary{} -> PassBinary' - PassAlways{} -> PassAlways' - in mr sheetType' <> " (" <> mr sheetGrading' <> ")" - | otherwise - = mr sheetType' - where - sheetType' = classifySheetType sheetType - - prog = 14 * (shNr % genericLength sheetCombinations) - - -- liftIO . hPutStrLn stderr $ Text.intercalate ", " [sheetTypeDescr, sheetGroupingDescr, sheetSubmissionModeDescr] - -- liftIO . hPutStrLn stderr $ tshow (sheetType, sheetGrouping, sheetSubmissionMode) - - shId <- insert Sheet - { sheetCourse = pmo - , sheetName = CI.mk $ tshow shNr <> ": " <> Text.intercalate ", " [sheetTypeDescr, sheetGroupingDescr, sheetSubmissionModeDescr] - , sheetDescription = Nothing - , sheetType, sheetGrouping, sheetSubmissionMode - , sheetMarkingText = Nothing - , sheetVisibleFrom = Just $ termTime True Q1 prog False Monday toMidnight - , sheetActiveFrom = Just $ termTime True Q1 (prog + 1) False Monday toMidnight - , sheetActiveTo = Just $ termTime True Q1 (prog + 2) False Sunday beforeMidnight - , sheetHintFrom = Just $ termTime True Q1 (prog + 1) False Sunday beforeMidnight - , sheetSolutionFrom = Just $ termTime True Q1 (prog + 2) False Sunday beforeMidnight - , sheetAutoDistribute = True - , sheetAnonymousCorrection = True - , sheetRequireExamRegistration = Nothing - , sheetAllowNonPersonalisedSubmission = True - , sheetAuthorshipStatementMode = SheetAuthorshipStatementModeExam - , sheetAuthorshipStatementExam = Nothing - , sheetAuthorshipStatement = Nothing - } - void . insert $ SheetEdit jost now shId - when (submissionModeCorrector sheetSubmissionMode) $ - forM_ [fhamann, maxMuster, tinaTester] $ \uid -> do - p <- liftIO getRandom - void . insert $ SheetPseudonym shId p uid - void . insert $ SheetCorrector jost shId (Load (Just True) 0 1) CorrectorNormal - void . insert $ SheetCorrector gkleen shId (Load (Just True) 1 1) CorrectorNormal - void . insert $ SheetCorrector svaupel shId (Load (Just True) 1 1) CorrectorNormal - void $ insertFile (SheetFileResidual shId SheetHint) "H10-2.hs" - void $ insertFile (SheetFileResidual shId SheetSolution) "H10-3.hs" - void $ insertFile (SheetFileResidual shId SheetExercise) "ProMo_Uebung10.pdf" - - forM_ [fhamann, maxMuster, tinaTester] $ \uid -> do - subId <- insert $ Submission - { submissionSheet = shId - , submissionRatingPoints = Nothing - , submissionRatingComment = Nothing - , submissionRatingBy = Nothing - , submissionRatingAssigned = Nothing - , submissionRatingTime = Nothing + , tutorialRegGroup = Just "schulung" + , tutorialRegisterFrom = jtt TermDayStart 0 Nothing toMidnight + , tutorialRegisterTo = jtt TermDayLectureStart (-1) Nothing toMidnight + , tutorialDeregisterUntil = jtt TermDayLectureStart (-5) (Just Monday) toMidnight + , tutorialLastChanged = now + , tutorialTutorControlled = True } - void . insert $ SubmissionEdit (Just uid) now subId - void . insert $ SubmissionUser uid subId - void $ insertFile (SubmissionFileResidual subId False False) "AbgabeH10-1.hs" - tut1 <- insert Tutorial - { tutorialName = "Di08" - , tutorialCourse = pmo - , tutorialType = "Tutorium" - , tutorialCapacity = Just 30 - , tutorialRoom = Just "Hilbert-Raum" - , tutorialRoomHidden = True - , tutorialTime = Occurrences - { occurrencesScheduled = Set.singleton $ ScheduleWeekly Tuesday (TimeOfDay 08 15 00) (TimeOfDay 10 00 00) - , occurrencesExceptions = Set.empty - } - , tutorialRegGroup = Just "tutorium" - , tutorialRegisterFrom = Just $ termTime True Q1 0 False Monday toMidnight - , tutorialRegisterTo = Nothing - , tutorialDeregisterUntil = Nothing - , tutorialLastChanged = now - , tutorialTutorControlled = True - } - void . insert $ Tutor tut1 gkleen - void . insert $ TutorialParticipant tut1 fhamann - tut2 <- insert Tutorial - { tutorialName = "Di10" - , tutorialCourse = pmo - , tutorialType = "Tutorium" - , tutorialCapacity = Just 30 - , tutorialRoom = Just "Hilbert-Raum" - , tutorialRoomHidden = True - , tutorialTime = Occurrences - { occurrencesScheduled = Set.singleton $ ScheduleWeekly Tuesday (TimeOfDay 10 15 00) (TimeOfDay 12 00 00) - , occurrencesExceptions = Set.empty - } - , tutorialRegGroup = Just "tutorium" - , tutorialRegisterFrom = Just $ termTime True Q1 0 False Monday toMidnight - , tutorialRegisterTo = Nothing - , tutorialDeregisterUntil = Nothing - , tutorialLastChanged = now - , tutorialTutorControlled = False - } - void . insert $ Tutor tut2 gkleen - -- datenbanksysteme - dbs <- insert' Course - { courseName = "Datenbanksysteme" - , courseDescription = Just "Datenbanken banken Daten damit die Daten nicht wanken. Die Datenschützer danken!" - , courseLinkExternal = Nothing - , courseShorthand = "DBS" - , courseTerm = TermKey $ seasonTerm False Q4 - , courseSchool = ifi - , courseCapacity = Just 50 - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Nothing - , courseRegisterTo = Nothing - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Just "dbs" - , courseMaterialFree = False - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = False - } - insert_ $ CourseEdit gkleen now dbs - void . insert' $ DegreeCourse dbs sdBsc sdInf - void . insert' $ DegreeCourse dbs sdBsc sdMath - void . insert' $ Lecturer gkleen dbs CourseLecturer - void . insert' $ Lecturer jost dbs CourseAssistant + insert_ $ Tutor tut1 jost + void . insert' $ Exam + { examCourse = c + , examName = mkName "Theorieprüfung" + , examGradingRule = Nothing + , examBonusRule = Nothing + , examOccurrenceRule = ExamRoomManual + , examExamOccurrenceMapping = Nothing + , examVisibleFrom = jtt TermDayStart 0 Nothing toMidnight + , examRegisterFrom = jtt TermDayStart 0 Nothing toMidnight + , examRegisterTo = jtt TermDayLectureStart (-1) Nothing toMidnight + , examDeregisterUntil = jtt TermDayLectureStart (-5) (Just Monday) toMidnight + , examPublishOccurrenceAssignments = Nothing + , examStart = Just $ toTimeOfDay 16 0 0 secondDay + , examEnd = Just $ toTimeOfDay 16 30 0 secondDay + , examFinished = Nothing + , examPartsFrom = Nothing + , examClosed = Nothing + , examPublicStatistics = True + , examGradingMode = ExamGradingPass + , examDescription = Just $ htmlToStoredMarkup [shamlet|Theoretische Prüfung mit Fragebogen|] + , examExamMode = ExamMode + { examAids = Just $ ExamAidsPreset ExamClosedBook + , examOnline = Just $ ExamOnlinePreset ExamOffline + , examSynchronicity = Just $ ExamSynchronicityPreset ExamSynchronous + , examRequiredEquipment = Just $ ExamRequiredEquipmentPreset ExamRequiredEquipmentNone + } + , examStaff = Just "Jost" + , examAuthorshipStatement = Nothing + } testMsg <- insert SystemMessage { systemMessageNewsOnly = False @@ -1224,7 +790,7 @@ fillDb = do , systemMessageLastUnhide = now } - + {- aSeedFunc <- liftIO $ getRandomBytes 40 funAlloc <- insert' Allocation { allocationName = "Funktionale Zentralanmeldung" @@ -1243,220 +809,18 @@ fillDb = do , allocationRegisterByStaffFrom = Nothing , allocationRegisterByStaffTo = Nothing , allocationRegisterByCourse = Nothing - , allocationOverrideDeregister = Just $ termTime True Q1 1 False Monday toMidnight + , allocationOverrideDeregister = Nothing , allocationMatchingSeed = aSeedFunc } insert_ $ AllocationCourse funAlloc pmo 100 Nothing Nothing insert_ $ AllocationCourse funAlloc ffp 2 (Just $ 2300 `addUTCTime` now) Nothing - + void . insertMany $ map (\(u, pState) -> CourseParticipant ffp u now (Just funAlloc) pState) [ (svaupel, CourseParticipantInactive False) , (jost, CourseParticipantActive) ] - -- void $ insertFile "H10-2.hs" -- unreferenced - - -- -- betriebssysteme - bs <- insert' Course - { courseName = "Betriebssystem" - , courseDescription = Nothing - , courseLinkExternal = Nothing - , courseShorthand = "BS" - , courseTerm = TermKey $ seasonTerm False Q4 - , courseSchool = ifi - , courseCapacity = Just 50 - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Nothing - , courseRegisterTo = Nothing - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = False - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = False - } - insert_ $ CourseEdit gkleen now bs - void . insert' $ Lecturer gkleen bs CourseLecturer - void . insertMany $ do - uid <- take 1024 manyUsers - return $ CourseParticipant bs uid now Nothing CourseParticipantActive - forM_ [1..14] $ \shNr -> do - shId <- insert Sheet - { sheetCourse = bs - , sheetName = CI.mk [st|Blatt #{tshow shNr}|] - , sheetDescription = Nothing - , sheetType = Normal $ PassPoints 12 6 - , sheetGrouping = Arbitrary 3 - , sheetMarkingText = Nothing - , sheetVisibleFrom = Just $ termTime False Q4 (fromInteger shNr) False Monday toMidnight - , sheetActiveFrom = Just $ termTime False Q4 (fromInteger $ succ shNr) False Monday toMidnight - , sheetActiveTo = Just $ termTime False Q4 (fromInteger $ succ shNr) False Sunday beforeMidnight - , sheetSubmissionMode = SubmissionMode False . Just $ UploadAny True Nothing False - , sheetHintFrom = Nothing - , sheetSolutionFrom = Nothing - , sheetAutoDistribute = False - , sheetAnonymousCorrection = True - , sheetRequireExamRegistration = Nothing - , sheetAllowNonPersonalisedSubmission = True - , sheetAuthorshipStatementMode = SheetAuthorshipStatementModeExam - , sheetAuthorshipStatementExam = Nothing - , sheetAuthorshipStatement = Nothing - } - manyUsers' <- shuffleM $ take 1024 manyUsers - groupSizes <- getRandomRs (1, 3) - let groups = go groupSizes manyUsers' - where go [] _ = [] - go (s:ss) us - | (grp, rest) <- splitAt s us - , length grp == s - = grp : go ss rest - | otherwise - = pure us - forM_ groups $ \grpUsers-> case grpUsers of - pUid : _ -> do - sub <- insert Submission - { submissionSheet = shId - , submissionRatingPoints = Nothing - , submissionRatingComment = Nothing - , submissionRatingBy = Nothing - , submissionRatingAssigned = Nothing - , submissionRatingTime = Nothing - } - forM_ grpUsers $ void . insert . flip SubmissionUser sub - void . insert $ SubmissionEdit (Just pUid) now sub - _other -> return () - - forM_ ([1..100] :: [Int]) $ \n -> do - csh <- pack . take 3 <$> getRandomRs ('A', 'Z') - - cid <- insert' Course - { courseName = CI.mk [st|Test Kurs #{n} (#{csh})|] - , courseDescription = Nothing - , courseLinkExternal = Nothing - , courseShorthand = CI.mk csh - , courseTerm = TermKey $ seasonTerm False Q4 - , courseSchool = ifi - , courseCapacity = Just 50 - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Nothing - , courseRegisterTo = Nothing - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = True - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = False - } - insert_ $ CourseEdit gkleen now cid - -- void . insert' $ Lecturer gkleen cid CourseLecturer - - participants <- getRandomR (0, 50) - manyUsers' <- shuffleM $ take 1024 manyUsers - forM_ (take participants manyUsers') $ \uid -> - void . insertUnique $ CourseParticipant cid uid now Nothing CourseParticipantActive - - aSeedBig <- liftIO $ getRandomBytes 40 - bigAlloc <- insert' Allocation - { allocationName = "Große Zentralanmeldung" - , allocationShorthand = "big" - , allocationTerm = TermKey $ seasonTerm True Q1 - , allocationSchool = ifi - , allocationLegacyShorthands = [] - , allocationDescription = Nothing - , allocationStaffDescription = Nothing - , allocationStaffRegisterFrom = Just now - , allocationStaffRegisterTo = Just $ 300 `addUTCTime` now - , allocationStaffAllocationFrom = Just $ 300 `addUTCTime` now - , allocationStaffAllocationTo = Just $ 900 `addUTCTime` now - , allocationRegisterFrom = Just $ 300 `addUTCTime` now - , allocationRegisterTo = Just $ 600 `addUTCTime` now - , allocationRegisterByStaffFrom = Nothing - , allocationRegisterByStaffTo = Nothing - , allocationRegisterByCourse = Nothing - , allocationOverrideDeregister = Just $ termTime True Q1 1 False Monday toMidnight - , allocationMatchingSeed = aSeedBig - } - bigAllocShorthands <- - let go xs = let (csh, xs') = List.splitAt 3 xs - in pack csh : go xs' - in take 40 . nubOrd . go <$> getRandomRs ('A', 'Z') - bigAllocCourses <- forM (zip [1..] bigAllocShorthands) $ \(n :: Natural, csh) -> do - cap <- getRandomR (10,50) - - minCap <- round . (* fromIntegral cap) <$> getRandomR (0, 0.5 :: Double) - - substitutesUntil <- (`addUTCTime` now) . fromInteger <$> getRandomR (900,2300) - - cid <- insert' Course - { courseName = CI.mk [st|Zentralanmeldungskurs #{n} (#{csh})|] - , courseDescription = Nothing - , courseLinkExternal = Nothing - , courseShorthand = CI.mk csh - , courseTerm = TermKey $ seasonTerm False Q4 - , courseSchool = ifi - , courseCapacity = Just cap - , courseVisibleFrom = Just now - , courseVisibleTo = Nothing - , courseRegisterFrom = Nothing - , courseRegisterTo = Nothing - , courseDeregisterUntil = Nothing - , courseRegisterSecret = Nothing - , courseMaterialFree = True - , courseApplicationsRequired = False - , courseApplicationsInstructions = Nothing - , courseApplicationsText = False - , courseApplicationsFiles = NoUpload - , courseApplicationsRatingsVisible = False - , courseDeregisterNoShow = False - } - insert_ $ CourseEdit gkleen now cid - void . insert' $ AllocationCourse bigAlloc cid minCap (Just substitutesUntil) Nothing - -- void . insert' $ Lecturer gkleen cid CourseLecturer - return cid - - forM_ manyUsers $ \uid -> do - totalCourses <- weighted $ do - n <- [1..10] - return (n, fromIntegral $ (1 - 10) ^ 2 - (1 - n) ^ 2) - - void . insert $ AllocationUser bigAlloc uid (fromIntegral totalCourses) Nothing - - appliedCourses <- weighted $ do - n <- [totalCourses - 2..totalCourses + 5] - return (n, fromIntegral $ (totalCourses + 1 - totalCourses - 5) ^ 2 - (totalCourses + 1 - n) ^ 2) - - appliedCourses' <- take appliedCourses <$> shuffleM bigAllocCourses - - forM_ (zip [0..] appliedCourses') $ \(prio, cid) -> do - rating <- weighted . Map.toList . Map.fromListWith (+) $ do - veto <- universeF :: [Bool] - grade <- universeF :: [ExamGrade] - rated <- universeF - - return ( bool Nothing (Just (veto, grade)) rated - , bool 5 1 veto * bool 5 1 rated - ) - - void $ insert CourseApplication - { courseApplicationCourse = cid - , courseApplicationUser = uid - , courseApplicationText = Nothing - , courseApplicationRatingVeto = maybe False (view _1) rating - , courseApplicationRatingPoints = view _2 <$> rating - , courseApplicationRatingComment = Nothing - , courseApplicationAllocation = Just bigAlloc - , courseApplicationAllocationPriority = Just prio - , courseApplicationTime = now - , courseApplicationRatingTime = now <$ rating - } + -} numericPriorities <- flip foldMapM manyUsers $ \uid -> do uRec <- get uid diff --git a/test/Model/TypesSpec.hs b/test/Model/TypesSpec.hs index 7f4e705ed..74d13b545 100644 --- a/test/Model/TypesSpec.hs +++ b/test/Model/TypesSpec.hs @@ -46,20 +46,26 @@ import qualified Data.Text.Lazy as LT import Text.Blaze.Html.Renderer.Text (renderHtml) +import Handler.Utils.DateTime (getYear) +{- +instance Arbitrary Day where + arbitrary = ModifiedJulianDay <$> choose (-313698, 2973483) -- 1000-01-1 to 9999-12-31 + -- arbitrary = ModifiedJulianDay <$> choose (15020, 2973483) -- 1900-01-1 to 9999-12-31 + shrink day = let + (y, m, d) = toGregorian day + dayShrink = [fromGregorian y m (d - 1) | d > 1] + monthShrink = [fromGregorian y (m - 1) d | m > 1] + yearShrink = [fromGregorian (y - 1) m d | y > 2000] + in dayShrink ++ monthShrink ++ yearShrink -instance Arbitrary Season where - arbitrary = genericArbitrary - shrink = genericShrink -instance CoArbitrary Season -instance Function Season +instance CoArbitrary Day where + coarbitrary (ModifiedJulianDay d) = coarbitrary d +-} instance Arbitrary TermIdentifier where - arbitrary = do - season <- arbitrary - year <- arbitrary `suchThat` (\y -> abs y >= 100) - return $ TermIdentifier{..} - shrink = filter ((\y -> abs y >= 100) . year) . genericShrink + arbitrary = TermIdentifier <$> arbitrary + shrink = fmap TermIdentifier . shrink . getTermDay instance CoArbitrary TermIdentifier instance Function TermIdentifier @@ -383,8 +389,6 @@ spec = do [ eqLaws, showReadLaws, ordLaws, boundedEnumLaws, finiteLaws, pathPieceLaws ] lawsCheckHspec (Proxy @Load) [ eqLaws, showReadLaws, ordLaws, jsonLaws, persistFieldLaws, commutativeSemigroupLaws, commutativeMonoidLaws ] - lawsCheckHspec (Proxy @Season) - [ eqLaws, showReadLaws, ordLaws, boundedEnumLaws ] lawsCheckHspec (Proxy @TermIdentifier) [ eqLaws, showReadLaws, ordLaws, enumLaws, persistFieldLaws, jsonLaws, httpApiDataLaws, pathPieceLaws ] lawsCheckHspec (Proxy @StudyFieldType) @@ -460,14 +464,14 @@ spec = do describe "TermIdentifier" $ do it "has compatible encoding/decoding to/from Text" . property $ - \term -> termFromText (termToText term) == Right term - it "works for some examples" . mapM_ termExample $ - [ (TermIdentifier 2017 Q2, "17Q2") - , (TermIdentifier 1995 Q4, "95Q4") - , (TermIdentifier 3068 Q1, "3068Q1") - ] - it "has compatbile encoding/decoding to/from Rational" . property $ + \term -> termFromText (termToText term) == Right term + it "has compatible encoding/decoding to/from Rational" . property $ \term -> termFromRational (termToRational term) == term + -- This is not sufficient + --it "has compatible encoding/decoding to/from PersistValue" . property $ + -- \term -> fromPersistValue (toPersistValue term) == term + it "has human readable year encoding to Rational" . property $ + \term -> truncate (termToRational term) == getYear (getTermDay term) describe "Pseudonym" $ do it "has sufficient vocabulary" $ (length pseudonymWordlist ^ 2) `shouldSatisfy` (> (fromIntegral (maxBound :: Pseudonym) - fromIntegral (minBound :: Pseudonym)))