feat(workflow): add workflow engine module (Phase 4)
Implement complete workflow engine with BPMN subset support: Backend (erp-workflow crate): - Token-driven execution engine with exclusive/parallel gateway support - BPMN parser with flow graph validation - Expression evaluator for conditional branching - Process definition CRUD with draft/publish lifecycle - Process instance management (start, suspend, terminate) - Task service (pending, complete, delegate) - PostgreSQL advisory locks for concurrent safety - 5 database tables: process_definitions, process_instances, tokens, tasks, process_variables - 13 API endpoints with RBAC protection - Timeout checker framework (placeholder) Frontend: - Workflow page with 4 tabs (definitions, pending, completed, monitor) - React Flow visual process designer (@xyflow/react) - Process viewer with active node highlighting - 3 API client modules for workflow endpoints - Sidebar menu integration
This commit is contained in:
122
crates/erp-workflow/src/engine/model.rs
Normal file
122
crates/erp-workflow/src/engine/model.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::dto::{EdgeDef, NodeDef, NodeType};
|
||||
|
||||
/// 内存中的流程图模型,用于执行引擎。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlowGraph {
|
||||
/// node_id → FlowNode
|
||||
pub nodes: HashMap<String, FlowNode>,
|
||||
/// edge_id → FlowEdge
|
||||
pub edges: HashMap<String, FlowEdge>,
|
||||
/// node_id → 从该节点出发的边列表
|
||||
pub outgoing: HashMap<String, Vec<String>>,
|
||||
/// node_id → 到达该节点的边列表
|
||||
pub incoming: HashMap<String, Vec<String>>,
|
||||
/// StartEvent 的 node_id
|
||||
pub start_node_id: Option<String>,
|
||||
/// 所有 EndEvent 的 node_id
|
||||
pub end_node_ids: Vec<String>,
|
||||
}
|
||||
|
||||
/// 内存中的节点模型。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlowNode {
|
||||
pub id: String,
|
||||
pub node_type: NodeType,
|
||||
pub name: String,
|
||||
pub assignee_id: Option<uuid::Uuid>,
|
||||
pub candidate_groups: Option<Vec<String>>,
|
||||
pub service_type: Option<String>,
|
||||
}
|
||||
|
||||
/// 内存中的边模型。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlowEdge {
|
||||
pub id: String,
|
||||
pub source: String,
|
||||
pub target: String,
|
||||
pub condition: Option<String>,
|
||||
pub label: Option<String>,
|
||||
}
|
||||
|
||||
impl FlowGraph {
|
||||
/// 从 DTO 节点和边列表构建 FlowGraph。
|
||||
pub fn build(nodes: &[NodeDef], edges: &[EdgeDef]) -> Self {
|
||||
let mut graph = FlowGraph {
|
||||
nodes: HashMap::new(),
|
||||
edges: HashMap::new(),
|
||||
outgoing: HashMap::new(),
|
||||
incoming: HashMap::new(),
|
||||
start_node_id: None,
|
||||
end_node_ids: Vec::new(),
|
||||
};
|
||||
|
||||
for n in nodes {
|
||||
let flow_node = FlowNode {
|
||||
id: n.id.clone(),
|
||||
node_type: n.node_type.clone(),
|
||||
name: n.name.clone(),
|
||||
assignee_id: n.assignee_id,
|
||||
candidate_groups: n.candidate_groups.clone(),
|
||||
service_type: n.service_type.clone(),
|
||||
};
|
||||
|
||||
if n.node_type == NodeType::StartEvent {
|
||||
graph.start_node_id = Some(n.id.clone());
|
||||
}
|
||||
if n.node_type == NodeType::EndEvent {
|
||||
graph.end_node_ids.push(n.id.clone());
|
||||
}
|
||||
|
||||
graph.nodes.insert(n.id.clone(), flow_node);
|
||||
graph.outgoing.insert(n.id.clone(), Vec::new());
|
||||
graph.incoming.insert(n.id.clone(), Vec::new());
|
||||
}
|
||||
|
||||
for e in edges {
|
||||
graph.edges.insert(e.id.clone(), FlowEdge {
|
||||
id: e.id.clone(),
|
||||
source: e.source.clone(),
|
||||
target: e.target.clone(),
|
||||
condition: e.condition.clone(),
|
||||
label: e.label.clone(),
|
||||
});
|
||||
|
||||
if let Some(out) = graph.outgoing.get_mut(&e.source) {
|
||||
out.push(e.id.clone());
|
||||
}
|
||||
if let Some(inc) = graph.incoming.get_mut(&e.target) {
|
||||
inc.push(e.id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
graph
|
||||
}
|
||||
|
||||
/// 获取节点的出边。
|
||||
pub fn get_outgoing_edges(&self, node_id: &str) -> Vec<&FlowEdge> {
|
||||
self.outgoing
|
||||
.get(node_id)
|
||||
.map(|edge_ids| {
|
||||
edge_ids
|
||||
.iter()
|
||||
.filter_map(|eid| self.edges.get(eid))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// 获取节点的入边。
|
||||
pub fn get_incoming_edges(&self, node_id: &str) -> Vec<&FlowEdge> {
|
||||
self.incoming
|
||||
.get(node_id)
|
||||
.map(|edge_ids| {
|
||||
edge_ids
|
||||
.iter()
|
||||
.filter_map(|eid| self.edges.get(eid))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user