diff --git a/messages/uniworx/misc/de-de-formal.msg b/messages/uniworx/misc/de-de-formal.msg index 3ed3bd645..36db7750e 100644 --- a/messages/uniworx/misc/de-de-formal.msg +++ b/messages/uniworx/misc/de-de-formal.msg @@ -4,8 +4,8 @@ #messages or constructors that are used all over the code -Logo !ident-ok: Uni2work -EmailInvitationWarning: Diese Adresse konnte keinem Uni2work-Benutzer/keiner Uni2work-Benutzerin zugeordnet werden. Es wird eine Einladung per E-Mail versandt. +Logo !ident-ok: FRADrive +EmailInvitationWarning: Diese Adresse konnte keinem FRADrive-Benutzer/-Benutzerin zugeordnet werden. Es wird eine Einladung per E-Mail versandt. BoolIrrelevant !ident-ok: — FieldPrimary: Hauptfach FieldSecondary: Nebenfach @@ -15,6 +15,7 @@ WeekDay: Wochentag LdapIdentificationOrEmail: Fraport AG-Kennung / E-Mail-Adresse Months num@Int64: #{num} #{pluralDE num "Monat" "Monate"} Days num@Int64: #{num} #{pluralDE num "Tag" "Tage"} +NoAutomaticUpdateTip: Dieser Wert wurde manuell editiert und wird daher nicht mehr automatisch aktualisiert. ClusterVolatileQuickActionsEnabled: Schnellzugriffsmenü aktiv diff --git a/messages/uniworx/misc/en-eu.msg b/messages/uniworx/misc/en-eu.msg index d652ed4ba..b968ce9c0 100644 --- a/messages/uniworx/misc/en-eu.msg +++ b/messages/uniworx/misc/en-eu.msg @@ -4,8 +4,8 @@ #messages or constructors that are used all over the Code -Logo: Uni2work -EmailInvitationWarning: This address could not be matched to any Uni2work user. An invitation will be sent via email. +Logo: FRADrive +EmailInvitationWarning: This address could not be matched to any FRADrive user. An invitation will be sent via email. BoolIrrelevant: — FieldPrimary: Major FieldSecondary: Minor @@ -15,6 +15,7 @@ WeekDay: Day of the week LdapIdentificationOrEmail: Fraport AG-Kennung / email address Months num: #{num} #{pluralEN num "Month" "Months"} Days num: #{num} #{pluralEN num "Day" "Days"} +NoAutomaticUpdateTip: This value receives no automatic updates, since it has been edited manually. ClusterVolatileQuickActionsEnabled: Quick actions enabled diff --git a/models/company.model b/models/company.model index c123e281b..4ed5274e1 100644 --- a/models/company.model +++ b/models/company.model @@ -11,15 +11,8 @@ Company prefersPostal Bool default=false -- new company users prefers letters by post instead of email postAddress StoredMarkup Maybe -- default company postal address, including company name email UserEmail Maybe -- Case-insensitive generic company eMail address - UniqueCompanyName name + -- UniqueCompanyName name -- Should be Unique in AVS, but we do not yet need to enforce it -- UniqueCompanyShorthand shorthand -- unnecessary, since it is the primary key already - UniqueCompanyAvsId avsId - Primary shorthand -- newtype Key Company = CompanyKey { unCompanyKey :: CompanyShorthand } + UniqueCompanyAvsId avsId -- Should be the key, is not for historical reasons and for convenience in URLs and columns + Primary shorthand -- newtype Key Company = CompanyKey { unCompanyKey :: CompanyShorthand } deriving Ord Eq Show Generic Binary - --- -- TODO: a way to populate this table (manually) --- CompanySynonym --- synonym CompanyName --- canonical CompanyShorthand OnDeleteCascade OnUpdateCascade -- DEPRECATED: should be CompanyId --- UniqueCompanySynonym synonym --- deriving Ord Eq Show Generic diff --git a/src/Handler/Admin/Avs.hs b/src/Handler/Admin/Avs.hs index 82d739bb8..d117376e8 100644 --- a/src/Handler/Admin/Avs.hs +++ b/src/Handler/Admin/Avs.hs @@ -93,7 +93,7 @@ validateAvsQueryPerson = do is _Just avsPersonQueryInternalPersonalNo || is _Just avsPersonQueryVersionNo -makeAvsStatusForm :: Maybe AvsQueryStatus -> Form AvsQueryStatus +makeAvsStatusForm :: Maybe AvsPersonId -> Form AvsQueryStatus makeAvsStatusForm tmpl = identifyForm FIDAvsQueryStatus . validateForm validateAvsQueryStatus $ \html -> flip (renderAForm FormStandard) html $ parseAvsIds <$> areq textField (fslI MsgAvsPersonId) (unparseAvsIds <$> tmpl) @@ -103,15 +103,15 @@ makeAvsStatusForm tmpl = identifyForm FIDAvsQueryStatus . validateForm validateA where nonemptys = filter (not . Text.null) $ Text.strip <$> Text.split (==',') txt ids = mapMaybe readMay nonemptys - unparseAvsIds :: AvsQueryStatus -> Text - unparseAvsIds (AvsQueryStatus ids) = Text.intercalate ", " $ tshow <$> Set.toAscList ids + unparseAvsIds :: AvsPersonId -> Text + unparseAvsIds = tshow . avsPersonId validateAvsQueryStatus :: FormValidator AvsQueryStatus Handler () validateAvsQueryStatus = do AvsQueryStatus ids <- State.get guardValidation (MsgAvsQueryStatusInvalid $ tshow ids) $ not (null ids) -makeAvsContactForm :: Maybe AvsQueryContact -> Form AvsQueryContact +makeAvsContactForm :: Maybe AvsPersonId -> Form AvsQueryContact makeAvsContactForm tmpl = identifyForm FIDAvsQueryContact . validateForm validateAvsQueryContact $ \html -> flip (renderAForm FormStandard) html $ parseAvsIds <$> areq textField (fslI MsgAvsPersonId) (unparseAvsIds <$> tmpl) -- consider using cfAnySeparatedSet here @@ -121,8 +121,9 @@ makeAvsContactForm tmpl = identifyForm FIDAvsQueryContact . validateForm validat where nonemptys = filter (not . Text.null) $ Text.strip <$> Text.split (==',') txt ids = mapMaybe (fmap AvsObjPersonId . readMay) nonemptys - unparseAvsIds :: AvsQueryContact -> Text - unparseAvsIds (AvsQueryContact ids) = Text.intercalate ", " $ tshow <$> Set.toAscList ids + unparseAvsIds :: AvsPersonId -> Text + unparseAvsIds = tshow . avsPersonId + --unparseAvsIds (AvsQueryContact ids) = Text.intercalate ", " $ tshow <$> Set.toAscList ids validateAvsQueryContact :: FormValidator AvsQueryContact Handler () validateAvsQueryContact = do @@ -161,19 +162,26 @@ postAdminAvsR = do ((presult, pwidget), penctype) <- runFormPost $ makeAvsPersonForm Nothing - let procFormPerson fr = do + let procFormPerson :: AvsQueryPerson -> Handler (Maybe (Maybe Widget, Maybe AvsPersonId)) + procFormPerson fr = do addMessage Info $ text2Html $ "Query: " <> tshow (toJSON fr) - tryShow $ do - AvsResponsePerson pns <- avsQuery fr - return [whamlet| -
+ ^{c} + $forall c <- otherCmp +
+ #{c} + |] + return $ toMaybe (notNull topCmp) resWgt + where + procCmp _ [] = (0, [],[]) + procCmp maxPri ((E.Value cmpSh, E.Value cmpName, E.Value cmpSpr, E.Value cmpPrio) : cs) = + let cmpWgt = companyWidget (cmpSh, cmpName, cmpSpr) + isTop = cmpPrio >= maxPri + (accPri,accTop,accRem) = procCmp maxPri cs + in (max cmpPrio accPri, bool accTop (cmpWgt : accTop) isTop, bool (cmpName : accRem) accRem isTop) -- lazy evaluation after repmin example -- TODO: use this function in company view Handler.Firm #157 -- | add all company supervisors for a given users diff --git a/src/Handler/Utils/Table/Cells.hs b/src/Handler/Utils/Table/Cells.hs index 48c2e4444..18b2186fb 100644 --- a/src/Handler/Utils/Table/Cells.hs +++ b/src/Handler/Utils/Table/Cells.hs @@ -356,10 +356,11 @@ courseCell Course{..} = anchorCell link name `mappend` desc ^{modal "Beschreibung" (Right $ toWidget descr)} |] +-- also see Handler.Utils.Widgets.companyWidget companyCell :: IsDBTable m a => CompanyShorthand -> CompanyName -> Bool -> DBCell m a -companyCell csh cname isSupervisor = anchorCell link name +companyCell csh cname isSupervisor = anchorCell curl name where - link = FirmUsersR csh + curl = FirmUsersR csh corg = ciOriginal cname name | isSupervisor = text2markup (corg <> " ") <> icon IconSupervisor diff --git a/src/Handler/Utils/Users.hs b/src/Handler/Utils/Users.hs index 686dc8692..293dc4f7b 100644 --- a/src/Handler/Utils/Users.hs +++ b/src/Handler/Utils/Users.hs @@ -18,6 +18,7 @@ module Handler.Utils.Users , getUserPrimaryCompany, getUserPrimaryCompanyAddress , getUserEmail , getEmailAddress, getJustEmailAddress + , getUserEmailAutomatic , getEmailAddressFor, getJustEmailAddressFor , getPostalAddress, getPostalAddress' , getPostalPreferenceAndAddress, getPostalPreferenceAndAddress' @@ -102,13 +103,13 @@ getPostalPreferenceAndAddress usr = do -- | result (True, Nothing, Nothing) indicates that neither userEmail nor userPostAddress is known -- primed variant returns storedMarkup without prefixed userDisplayName -getPostalPreferenceAndAddress' :: Entity User -> DB (Bool, Maybe StoredMarkup, Maybe UserEmail) +getPostalPreferenceAndAddress' :: Entity User -> DB (Bool, (Maybe StoredMarkup, Bool), (Maybe UserEmail, Bool)) getPostalPreferenceAndAddress' usr = do - pa <- getPostalAddress' usr - em <- getUserEmail usr + pa <- getPostalAddress' usr + em <- getUserEmailAutomatic usr let usrPrefPost = usr ^. _entityVal . _userPrefersPostal - finalPref = (usrPrefPost && isJust pa) || isNothing em - -- finalPref = isJust pa && (usrPrefPost || isNothing em) + finalPref = (usrPrefPost && isJust (fst pa)) || isNothing (fst em) + -- finalPref = isJust (fst pa) && (usrPrefPost || isNothing (fst em)) return (finalPref, pa, em) getEmailAddressFor :: UserId -> DB (Maybe Address) @@ -133,6 +134,21 @@ getUserEmail Entity{entityKey=uid, entityVal=User{userDisplayEmail, userEmail}} compEmailMb <- getUserPrimaryCompanyAddress uid companyEmail return $ pickValidEmail' $ mcons compEmailMb [userEmail] +-- like `getUserEmail`, but also checks whether the Email will be update automatically +getUserEmailAutomatic :: Entity User -> DB (Maybe UserEmail, Bool) +getUserEmailAutomatic Entity{entityKey=uid, entityVal=User{userDisplayEmail, userEmail}} + | validEmail' userDisplayEmail + = do + muavs <- getBy $ UniqueUserAvsUser uid + let auto = userDisplayEmail == muavs ^. _Just . _userAvsLastFirmInfo . _Just . _avsFirmPrimaryEmail . _Just . from _CI -- Recall: _Just on Nothing yields mempty here + || userDisplayEmail == muavs ^. _Just . _userAvsLastPersonInfo . _Just . _avsInfoPersonEMail . _Just . from _CI + return (Just userDisplayEmail, auto) + | otherwise + = getUserPrimaryCompanyAddress uid companyEmail >>= \case + Just compEmail | validEmail' compEmail -> return (Just compEmail, True ) + Nothing | validEmail' userEmail -> return (Just userEmail, False) + _ -> return (Nothing , False) + -- address is prefixed with userDisplayName getPostalAddress :: Entity User -> DB (Maybe [Text]) getPostalAddress Entity{entityKey=uid, entityVal=User{..}} @@ -151,22 +167,25 @@ getPostalAddress Entity{entityKey=uid, entityVal=User{..}} where prefixMarkupName = return . Just . (userDisplayName :) . html2textlines --- primed variant returns storedMarkup without prefixed userDisplayName -getPostalAddress' :: Entity User -> DB (Maybe StoredMarkup) +-- primed variant returns storedMarkup without prefixed userDisplayName and whether updates are automatic +getPostalAddress' :: Entity User -> DB (Maybe StoredMarkup, Bool) getPostalAddress' Entity{entityKey=uid, entityVal=User{..}} - | res@(Just _) <- userPostAddress - = return res + | res@(Just upo) <- userPostAddress + = do + muavs <- getBy $ UniqueUserAvsUser uid + let auto = upo == muavs ^. _Just . _userAvsLastFirmInfo . _Just . _avsFirmPostAddress . _Just -- Recall: _Just on Nothing yields mempty here + return (res, auto) | otherwise = do getUserPrimaryCompanyAddress uid companyPostAddress >>= \case res@(Just _) - -> return res + -> return (res, True) Nothing | Just abt <- userCompanyDepartment - -> return $ Just $ plaintextToStoredMarkup $ textUnlines $ + -> return $ (,True) $ Just $ plaintextToStoredMarkup $ textUnlines $ if | "BVD" `isPrefixOf` abt -> [userDisplayName, abt, "Bodenverkehrsdienste"] | otherwise -> [userDisplayName, abt, "Hausbriefkasten" ] - | otherwise -> return Nothing + | otherwise -> return (Nothing, True) -- | Consider using Handler.Utils.Avs.updateReceivers instead -- Return Entity User and all Supervisors with rerouteNotifications as well as diff --git a/src/Handler/Utils/Widgets.hs b/src/Handler/Utils/Widgets.hs index 1e5f6bdc2..3f6b1fe89 100644 --- a/src/Handler/Utils/Widgets.hs +++ b/src/Handler/Utils/Widgets.hs @@ -14,6 +14,7 @@ import Handler.Utils.DateTime import qualified Data.Char as Char import qualified Data.HashMap.Strict as Aeson -- ON UPDATE replace with: import qualified Data.Aeson.KeyMap as Aeson +import Data.Scientific --------- -- Simple utilities for consistent display @@ -131,6 +132,16 @@ modalAccess wdgtNo wdgtYes writeAccess route = do then modal wdgtYes (Left $ SomeRoute route) else wdgtNo +-- also see Handler.Utils.Table.Cells.companyCell +companyWidget :: (CompanyShorthand, CompanyName, Bool) -> Widget +companyWidget (csh, cname, isSupervisor) = simpleLink (toWgt name) curl + where + curl = FirmUsersR csh + corg = ciOriginal cname + name + | isSupervisor = text2markup (corg <> " ") <> icon IconSupervisor + | otherwise = text2markup corg + ---------- -- HEAT -- ---------- @@ -253,7 +264,9 @@ jsonWidget x = jsonWidgetAux $ toJSON x jsonWidgetAux Null = [whamlet|Null|] jsonWidgetAux (Bool b) = toWidget $ boolSymbol b jsonWidgetAux (String s) = [whamlet|#{s}|] - jsonWidgetAux (Number n) = [whamlet|#{show n}|] + jsonWidgetAux (Number n) + | isInteger n = [whamlet|#{formatScientific Fixed (Just 0) n}|] + | otherwise = [whamlet|#{formatScientific Generic Nothing n}|] jsonWidgetAux (Array l) | 1 >= length l = foldMap jsonWidgetAux l -- empty arrays don't show | otherwise = diff --git a/src/Utils.hs b/src/Utils.hs index 21685f564..18edea373 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -710,6 +710,10 @@ bcons :: Bool -> a -> [a] -> [a] bcons False _ = id bcons True x = (x:) +bsnoc :: Bool -> a -> [a] -> [a] +bsnoc False _ xs = xs +bsnoc True x xs = xs ++ [x] + -- | Merge/Add any attribute-value pair to an existing list of such pairs. -- If the attribute exists, the new valu will be prepended, separated by a single empty space insertAttr :: Text -> Text -> [(Text,Text)] -> [(Text,Text)] diff --git a/src/Utils/Avs.hs b/src/Utils/Avs.hs index c54b80864..704459f51 100644 --- a/src/Utils/Avs.hs +++ b/src/Utils/Avs.hs @@ -102,7 +102,7 @@ mkAvsQuery _ _ _ = AvsQuery AvsQueryPerson{avsPersonQueryCardNo=Just (AvsCardNo "00006666"), avsPersonQueryVersionNo=Just "4"} -> AvsResponsePerson $ sumpfi1 <> sumpfi2 <> sumpfi3 AvsQueryPerson{avsPersonQueryCardNo=Just (AvsCardNo "00007777"), avsPersonQueryVersionNo=Just "4"} -> AvsResponsePerson $ sumpfi1 <> sumpfi2 <> sumpfi3 AvsQueryPerson{avsPersonQueryCardNo=Just (AvsCardNo "00008888"), avsPersonQueryVersionNo=Just "4"} -> AvsResponsePerson $ sumpfi1 <> sumpfi2 <> sumpfi3 - _ -> AvsResponsePerson mempty + _ -> AvsResponsePerson steffen fakeStatus :: AvsQueryStatus -> AvsResponseStatus fakeStatus (AvsQueryStatus (Set.toList -> (api:_))) = AvsResponseStatus $ Set.singleton $ AvsStatusPerson api $ Set.fromList diff --git a/src/Utils/DB.hs b/src/Utils/DB.hs index e624ef497..29b74757d 100644 --- a/src/Utils/DB.hs +++ b/src/Utils/DB.hs @@ -82,6 +82,9 @@ getEntity404 :: (PersistStoreRead backend, PersistRecordBackend record backend, => Key record -> ReaderT backend m (Entity record) getEntity404 k = Entity k <$> get404 k +notExists :: (PersistRecordBackend record backend, PersistQueryRead backend, MonadIO m) => [Filter record] -> ReaderT backend m Bool +notExists = fmap not . exists + existsBy :: (PersistRecordBackend record backend, PersistUniqueRead backend, MonadIO m) => Unique record -> ReaderT backend m Bool existsBy = fmap (is _Just) . getKeyBy @@ -108,6 +111,7 @@ existsKey404 :: (PersistRecordBackend record backend, PersistQueryRead backend, existsKey404 = bool notFound (return ()) <=< existsKey -- | given filter criteria like `selectList` this function returns Just if and only if there is precisely one result +-- getByPeseudoUnique getByFilter :: (PersistRecordBackend record backend, PersistQueryRead backend, MonadIO m) => [Filter record] -> ReaderT backend m (Maybe (Entity record)) getByFilter crit = @@ -368,7 +372,6 @@ updateRecord ent new (CheckUpdate up l) = -- | like mkUpdate' but only returns the update if the new value would be unique -- mkUpdateCheckUnique' :: PersistEntity record => record -> iraw -> Maybe iraw -> CheckUpdate record iraw -> DB (Maybe (Update record)) - mkUpdateCheckUnique' :: (MonadIO m, PersistQueryRead backend, PersistEntity record, PersistEntityBackend record ~ BaseBackend backend) => record -> a -> Maybe a -> CheckUpdate record a -> ReaderT backend m (Maybe (Update record)) diff --git a/src/Utils/Icon.hs b/src/Utils/Icon.hs index 0ec91a144..d5d2fc413 100644 --- a/src/Utils/Icon.hs +++ b/src/Utils/Icon.hs @@ -118,7 +118,7 @@ data Icon | IconCompany | IconEdit | IconUserEdit - | IconMagic -- indicates automatic updates + -- | IconMagic -- indicates automatic updates deriving (Eq, Ord, Enum, Bounded, Show, Read, Generic) deriving anyclass (Universe, Finite, NFData) @@ -215,7 +215,7 @@ iconText = \case IconCompany -> "building" IconEdit -> "edit" IconUserEdit -> "user-edit" - IconMagic -> "wand-magic" + -- IconMagic -> "wand-magic" nullaryPathPiece ''Icon $ camelToPathPiece' 1 deriveLift ''Icon @@ -298,10 +298,11 @@ isNew :: Bool -> Markup isNew True = icon IconNew isNew False = mempty --- ^ Maybe display an icon that denotes that something™ is automagically updated or derived -isAutomatic :: Bool -> Markup -isAutomatic True = icon IconMagic -isAutomatic False = mempty +-- DEPRECATED by Handler.Utils.updateAutomatic, which includes a helpful tooltip +-- Maybe display an icon that denotes that something™ is NOT automagically updated or derived, but had been edited +-- isAutomatic :: Bool -> Markup +-- isAutomatic True = mempty -- icon IconMagic +-- isAutomatic False = icon IconLocked -- IconEdit boolSymbol :: Bool -> Markup boolSymbol True = icon IconOK diff --git a/templates/avs.hamlet b/templates/avs.hamlet index b9dadd9b8..f3c84153f 100644 --- a/templates/avs.hamlet +++ b/templates/avs.hamlet @@ -44,6 +44,10 @@ $# SPDX-License-Identifier: AGPL-3.0-or-later
Unverarbeitete Antwort: # ^{answer} + $maybe apid <- mapid +
+ Einzelne erhaltene AVS PersonId #{show apid} wurde auch gleich
+ in die Status und Contact Abfragen eingesetzt.
diff --git a/templates/i18n/profile-remarks/de-de-formal.hamlet b/templates/i18n/profile-remarks/de-de-formal.hamlet
index 362931765..f851d9b81 100644
--- a/templates/i18n/profile-remarks/de-de-formal.hamlet
+++ b/templates/i18n/profile-remarks/de-de-formal.hamlet
@@ -7,21 +7,17 @@ $# SPDX-License-Identifier: AGPL-3.0-or-later
Hinweise
-