diff --git a/messages/uniworx/categories/qualification/de-de-formal.msg b/messages/uniworx/categories/qualification/de-de-formal.msg index 1cbc49ee6..a2c00de7a 100644 --- a/messages/uniworx/categories/qualification/de-de-formal.msg +++ b/messages/uniworx/categories/qualification/de-de-formal.msg @@ -55,7 +55,7 @@ LmsErrorNoRefreshElearning: Fehler: E-Learning wird nicht automatisch gestartet, MailSubjectQualificationRenewal qname@Text: Qualifikation #{qname} muss demnächst erneuert werden MailSubjectQualificationExpiry qname@Text: Qualifikation #{qname} läuft demnächst ab MailSubjectQualificationExpired qname@Text: Qualifikation #{qname} ist ab sofort ungültig -MailBodyQualificationRenewal: Sie müssen diese Qualifikaton demnächst durch einen E-Learning Kurs erneuern. +MailBodyQualificationRenewal qname@Text: Sie müssen Qualifikaton #{qname} demnächst durch einen E-Learning Kurs erneuern, siehe Anhang. MailBodyQualificationExpiry: Diese Qualifikaton läuft bald ab. Tätigkeiten, welche diese Qualifikation voraussetzen dürfen dann nicht länger ausgeübt werden! MailBodyQualificationExpired: Diese Qualifikaton is nun abgelaufen. Tätigkeiten, welche diese Qualifikation voraussetzen dürfen ab sofort nicht länger ausgeübt werden! Es ist möglich, dass die Qualifikation vorzeit ungültig wurde, z.B. wegen erfolgloser Teilnahme an einem verpflichtendem E-Learning. LmsRenewalInstructions: Anweisungen zur Verlängerung finden Sie im angehängten PDF. Um Missbrauch zu verhindern wurde das PDF dem von Ihnen in FRADrive hinterlegten PDF-Passwort verschlüsselt. Falls kein PDF-Passwort hinterlegt wurde, ist das PDF-Passwort Ihre Fraport Ausweisnummer, inklusive Punkt und der Ziffer danach. diff --git a/messages/uniworx/categories/qualification/en-eu.msg b/messages/uniworx/categories/qualification/en-eu.msg index 2ee82d305..f92509750 100644 --- a/messages/uniworx/categories/qualification/en-eu.msg +++ b/messages/uniworx/categories/qualification/en-eu.msg @@ -52,10 +52,10 @@ LmsResultCsvExceptionDuplicatedKey: CSV import with ambiguous key LmsUserlistCsvExceptionDuplicatedKey: CSV import with ambiguous key LmsDirectUpload: Direct upload for automated Systems LmsErrorNoRefreshElearning: Error: E-learning will not be started automatically due to refresh-within time period not being set. -MailSubjectQualificationRenewal qname@Text: Qualification #{qname} must be renewed shortly -MailSubjectQualificationExpiry qname@Text: Qualification #{qname} expires soon -MailSubjectQualificationExpired qname@Text: Qualification #{qname} is no longer valid -MailBodyQualificationRenewal: You will soon need to renew this qualficiation by completing an e-learning course. +MailSubjectQualificationRenewal qname: Qualification #{qname} must be renewed shortly +MailSubjectQualificationExpiry qname: Qualification #{qname} expires soon +MailSubjectQualificationExpired qname: Qualification #{qname} is no longer valid +MailBodyQualificationRenewal qname: You will soon need to renew qualficiation #{qname} by completing an e-learning course. For details see attachment. MailBodyQualificationExpiry: This qualificaton expires soon. You may then no longer execute any duties that require this qualification as a precondition! MailBodyQualificationExpired: This qualificaton is now expired. You may no longer execute any duties that require this qualification as a precondition! It is possible that the qualification expired prematurely, e.g. due to a failed compulsory e-learning. LmsRenewalInstructions: Instruction on how to accomplish the renewal are enclosed in the attached PDF. In order to avoid misuse, the PDF is encrypted with your chosen FRADrive PDF-Password. If you have not yet chosen a PDF-Password yet, then the password is your Fraport id card number, inkluding the punctuation mark and the Digit thereafter. diff --git a/src/Handler/LMS/Fake.hs b/src/Handler/LMS/Fake.hs index a9b3b1703..00b470ba8 100644 --- a/src/Handler/LMS/Fake.hs +++ b/src/Handler/LMS/Fake.hs @@ -68,10 +68,20 @@ fakeQualificationUsers (Entity qid Qualification{qualificationRefreshWithin}) (u PWHashConf{..} <- getsYesod $ view _appAuthPWHash pwHash <- liftIO $ PWStore.makePasswordWith pwHashAlgorithm pw pwHashStrength return $ AuthPWHash $ TEnc.decodeUtf8 pwHash - let expiryOffsets = concatMap (replicate usersPerDay) [0..(diffDays dto dfrom)] + theSupervisor <- selectKeysList [UserSurname ==. "Jost", UserFirstName ==. "Steffen"] [Asc UserCreated, LimitTo 1] + let addSupervisor = case theSupervisor of + [s] -> \suid k -> case k of + 1 -> void $ insertBy $ UserSupervisor s suid True + 2 -> do + void $ insertBy $ UserSupervisor s suid True + void $ insertBy $ UserSupervisor suid suid True + 3 -> void $ insertBy $ UserSupervisor s suid True + _ -> return () + _ -> \_ _ -> return () + expiryOffsets = concatMap (replicate usersPerDay) [0..(diffDays dto dfrom)] expiryNotifyDay = addGregorianDurationClip (fromMaybe calendarDay qualificationRefreshWithin) dfrom - fakeUser :: ([Text], UserSurname, (Maybe Languages, DateTimeFormat, DateTimeFormat, DateTimeFormat), Bool) -> User - fakeUser (firstNames, userSurname, (userLanguages, userDateTimeFormat, userDateFormat, userTimeFormat), userPrefersPostal) = + fakeUser :: ([Text], UserSurname, (Maybe Languages, DateTimeFormat, DateTimeFormat, DateTimeFormat), Bool, Int) -> User + fakeUser (firstNames, userSurname, (userLanguages, userDateTimeFormat, userDateFormat, userTimeFormat), userPrefersPostal, _isSupervised) = let userIdent = CI.mk $ Text.intercalate "." (take 1 firstNames ++ (Text.take 1 <$> drop 1 firstNames) ++ [userSurname]) <> "@example.com" userEmail = userIdent userDisplayEmail = userIdent @@ -122,6 +132,7 @@ fakeQualificationUsers (Entity qid Qualification{qualificationRefreshWithin}) (u [ QualificationUserValidUntil =. qualificationUserValidUntil , QualificationUserLastRefresh =. qualificationUserLastRefresh ] + addSupervisor uid (user ^. _5) return $ either (const 0) (const 1) euid -- ok <- insertUnique QualificationUser{..} -- We do not overwrite any existing qualifications, just to be on the save side: -- return $ maybe 0 (const 1) ok @@ -154,11 +165,13 @@ fakeQualificationUsers (Entity qid Qualification{qualificationRefreshWithin}) (u , (Just $ Languages ["fr"] , DateTimeFormat "%d-%m-%Y %R" , DateTimeFormat "%d-%m-%Y" , DateTimeFormat "%R") , (Just $ Languages ["fr","en"] , DateTimeFormat "%B %d %Y %R" , DateTimeFormat "%B %d %y" , DateTimeFormat "%I:%M:%S %p") ] - postal = [False, True, False] + postal = [False, True, False] + supervised = [0,1,2,3] - names = getZipList $ (\f m s l p -> (f : concat m, s, l, p)) + names = getZipList $ (\f m s l p v -> (f : concat m, s, l, p, v)) <$> ZipList (cycle givenNames) <*> ZipList (cycle middlenames) <*> ZipList (cycle surnames) <*> ZipList (cycle someLangs) <*> ZipList (cycle postal) + <*> ZipList (cycle supervised) diff --git a/src/Handler/LMS/Users.hs b/src/Handler/LMS/Users.hs index 6c5d69e9e..e942cde35 100644 --- a/src/Handler/LMS/Users.hs +++ b/src/Handler/LMS/Users.hs @@ -153,6 +153,7 @@ getLmsUsersR sid qsh = do getLmsUsersDirectR :: SchoolId -> QualificationShorthand -> Handler TypedContent getLmsUsersDirectR sid qsh = do + $logInfoS "LMS" $ "Direct Download Users for " <> tshow qsh <> " at " <> tshow sid lms_users <- runDB $ do qid <- getKeyBy404 $ SchoolQualificationShort sid qsh selectList [LmsUserQualification ==. qid, LmsUserEnded ==. Nothing] [Asc LmsUserStarted, Asc LmsUserIdent] diff --git a/src/Jobs/Handler/SendNotification/Qualification.hs b/src/Jobs/Handler/SendNotification/Qualification.hs index 0409ed2f6..0d519e183 100644 --- a/src/Jobs/Handler/SendNotification/Qualification.hs +++ b/src/Jobs/Handler/SendNotification/Qualification.hs @@ -14,13 +14,8 @@ import Import import Utils.Print import Handler.Utils -import Handler.Utils.Users import Jobs.Handler.SendNotification.Utils -import qualified Data.ByteString.Lazy as LBS - -import qualified Data.Text as Text - -- import Handler.Info (FAQItem(..)) import qualified Data.CaseInsensitive as CI import Text.Hamlet @@ -68,89 +63,35 @@ dispatchNotificationQualificationExpired nQualification dExpired jRecipient = us addHtmlMarkdownAlternatives $(ihamletFile "templates/mail/qualificationExpired.hamlet") --- NOTE: qualificationRenewal expects that LmsUser already exists for recipient +-- NOTE: Renewal expects that LmsUser already exists for recipient dispatchNotificationQualificationRenewal :: QualificationId -> UserId -> Handler () dispatchNotificationQualificationRenewal nQualification jRecipient = do - (recipient@User{..}, Qualification{..}, Entity _ QualificationUser{..}, Entity luid LmsUser{..}) <- runDB $ (,,,) - <$> getJust jRecipient - <*> getJust nQualification - <*> getJustBy (UniqueQualificationUser nQualification jRecipient) - <*> getJustBy (UniqueLmsQualificationUser nQualification jRecipient) - encRecipient :: CryptoUUIDUser <- encrypt jRecipient - let entRecipient = Entity jRecipient recipient - qname = CI.original qualificationName - - $logInfoS "LMS" $ "Notify " <> tshow encRecipient <> " for renewal of qualification " <> qname - - now <- liftIO getCurrentTime - letterDate <- formatTimeUser SelFormatDate now $ Just entRecipient - expiryDate <- formatTimeUser SelFormatDate qualificationUserValidUntil $ Just entRecipient - - let printJobName = "RenewalPin" - fileName = printJobName <> "_" <> (text2asciiAlphaNum . abbrvName) recipient <> ".pdf" - lmsIdent = lmsUserIdent & getLmsIdent - lmsUrl = "https://drive.fraport.de" - lmsLogin = lmsUrl <> "/?login=" <> lmsIdent - prepAddress upa = userDisplayName : (upa & html2textlines) -- TODO: use supervisor's address, once implemented - pdfMeta = mkMeta - [ toMeta "date" letterDate - , toMeta "lang" (selectDeEn userLanguages) -- select either German or English only, see Utils.Lang - , toMeta "login" lmsIdent - , toMeta "pin" lmsUserPin - , toMeta "examinee" userDisplayName - , mbMeta "address" (prepAddress <$> userPostAddress) -- TODO: this is buggy if there is no address set! - , toMeta "expiry" expiryDate - , mbMeta "validduration" (show <$> qualificationValidDuration) - , toMeta "url-text" lmsUrl - , toMeta "url" lmsLogin - ] - emailRenewal attachment - | Text.null (CI.original userEmail) = do -- if neither email nor postal address is known, we must abort! - let msg = "Notify " <> tshow encRecipient <> " failed: no email nor address for user known!" - $logErrorS "LMS" msg - return False - | otherwise = do - userMailT jRecipient $ do - replaceMailHeader "Auto-Submitted" $ Just "auto-generated" - setSubjectI $ MsgMailSubjectQualificationRenewal qname - editNotifications <- mkEditNotifications jRecipient - addHtmlMarkdownAlternatives $(ihamletFile "templates/mail/qualificationRenewal.hamlet") - whenIsJust attachment $ \afile -> - addPart (File { fileTitle = Text.unpack fileName - , fileModified = now - , fileContent = Just $ yield $ LBS.toStrict afile - } :: PureFile) - return True - - notifyOk <- pdfRenewal pdfMeta >>= \case - Right pdf | userPrefersLetter recipient -> -- userPrefersLetter is false if both userEmail and userPostAddress are null - let printSender = Nothing - in runDB (sendLetter' printJobName pdf (Just jRecipient, printSender) Nothing (Just nQualification) (Just lmsUserIdent)) >>= \case - Left err -> do - let msg = "Notify " <> tshow encRecipient <> ": PDF printing to send letter failed with error " <> cropText err - $logErrorS "LMS" msg - return False - Right (msg,_) - | null msg -> return True - | otherwise -> do - $logWarnS "LMS" $ "PDF printing to send letter with lpr returned ExitSucces and the following message: " <> msg - return True - - Right pdf -> do - attch <- case userPinPassword of - Nothing -> return $ Just pdf -- attach unencrypted, since there is no password set - Just passwd -> encryptPDF passwd pdf >>= \case - Right encPdf -> return $ Just encPdf -- attach encrypted - Left err -> do -- send email without attachment, so that the user is at least notified about the expiry - let msg = "Notify " <> tshow encRecipient <> " PDF encryption failed with error: " <> cropText err - $logErrorS "LMS" msg - return Nothing - emailRenewal attch - - Left err -> do - let msg = "Notify " <> tshow encRecipient <> " PDF generation failed with error: " <> cropText err - $logErrorS "LMS" msg - emailRenewal Nothing - - when notifyOk $ runDB $ update luid [ LmsUserNotified =. Just now] + query <- runDB $ (,,,) + <$> get jRecipient + <*> get nQualification + <*> getBy (UniqueQualificationUser nQualification jRecipient) + <*> getBy (UniqueLmsQualificationUser nQualification jRecipient) + case query of + (Just User{userDisplayName}, Just Qualification{..}, Just (Entity _ QualificationUser{..}), Just(Entity luid LmsUser{..})) -> do + let qname = CI.original qualificationName + let letter = LetterRenewQualificationF + { lmsLogin = lmsUserIdent + , lmsPin = lmsUserPin + , qualHolder = userDisplayName + , qualExpiry = qualificationUserValidUntil + , qualId = nQualification + , qualName = qname + , qualShort = CI.original qualificationShorthand + , qualDuration = qualificationValidDuration + } + $logInfoS "LMS" $ "Notify " <> tshow encRecipient <> " for renewal of qualification " <> qname + notifyOk <- sendEmailOrLetter jRecipient letter + when notifyOk $ do + now <- liftIO getCurrentTime + runDB $ update luid [ LmsUserNotified =. Just now] + (_, Nothing, _, _) -> $logErrorS "LMS" $ "Notify " <> tshow encRecipient <> " for renewal failed: Qualification " <> tshow nQualification <> " does not exist!" + (Nothing, _, _, _) -> $logErrorS "LMS" $ "Notify " <> tshow encRecipient <> " for renewal failed: User does not exist!" + (_, _, Nothing, _) -> $logErrorS "LMS" $ "Notify " <> tshow encRecipient <> " for renewal failed: QualificationUser does not exist, i.e. user does not have this qualification!" + (_, _, _, Nothing) -> $logWarnS "LMS" $ "Notify " <> tshow encRecipient <> " for renewal failed: LmsUser does not exist!" + \ No newline at end of file diff --git a/src/Utils/Print.hs b/src/Utils/Print.hs index 4a1794c6b..ff5a65bcb 100644 --- a/src/Utils/Print.hs +++ b/src/Utils/Print.hs @@ -7,6 +7,7 @@ module Utils.Print ( pdfRenewal , sendLetter, sendLetter' + , sendEmailOrLetter , encryptPDF , sanitizeCmdArg, validCmdArgument , templateDIN5008 @@ -15,6 +16,7 @@ module Utils.Print , _Meta, addMeta , toMeta, mbMeta -- single values , mkMeta, appMeta, applyMetas -- multiple values + , LetterRenewQualificationF(..) ) where -- import Import.NoModel @@ -40,6 +42,7 @@ import System.Process.Typed -- for calling pdftk for pdf encryption import Handler.Utils.Users import Handler.Utils.DateTime import Handler.Utils.Mail +import Handler.Utils.Widgets (nameHtml') import Jobs.Handler.SendNotification.Utils -- import Model.Types.Markup -- TODO-QSV: should this module be moved accordingly? @@ -363,24 +366,28 @@ convertProto f (IsTime t) = P.toMetaValue $ f t -} class MDLetter l where - getTemplate :: Proxy l -> Text - getSubject :: Proxy l -> SomeMessage UniWorX - letterMeta :: l -> Lang -> DateTimeFormatter -> P.Meta - getPJId :: l -> PrintJobIdentification + getTemplate :: Proxy l -> Text + getMailSubject :: l -> SomeMessage UniWorX -- only used if letter is sent by email as pdf attachment + getMailBody :: l -> SomeMessage UniWorX -- only used if letter is sent by email as pdf attachment + letterMeta :: l -> Lang -> DateTimeFormatter -> P.Meta + getPJId :: l -> PrintJobIdentification data LetterRenewQualificationF = LetterRenewQualificationF { lmsLogin :: LmsIdent , lmsPin :: Text - , qualId :: QualificationId - , qualHolder :: Text + , qualHolder :: UserDisplayName , qualExpiry :: Day + , qualId :: QualificationId + , qualName :: Text + , qualShort :: Text , qualDuration :: Maybe Int } deriving (Eq, Show) instance MDLetter LetterRenewQualificationF where - getTemplate _ = templateRenewal - getSubject _ = SomeMessage $ MsgMailSubjectQualificationRenewal "F" + getTemplate _ = templateRenewal + getMailSubject l = SomeMessage $ MsgMailSubjectQualificationRenewal $ qualShort l + getMailBody l = SomeMessage $ MsgMailBodyQualificationRenewal $ qualName l letterMeta LetterRenewQualificationF{..} _lang DateTimeFormatter{ format } = mkMeta [ toMeta "login" lmsIdent , toMeta "pin" lmsPin @@ -404,31 +411,37 @@ instance MDLetter LetterRenewQualificationF where , pjiLmsUser = Just lmsLogin } -sendEmailOrLetter :: (MDLetter l) => UserId -> l -> m Bool +sendEmailOrLetter :: (MDLetter l) => UserId -> l -> Handler Bool sendEmailOrLetter recipient letter = do (underling, receivers, undercopy) <- liftHandler . runDB $ getReceivers recipient let tmpl = getTemplate $ pure letter pjid = getPJId letter + -- Below are only needed if sent by email + mailSubject = getMailSubject letter + mailBody = getMailBody letter + undername = underling ^. _userDisplayName -- nameHtml' underling + undermail = CI.original $ underling ^. _userEmail now <- liftIO getCurrentTime - oks <- forM receivers $ \rcvr@Entity{ entityKey = svr, entityVal = rcvrUsr } -> do + oks <- forM receivers $ \Entity{ entityKey = svr, entityVal = rcvrUsr } -> do formatter@DateTimeFormatter{ format } <- getDateTimeFormatterUser' rcvrUsr - let (preferPost,postal) = getPostalPreferenceAndAddress rcvrUsr - -- continue here, since post = Nothing might happen here?! + let (preferPost, postal) = getPostalPreferenceAndAddress rcvrUsr + isSupervised = recipient /= svr lang = selectDeEn $ rcvrUsr & userLanguages -- select either German or English only, default de; see Utils.Lang lMeta = letterMeta letter lang formatter <> mkMeta - [ toMeta "lang" lang - , toMeta "date" $ format SelFormatDate now - , toMeta "address" $ fromMaybe (rcvrUsr & userDisplayName) postal + [ toMeta "lang" lang + , toMeta "date" $ format SelFormatDate now + , toMeta "address" $ fromMaybe [rcvrUsr & userDisplayName] postal + , mbMeta "supervisor" $ toMaybe isSupervised (rcvrUsr & userDisplayName) ] pdfLetter tmpl lMeta >>= \case _ | preferPost, isNothing postal -> do -- neither email nor postal is known encRecipient :: CryptoUUIDUser <- encrypt svr - let msg = "Notification failed for " <> tshow encRecipient <> ", who has neither a known email nor postal address. Notfication: " <> tshow pjid + let msg = "Notification failed for " <> tshow encRecipient <> ", who has neither a known email nor postal address. Notification: " <> tshow pjid $logErrorS "LETTER" msg return False Left err -> do -- pdf generation failed encRecipient :: CryptoUUIDUser <- encrypt svr - let msg = "Notification failed for " <> tshow encRecipient <> ". PDF generation failed. Notfication: " <> tshow pjid + let msg = "Notification failed for " <> tshow encRecipient <> ". PDF generation failed: "<> cropText err <> "For Notification: " <> tshow pjid $logErrorS "LETTER" msg return False Right pdf | preferPost -> -- send letter @@ -448,17 +461,17 @@ sendEmailOrLetter recipient letter = do Nothing -> return pdf Just passwd -> encryptPDF passwd pdf >>= \case Right encPdf -> return encPdf - Left err -> do + Left err -> do encRecipient :: CryptoUUIDUser <- encrypt svr let msg = "Notification for " <> tshow encRecipient <> " has unencrypted attachment. Encrypting PDF failed: " <> cropText err $logWarnS "LETTER" msg return pdf userMailTdirect svr $ do replaceMailHeader "Auto-Submitted" $ Just "auto-generated" - setSubjectI $ getSubject $ pure letter + setSubjectI mailSubject editNotifications <- mkEditNotifications svr - -- TODO: create generic template - addHtmlMarkdownAlternatives $(ihamletFile "templates/mail/qualificationRenewal.hamlet") + let supername = rcvrUsr ^. _userDisplayName -- nameHtml' rcvrUsr + addHtmlMarkdownAlternatives $(ihamletFile "templates/mail/genericMailLetter.hamlet") addPart (File { fileTitle = T.unpack $ pjiName pjid , fileModified = now , fileContent = Just $ yield $ LBS.toStrict attachment diff --git a/templates/letter/fraport_renewal.md b/templates/letter/fraport_renewal.md index b00072a77..c2240db34 100644 --- a/templates/letter/fraport_renewal.md +++ b/templates/letter/fraport_renewal.md @@ -53,13 +53,24 @@ $endfor$ $if(is-de)$ -die Gültigkeit des Vorfeldführerscheins läuft demnächst ab. +die Gültigkeit +$if(supervisor)$ + des Vorfeldführerscheins von $examinee$ +$else$ + Ihres Vorfeldführerscheins +$endif$ +läuft bald ab. Durch die erfolgreiche Teilnahme an einem E-Learning kann die Gültigkeit $if(validduration)$ um $validduration$ Monate $endif$ -verlängert werden. Dazu bitte die Login-Daten -aus dem geschützen Sichtfenster weiter unten verwenden. +verlängert werden. +$if(supervisor)$ + Ansprechpartner werden gebeten, die Login-Daten aus dem geschützen Sichtfenster weiter unten + vertraulich an den Prüfling weiterzuleiten. +$else$ + Dazu bitte die Login-Daten aus dem geschützen Sichtfenster weiter unten verwenden. +$endif$ Prüfling @@ -84,13 +95,24 @@ $else$ -the apron diving license is about to expire soon. +$if(supervisor)$ + the apron diving license of $examinee$ +$else$ + your apron diving license +$endif$ +is about to expire soon. The validity may be extended $if(validduration)$ by $validduration$ months $endif$ by successfully participating in -an e-learning. Please use the login data from the protected area below. +an e-learning. +$if(supervisor)$ + Supervisors are kindly requested to confidentially forward the login data + from the protected area below to the examinee. +$else$ + Please use the login data from the protected area below. +$endif$ Examinee diff --git a/templates/mail/genericMailLetter.hamlet b/templates/mail/genericMailLetter.hamlet new file mode 100644 index 000000000..e705cc467 --- /dev/null +++ b/templates/mail/genericMailLetter.hamlet @@ -0,0 +1,44 @@ +$newline never + +$# SPDX-FileCopyrightText: 2022 Steffen Jost +$# +$# SPDX-License-Identifier: AGPL-3.0-or-later + +\ + + + +