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:
iven
2026-04-11 09:54:02 +08:00
parent 0cbd08eb78
commit 91ecaa3ed7
51 changed files with 4826 additions and 12 deletions

View 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()
}
}