-- SPDX-FileCopyrightText: 2022 Sarah Vaupel ,Steffen Jost -- -- SPDX-License-Identifier: AGPL-3.0-or-later Qualification -- INVARIANT: 2*refreshWithin < validDuration school SchoolId --TODO: Ansprechpartner der Schule in Briefe erwähnen shorthand (CI Text) name (CI Text) description StoredMarkup Maybe -- user-defined large Html, ought to contain full description validDuration Int Maybe -- > 0, qualification is valid indefinitely or for a specified number of months, use with addMonthsDay auditDuration Int Maybe -- > 0, number of months to keep audit log and LmsUserIdents; or indefinitely (dangerous, since LmsIdents may run out) refreshWithin CalendarDiffDays Maybe -- notify users about renewal within this number of month/days before expiry; to be used with addGregorianDurationClip elearningStart Bool -- automatically schedule e-refresher -- elearningOnly Bool -- successful E-learing automatically increases validity. NO! -- refreshInvitation StoredMarkup -- hard-coded I18N-MSGs used instead, but displayed on qualification page NO! -- expiryNotification StoredMarkup Maybe -- configurable user-profile-notifcations are used instead NO! avsLicence AvsLicence Maybe -- if set, valid QualificationUsers are synchronized to AVS as a driving licence sapId Text Maybe -- if set, valid QualificationUsers with userCompanyPersonalNumber are transmitted via SAP interface under this id SchoolQualificationShort school shorthand -- must be unique per school and shorthand SchoolQualificationName school name -- must be unique per school and name -- across all schools, only one qualification may be a driving licence: UniqueQualificationAvsLicence avsLicence !force -- NOTE: two NULL values are not equal for the purpose of Uniqueness constraints! deriving Eq Generic -- TODOs: -- - Enstehen Kosten, wenn Teilnehmer für KnowHow eingereiht werden, aber nicht am Kurs teilnehmen? -- Falls ja, so sollte bei automatischem refresher vorher der Kunde durch FRADrive befragt werden?! -- A: Der Inhaber per Email informieren! -- A: Es kann gleich eine LMS Pin generiert und verschickt werden! -- - Aufteilung Qualification "R" in zwei Teile: "R e-learning" und "R praxis" okay? -- Besonderheiten: -- - LmsIdent muss für alle Qualificationen einzigartig sein! -- - Durchfallen wird mit UserList ständig erneut gesandt, bis Löschantrag gestellt wurde. -- - Bestehen mit Result wird nur ein einziges mal gesendet! (Ausfallrisiko: keine Bestätigung der Kommunikation!) -- - Explizites Löschen eines LmsIdent nach Success/Failure ist notwendig (feedback bei Block) -- - LmsUser soll nur DELTA übermitteln. (GET Request will always return the same; until POST Request was processed!) -- - PinReset==1 mit bestehendem Passwort kann problemlos erneut gesendet werden -- - Flag "interner Mitarbeiter" wird von Know-How ignoriert / nicht ausgewertet (legacy) QualificationPrecondition qualification QualificationId OnDeleteCascade OnUpdateCascade -- AND: not unique, ie. qualification can have multiple required preconditions required [QualificationId] -- OR : alternatives, any one will suffice continuous Bool -- expiring precondition removes qualification deriving Generic -- TODO: connect Qualification with Exams! QualificationEdit user UserId time UTCTime qualification QualificationId OnDeleteCascade OnUpdateCascade deriving Generic QualificationUser user UserId OnDeleteCascade OnUpdateCascade qualification QualificationId OnDeleteCascade OnUpdateCascade validUntil Day -- addGregorianMonthsRollOver (toInteger renewalMonths) qualificationUserValidUntil lastRefresh Day -- lastRefresh > validUntil possible, if Qualification^elearningOnly == False firstHeld Day -- first time the qualification was earned, should never change blockedDue QualificationBlocked Maybe -- isJust means that the qualification is currently revoked scheduleRenewal Bool default=true -- if false, no automatic renewal is scheduled and the qualification expires -- temporärer Entzug vorsehen -- SAP Schnittstelle muss dann angepasst werden -- Begründungsfeld vorsehen UniqueQualificationUser qualification user deriving Generic -- LMS Interface Tables, need regular processing by background jobs, per QualificationId: -- -- 1. Daily Job: Add to LmsUser daily all qualification holders with -- QualificationUserValidUntil >= now -- /\ QualificationUserValudUntil <= now + QualificationRefreshWithin (time to schedule refresher) -- /\ not already enlisted -- -- 2. REST GET User.csv: -- - where LmsUserReceived == Nothing \/ (LmsUserResetPin /\ LmsUserEnded == Nothing) -- - delete-flag: isJust LmsUserStatus -- Note: REST means that LmsUserResetPin and LmsUserDelete remain unchanged by this GET request! -- -- 3. REST POST Userlist.csv: just save as is to LmsUserlist -- -- 4. REST POST Ergebnisse.csv: just save as is to LmsResult -- -- 5. When received: Job LmsUserlist: -- Note: containment needs at-once processing -- - For all LmsUser: -- + if contained: -- set LmsUserReceived to Just now() -- if LmsUserlistFailed: set LmsUserStatus to Just LmsBlocked now -- + not contained, by LmsUserReceived is set: set LmsUserEnded to Just now() -- - move row to LmsAudit -- -- 6. When received: Daily Job LmsResult: -- - set LmsUserReceived to Just now() -- always -- - set LmsUserStatus to Just LmsSuccess now -- conditional -- - and renew QualificationValidTo -- - move row to LmsAudit -- -- 7. Daily Job: dequeue LMS Users -- - remove from LmsUser after audit Period has passed LmsUser qualification QualificationId OnDeleteCascade OnUpdateCascade user UserId OnDeleteCascade OnUpdateCascade ident LmsIdent -- must be unique accross all LMS courses! pin Text resetPin Bool default=false -- should pin be reset? datePin UTCTime default=now() -- time pin was created status LmsStatus Maybe -- open, success or failure; status should never change unless isNothing; isJust indicates lms is finished and user shall be deleted from LMS --toDelete encoded by Handler.Utils.LMS.lmsUserToDelete started UTCTime default=now() received UTCTime Maybe -- last acknowledgement by LMS notified UTCTime Maybe -- last notified by FRADrive ended UTCTime Maybe -- ident was deleted from LMS -- Primary ident -- newtype Key LmsUserId = LmsUserKey { unLmsUser :: Text } -- change LmsIdent -> Text. Do we want this? UniqueLmsIdent ident -- idents must be unique accross all qualifications, since idents are global within LMS! UniqueLmsQualificationUser qualification user -- each user may be enrolled at most once per course deriving Generic -- LmsUserlist stores LMS upload for later processing only LmsUserlist qualification QualificationId OnDeleteCascade OnUpdateCascade ident LmsIdent failed Bool timestamp UTCTime default=now() UniqueLmsUserlist qualification ident deriving Generic Show -- LmsResult stores LMS upload for later processing only LmsResult qualification QualificationId OnDeleteCascade OnUpdateCascade ident LmsIdent success Day -- BEWARE: timezone is local as submitted by LMS timestamp UTCTime default=now() UniqueLmsResult qualification ident -- required by DBTable deriving Generic