feat(workflows): node messages
This commit is contained in:
parent
5b897c7a42
commit
6a7a892c74
@ -2,6 +2,7 @@ module Handler.Utils.Workflow.Workflow
|
|||||||
( ensureScope
|
( ensureScope
|
||||||
, followEdge
|
, followEdge
|
||||||
, followAutomaticEdges, WorkflowAutomaticEdgeException(..)
|
, followAutomaticEdges, WorkflowAutomaticEdgeException(..)
|
||||||
|
, checkWorkflowRestriction
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
@ -68,16 +69,22 @@ followAutomaticEdges WorkflowGraph{..} = go []
|
|||||||
(nodeLbl, WGN{..}) <- Map.toList wgNodes
|
(nodeLbl, WGN{..}) <- Map.toList wgNodes
|
||||||
(edgeLbl, WorkflowGraphEdgeAutomatic{..}) <- Map.toList wgnEdges
|
(edgeLbl, WorkflowGraphEdgeAutomatic{..}) <- Map.toList wgnEdges
|
||||||
guard $ wgeSource == cState
|
guard $ wgeSource == cState
|
||||||
whenIsJust wgeRestriction $ guard . checkRestriction
|
whenIsJust wgeRestriction $ guard . checkWorkflowRestriction history
|
||||||
return (edgeLbl, nodeLbl)
|
return (edgeLbl, nodeLbl)
|
||||||
checkRestriction :: PredDNF WorkflowGraphEdgeAutomaticRestriction -> Bool
|
|
||||||
checkRestriction dnf = maybe False (ofoldr1 (||)) . fromNullable $ map evalConj dnf'
|
|
||||||
where
|
|
||||||
evalConj = maybe True (ofoldr1 (&&)) . fromNullable . map evalPred
|
|
||||||
evalPred PLVariable{ plVar = WorkflowGraphEdgeAutomaticRestrictionPayloadFilled{..} } = wgearPayloadFilled `Set.member` filledPayloads
|
|
||||||
evalPred PLNegated{ plVar = WorkflowGraphEdgeAutomaticRestrictionPayloadFilled{..} } = wgearPayloadFilled `Set.notMember` filledPayloads
|
|
||||||
evalPred PLVariable{ plVar = WorkflowGraphEdgeAutomaticRestrictionPreviousNode{..} } = wgearPreviousNode == cState
|
|
||||||
evalPred PLNegated{ plVar = WorkflowGraphEdgeAutomaticRestrictionPreviousNode{..} } = wgearPreviousNode /= cState
|
|
||||||
dnf' = map (Set.toList . toNullable) . Set.toList $ dnfTerms dnf
|
|
||||||
filledPayloads = Map.keysSet . Map.filter (not . Set.null) $ workflowStateCurrentPayloads history
|
filledPayloads = Map.keysSet . Map.filter (not . Set.null) $ workflowStateCurrentPayloads history
|
||||||
edgeDecisionInput = (cState, filledPayloads)
|
edgeDecisionInput = (cState, filledPayloads)
|
||||||
|
|
||||||
|
checkWorkflowRestriction :: WorkflowState FileReference UserId
|
||||||
|
-> PredDNF WorkflowGraphRestriction
|
||||||
|
-> Bool
|
||||||
|
checkWorkflowRestriction history dnf = maybe False (ofoldr1 (||)) . fromNullable $ map evalConj dnf'
|
||||||
|
where
|
||||||
|
evalConj = maybe True (ofoldr1 (&&)) . fromNullable . map evalPred
|
||||||
|
evalPred PLVariable{ plVar = WorkflowGraphRestrictionPayloadFilled{..} } = wgrPayloadFilled `Set.member` filledPayloads
|
||||||
|
evalPred PLNegated{ plVar = WorkflowGraphRestrictionPayloadFilled{..} } = wgrPayloadFilled `Set.notMember` filledPayloads
|
||||||
|
evalPred PLVariable{ plVar = WorkflowGraphRestrictionPreviousNode{..} } = wgrPreviousNode == cState
|
||||||
|
evalPred PLNegated{ plVar = WorkflowGraphRestrictionPreviousNode{..} } = wgrPreviousNode /= cState
|
||||||
|
dnf' = map (Set.toList . toNullable) . Set.toList $ dnfTerms dnf
|
||||||
|
|
||||||
|
filledPayloads = Map.keysSet . Map.filter (not . Set.null) $ workflowStateCurrentPayloads history
|
||||||
|
cState = wpTo $ last history
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import Handler.Utils.Workflow.Workflow
|
|||||||
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
import qualified Data.Sequence as Seq
|
||||||
|
|
||||||
import qualified Control.Monad.State.Class as State
|
import qualified Control.Monad.State.Class as State
|
||||||
import Control.Monad.Trans.RWS.Strict (RWST, execRWST)
|
import Control.Monad.Trans.RWS.Strict (RWST, execRWST)
|
||||||
@ -51,6 +52,7 @@ data WorkflowHistoryItem = WorkflowHistoryItem
|
|||||||
|
|
||||||
data WorkflowCurrentState = WorkflowCurrentState
|
data WorkflowCurrentState = WorkflowCurrentState
|
||||||
{ wcsState :: Maybe Text
|
{ wcsState :: Maybe Text
|
||||||
|
, wcsMessages :: Set Message
|
||||||
, wcsPayload :: [(Text, ([WorkflowFieldPayloadW Void (Maybe (Entity User))], Maybe (Route UniWorX)))]
|
, wcsPayload :: [(Text, ([WorkflowFieldPayloadW Void (Maybe (Entity User))], Maybe (Route UniWorX)))]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,11 +75,12 @@ workflowR wwId = do
|
|||||||
rScope <- maybeT notFound . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
|
rScope <- maybeT notFound . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
|
||||||
mEdgeForm <- workflowEdgeForm (Right wwId) Nothing
|
mEdgeForm <- workflowEdgeForm (Right wwId) Nothing
|
||||||
let canonRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
|
let canonRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
|
||||||
|
wGraph = _DBWorkflowGraph # workflowWorkflowGraph
|
||||||
|
|
||||||
mEdge <- for mEdgeForm $ \edgeForm -> do
|
mEdge <- for mEdgeForm $ \edgeForm -> do
|
||||||
((edgeRes, edgeView), edgeEnc) <- liftHandler . runFormPost $ renderAForm FormStandard edgeForm
|
((edgeRes, edgeView), edgeEnc) <- liftHandler . runFormPost $ renderAForm FormStandard edgeForm
|
||||||
edgeAct <- formResultMaybe edgeRes $ \edgeRes' -> do
|
edgeAct <- formResultMaybe edgeRes $ \edgeRes' -> do
|
||||||
nState <- followEdge (_DBWorkflowGraph # workflowWorkflowGraph) edgeRes' . Just $ _DBWorkflowState # workflowWorkflowState
|
nState <- followEdge wGraph edgeRes' . Just $ _DBWorkflowState # workflowWorkflowState
|
||||||
|
|
||||||
update wwId [ WorkflowWorkflowState =. view _DBWorkflowState nState ]
|
update wwId [ WorkflowWorkflowState =. view _DBWorkflowState nState ]
|
||||||
|
|
||||||
@ -95,10 +98,10 @@ workflowR wwId = do
|
|||||||
)
|
)
|
||||||
=> WorkflowStateIndex
|
=> WorkflowStateIndex
|
||||||
-> Maybe WorkflowGraphNodeLabel
|
-> Maybe WorkflowGraphNodeLabel
|
||||||
-> Map WorkflowPayloadLabel (Set (WorkflowFieldPayloadW FileReference UserId))
|
-> [WorkflowAction FileReference UserId]
|
||||||
-> WorkflowAction FileReference UserId
|
-> WorkflowAction FileReference UserId
|
||||||
-> RWST () (Maybe (Last WorkflowCurrentState), [WorkflowHistoryItem]) (Map WorkflowPayloadLabel (Set (WorkflowFieldPayloadW FileReference UserId))) (SqlPersistT m) ()
|
-> RWST () (Maybe (Last WorkflowCurrentState), [WorkflowHistoryItem]) (Map WorkflowPayloadLabel (Set (WorkflowFieldPayloadW FileReference UserId))) (SqlPersistT m) ()
|
||||||
go stIx wpFrom currentPayload act@WorkflowAction{..} = maybeT (return ()) $ do
|
go stIx wpFrom history@(workflowStateCurrentPayloads -> currentPayload) act@WorkflowAction{..} = maybeT (return ()) $ do
|
||||||
mAuthId <- maybeAuthId
|
mAuthId <- maybeAuthId
|
||||||
guardM . lift . lift . hoist liftHandler $ mayViewWorkflowAction mAuthId wwId act
|
guardM . lift . lift . hoist liftHandler $ mayViewWorkflowAction mAuthId wwId act
|
||||||
|
|
||||||
@ -183,17 +186,28 @@ workflowR wwId = do
|
|||||||
whiPayloadChanges <- renderPayload payloadChanges
|
whiPayloadChanges <- renderPayload payloadChanges
|
||||||
wcsPayload <- renderPayload currentPayload
|
wcsPayload <- renderPayload currentPayload
|
||||||
|
|
||||||
|
wcsMessages <- do
|
||||||
|
let msgs = maybe Set.empty wgnMessages $ Map.lookup wpTo wgNodes
|
||||||
|
flip foldMapM msgs $ \WorkflowNodeMessage{..} -> lift . maybeT (return Set.empty) . fmap Set.singleton $ do
|
||||||
|
guardM $ anyM (otoList wnmViewers) hasWorkflowRole'
|
||||||
|
history' <- hoistMaybe . fromNullable $ Seq.fromList history
|
||||||
|
whenIsJust wnmRestriction $ guard . checkWorkflowRestriction history'
|
||||||
|
let messageStatus = wnmStatus
|
||||||
|
messageIcon = Nothing
|
||||||
|
messageContent <- selectLanguageI18n wnmContent
|
||||||
|
return Message{..}
|
||||||
|
|
||||||
tell ( Just $ Last WorkflowCurrentState{..}
|
tell ( Just $ Last WorkflowCurrentState{..}
|
||||||
, pure WorkflowHistoryItem{..}
|
, pure WorkflowHistoryItem{..}
|
||||||
)
|
)
|
||||||
WorkflowGraph{..} = _DBWorkflowGraph # workflowWorkflowGraph
|
WorkflowGraph{..} = wGraph
|
||||||
wState = otoList $ review _DBWorkflowState workflowWorkflowState
|
wState = otoList $ review _DBWorkflowState workflowWorkflowState
|
||||||
in fmap (over _2 (sortOn (Down . whiTime) . reverse) . view _2) . (\act -> execRWST act () Map.empty) $ sequence_
|
in fmap (over _2 (sortOn (Down . whiTime) . reverse) . view _2) . (\act -> execRWST act () Map.empty) $ sequence_
|
||||||
[ go stIx fromSt payload act
|
[ go stIx fromSt payload act
|
||||||
| fromSt <- Nothing : map (Just . wpTo) wState
|
| fromSt <- Nothing : map (Just . wpTo) wState
|
||||||
| act <- wState
|
| act <- wState
|
||||||
| stIx <- [minBound..]
|
| stIx <- [minBound..]
|
||||||
| payload <- map workflowStateCurrentPayloads . tailEx $ inits wState
|
| payload <- tailEx $ inits wState
|
||||||
]
|
]
|
||||||
return (mEdge, rScope, (workflowState, workflowHistory))
|
return (mEdge, rScope, (workflowState, workflowHistory))
|
||||||
|
|
||||||
|
|||||||
@ -68,7 +68,7 @@ predNFAesonOptions = defaultOptions
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
workflowGraphAesonOptions, workflowGraphEdgeAesonOptions, workflowGraphNodeAesonOptions, workflowActionAesonOptions, workflowPayloadViewAesonOptions, workflowNodeViewAesonOptions :: Options
|
workflowGraphAesonOptions, workflowGraphEdgeAesonOptions, workflowGraphNodeAesonOptions, workflowActionAesonOptions, workflowPayloadViewAesonOptions, workflowNodeViewAesonOptions, workflowNodeMessageAesonOptions :: Options
|
||||||
workflowGraphAesonOptions = defaultOptions
|
workflowGraphAesonOptions = defaultOptions
|
||||||
{ fieldLabelModifier = camelToPathPiece' 1
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
}
|
}
|
||||||
@ -89,3 +89,6 @@ workflowPayloadViewAesonOptions = defaultOptions
|
|||||||
workflowNodeViewAesonOptions = defaultOptions
|
workflowNodeViewAesonOptions = defaultOptions
|
||||||
{ fieldLabelModifier = camelToPathPiece' 1
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
}
|
}
|
||||||
|
workflowNodeMessageAesonOptions = defaultOptions
|
||||||
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
|
}
|
||||||
|
|||||||
@ -5,9 +5,10 @@ module Model.Types.Workflow
|
|||||||
, WorkflowGraphNodeLabel
|
, WorkflowGraphNodeLabel
|
||||||
, WorkflowGraphNode(..)
|
, WorkflowGraphNode(..)
|
||||||
, WorkflowNodeView(..)
|
, WorkflowNodeView(..)
|
||||||
|
, WorkflowNodeMessage(..)
|
||||||
, WorkflowGraphEdgeLabel
|
, WorkflowGraphEdgeLabel
|
||||||
, WorkflowGraphEdge(..)
|
, WorkflowGraphEdge(..)
|
||||||
, WorkflowGraphEdgeAutomaticRestriction(..)
|
, WorkflowGraphRestriction(..)
|
||||||
, WorkflowGraphEdgeFormOrder
|
, WorkflowGraphEdgeFormOrder
|
||||||
, WorkflowGraphEdgeForm(..)
|
, WorkflowGraphEdgeForm(..)
|
||||||
, WorkflowRole(..)
|
, WorkflowRole(..)
|
||||||
@ -78,6 +79,7 @@ newtype WorkflowGraphNodeLabel = WorkflowGraphNodeLabel { unWorkflowGraphNodeLab
|
|||||||
data WorkflowGraphNode fileid userid = WGN
|
data WorkflowGraphNode fileid userid = WGN
|
||||||
{ wgnFinal :: Bool
|
{ wgnFinal :: Bool
|
||||||
, wgnViewers :: Maybe (WorkflowNodeView userid)
|
, wgnViewers :: Maybe (WorkflowNodeView userid)
|
||||||
|
, wgnMessages :: Set (WorkflowNodeMessage userid)
|
||||||
, wgnEdges :: Map WorkflowGraphEdgeLabel (WorkflowGraphEdge fileid userid)
|
, wgnEdges :: Map WorkflowGraphEdgeLabel (WorkflowGraphEdge fileid userid)
|
||||||
, wgnPayloadView :: Map WorkflowPayloadLabel (WorkflowPayloadView userid)
|
, wgnPayloadView :: Map WorkflowPayloadLabel (WorkflowPayloadView userid)
|
||||||
}
|
}
|
||||||
@ -92,15 +94,22 @@ data WorkflowNodeView userid = WorkflowNodeView
|
|||||||
, wnvDisplayLabel :: I18nText
|
, wnvDisplayLabel :: I18nText
|
||||||
} deriving (Eq, Ord, Read, Show, Data, Generic, Typeable)
|
} deriving (Eq, Ord, Read, Show, Data, Generic, Typeable)
|
||||||
|
|
||||||
|
data WorkflowNodeMessage userid = WorkflowNodeMessage
|
||||||
|
{ wnmViewers :: NonNull (Set (WorkflowRole userid))
|
||||||
|
, wnmRestriction :: Maybe (PredDNF WorkflowGraphRestriction)
|
||||||
|
, wnmStatus :: MessageStatus
|
||||||
|
, wnmContent :: I18nHtml
|
||||||
|
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
----- WORKFLOW GRAPH: EDGES -----
|
----- WORKFLOW GRAPH: EDGES -----
|
||||||
|
|
||||||
newtype WorkflowGraphEdgeLabel = WorkflowGraphEdgeLabel { unWorkflowGraphEdgeLabel :: CI Text }
|
newtype WorkflowGraphEdgeLabel = WorkflowGraphEdgeLabel { unWorkflowGraphEdgeLabel :: CI Text }
|
||||||
deriving stock (Eq, Ord, Read, Show, Data, Generic, Typeable)
|
deriving stock (Eq, Ord, Read, Show, Data, Generic, Typeable)
|
||||||
deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary)
|
deriving newtype (IsString, ToJSON, ToJSONKey, FromJSON, FromJSONKey, PathPiece, PersistField, PersistFieldSql, Binary)
|
||||||
|
|
||||||
data WorkflowGraphEdgeAutomaticRestriction
|
data WorkflowGraphRestriction
|
||||||
= WorkflowGraphEdgeAutomaticRestrictionPayloadFilled { wgearPayloadFilled :: WorkflowPayloadLabel }
|
= WorkflowGraphRestrictionPayloadFilled { wgrPayloadFilled :: WorkflowPayloadLabel }
|
||||||
| WorkflowGraphEdgeAutomaticRestrictionPreviousNode { wgearPreviousNode :: WorkflowGraphNodeLabel }
|
| WorkflowGraphRestrictionPreviousNode { wgrPreviousNode :: WorkflowGraphNodeLabel }
|
||||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
|
||||||
data WorkflowGraphEdge fileid userid
|
data WorkflowGraphEdge fileid userid
|
||||||
@ -113,7 +122,7 @@ data WorkflowGraphEdge fileid userid
|
|||||||
}
|
}
|
||||||
| WorkflowGraphEdgeAutomatic
|
| WorkflowGraphEdgeAutomatic
|
||||||
{ wgeSource :: WorkflowGraphNodeLabel
|
{ wgeSource :: WorkflowGraphNodeLabel
|
||||||
, wgeRestriction :: Maybe (PredDNF WorkflowGraphEdgeAutomaticRestriction)
|
, wgeRestriction :: Maybe (PredDNF WorkflowGraphRestriction)
|
||||||
}
|
}
|
||||||
| WorkflowGraphEdgeInitial
|
| WorkflowGraphEdgeInitial
|
||||||
{ wgeActors :: Set (WorkflowRole userid)
|
{ wgeActors :: Set (WorkflowRole userid)
|
||||||
@ -601,15 +610,18 @@ deriveJSON defaultOptions
|
|||||||
} ''WorkflowRole
|
} ''WorkflowRole
|
||||||
|
|
||||||
deriveToJSON workflowNodeViewAesonOptions ''WorkflowNodeView
|
deriveToJSON workflowNodeViewAesonOptions ''WorkflowNodeView
|
||||||
|
deriveToJSON workflowNodeMessageAesonOptions ''WorkflowNodeMessage
|
||||||
deriveToJSON workflowPayloadViewAesonOptions ''WorkflowPayloadView
|
deriveToJSON workflowPayloadViewAesonOptions ''WorkflowPayloadView
|
||||||
pathPieceJSON ''WorkflowFieldPayload'
|
pathPieceJSON ''WorkflowFieldPayload'
|
||||||
pathPieceJSON ''WorkflowPayloadField'
|
pathPieceJSON ''WorkflowPayloadField'
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ fieldLabelModifier = camelToPathPiece' 1
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
, constructorTagModifier = camelToPathPiece' 5
|
, constructorTagModifier = camelToPathPiece' 3
|
||||||
} ''WorkflowGraphEdgeAutomaticRestriction
|
} ''WorkflowGraphRestriction
|
||||||
|
|
||||||
|
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowNodeMessage userid) where
|
||||||
|
parseJSON = genericParseJSON workflowNodeMessageAesonOptions
|
||||||
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowNodeView userid) where
|
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowNodeView userid) where
|
||||||
parseJSON = genericParseJSON workflowNodeViewAesonOptions
|
parseJSON = genericParseJSON workflowNodeViewAesonOptions
|
||||||
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowPayloadView userid) where
|
instance (FromJSON userid, Ord userid) => FromJSON (WorkflowPayloadView userid) where
|
||||||
|
|||||||
@ -30,12 +30,10 @@ import Text.HTML.SanitizeXSS (sanitizeBalance)
|
|||||||
|
|
||||||
|
|
||||||
data MessageStatus = Error | Warning | Info | Success
|
data MessageStatus = Error | Warning | Info | Success
|
||||||
deriving (Eq, Ord, Enum, Bounded, Show, Read, Lift)
|
deriving (Eq, Ord, Enum, Bounded, Show, Read, Lift, Generic, Typeable)
|
||||||
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
|
instance Default MessageStatus where
|
||||||
instance Universe MessageStatus
|
|
||||||
instance Finite MessageStatus
|
|
||||||
instance Default MessageStatus where
|
|
||||||
def = Info
|
def = Info
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
@ -113,7 +111,7 @@ instance FromJSON Message where
|
|||||||
parseJSON = withObject "Message" $ \o -> do
|
parseJSON = withObject "Message" $ \o -> do
|
||||||
messageStatus <- o .: "status"
|
messageStatus <- o .: "status"
|
||||||
messageContent <- preEscapedText . sanitizeBalance <$> o .: "content"
|
messageContent <- preEscapedText . sanitizeBalance <$> o .: "content"
|
||||||
messageIcon <- o .: "icon"
|
messageIcon <- o .:? "icon"
|
||||||
return Message{..}
|
return Message{..}
|
||||||
|
|
||||||
statusToUrgencyClass :: MessageStatus -> Text
|
statusToUrgencyClass :: MessageStatus -> Text
|
||||||
|
|||||||
@ -14,6 +14,10 @@ $maybe WorkflowCurrentState{..} <- workflowState
|
|||||||
$nothing
|
$nothing
|
||||||
<span .workflow-state--state-special>
|
<span .workflow-state--state-special>
|
||||||
_{MsgWorkflowWorkflowWorkflowStateStateHidden}
|
_{MsgWorkflowWorkflowWorkflowStateStateHidden}
|
||||||
|
|
||||||
|
$forall msg <- wcsMessages
|
||||||
|
^{notification NotificationBroad msg}
|
||||||
|
|
||||||
$if not (onull wcsPayload)
|
$if not (onull wcsPayload)
|
||||||
<div .workflow-payload>
|
<div .workflow-payload>
|
||||||
<div .workflow-payload--label>
|
<div .workflow-payload--label>
|
||||||
|
|||||||
17
testdata/theses.yaml
vendored
17
testdata/theses.yaml
vendored
@ -79,6 +79,7 @@ nodes:
|
|||||||
- *betreuer
|
- *betreuer
|
||||||
- {"tag": "initiator"}
|
- {"tag": "initiator"}
|
||||||
display-label: "Notizen"
|
display-label: "Notizen"
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"antrag als pruefungsamt":
|
"antrag als pruefungsamt":
|
||||||
@ -352,6 +353,7 @@ nodes:
|
|||||||
- *hochschullehrer
|
- *hochschullehrer
|
||||||
- *betreuer
|
- *betreuer
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"antrag bestaetigen als hochschullehrer":
|
"antrag bestaetigen als hochschullehrer":
|
||||||
@ -381,6 +383,7 @@ nodes:
|
|||||||
- *hochschullehrer
|
- *hochschullehrer
|
||||||
- *betreuer
|
- *betreuer
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"antrag bestaetigen als student":
|
"antrag bestaetigen als student":
|
||||||
@ -409,6 +412,13 @@ nodes:
|
|||||||
- *hochschullehrer
|
- *hochschullehrer
|
||||||
- *betreuer
|
- *betreuer
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages:
|
||||||
|
- viewers:
|
||||||
|
- *hochschullehrer
|
||||||
|
- *betreuer
|
||||||
|
restriction: null
|
||||||
|
status: info
|
||||||
|
content: "Es muss zunächst „Anmeldetag“ eingetragen werden, damit der Antrag weiter von der Prüfungsverwaltung bearbeitet werden kann."
|
||||||
final: false
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"antrag bestaetigen als student":
|
"antrag bestaetigen als student":
|
||||||
@ -437,6 +447,7 @@ nodes:
|
|||||||
- *hochschullehrer
|
- *hochschullehrer
|
||||||
- *betreuer
|
- *betreuer
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"anmeldetag ist eingetragen":
|
"anmeldetag ist eingetragen":
|
||||||
@ -457,6 +468,7 @@ nodes:
|
|||||||
- *betreuer
|
- *betreuer
|
||||||
- *student
|
- *student
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges:
|
edges:
|
||||||
"anmelden, bestaetigt student&hochschullehrer, anmeldetag":
|
"anmelden, bestaetigt student&hochschullehrer, anmeldetag":
|
||||||
@ -516,6 +528,7 @@ nodes:
|
|||||||
- *betreuer
|
- *betreuer
|
||||||
- *student
|
- *student
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges: {}
|
edges: {}
|
||||||
"abgegeben":
|
"abgegeben":
|
||||||
@ -527,6 +540,7 @@ nodes:
|
|||||||
- *betreuer
|
- *betreuer
|
||||||
- *student
|
- *student
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges: {}
|
edges: {}
|
||||||
"benotet":
|
"benotet":
|
||||||
@ -538,6 +552,7 @@ nodes:
|
|||||||
- *betreuer
|
- *betreuer
|
||||||
- *student
|
- *student
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges: {}
|
edges: {}
|
||||||
"abgebrochen":
|
"abgebrochen":
|
||||||
@ -549,6 +564,7 @@ nodes:
|
|||||||
- *betreuer
|
- *betreuer
|
||||||
- *student
|
- *student
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: false
|
final: false
|
||||||
edges: {}
|
edges: {}
|
||||||
"fertig":
|
"fertig":
|
||||||
@ -557,5 +573,6 @@ nodes:
|
|||||||
viewers:
|
viewers:
|
||||||
- *pruefungsamt
|
- *pruefungsamt
|
||||||
payload-view: *payload-view
|
payload-view: *payload-view
|
||||||
|
messages: []
|
||||||
final: true
|
final: true
|
||||||
edges: {}
|
edges: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user