Files
erp/plans/stateless-swimming-perlis.md
iven 97d3c9026b fix: resolve remaining clippy warnings and improve workflow frontend
- Collapse nested if-let in user_service.rs search filter
- Suppress dead_code warning on ApiDoc struct
- Refactor server routing: nest all routes under /api/v1 prefix
- Simplify health check route path
- Improve workflow ProcessDesigner with edit mode and loading states
- Update workflow pages with enhanced UX
- Add Phase 2 implementation plan document
2026-04-11 12:59:43 +08:00

370 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 4: 工作流引擎模块 — 实施计划
## Context
Phase 1基础设施、Phase 2身份与权限和 Phase 3系统配置已完成。Phase 4 需要实现工作流引擎模块erp-workflow提供流程定义、流程实例管理、任务审批、Token 驱动的执行引擎和可视化流程设计器。当前 `erp-workflow` 仅为 placeholder。
用户选择"完整实施"方案,包括 BPMN 子集解析器、Token 驱动执行引擎、完整 CRUD 端点和 React Flow 可视化设计器。
---
## Task 1: erp-workflow 骨架 + WorkflowState + Error
**目标:** 创建可编译的最小 crate注册到 erp-server。
**创建/修改文件:**
- 修改: `crates/erp-workflow/Cargo.toml` — 添加完整依赖
- 修改: `crates/erp-workflow/src/lib.rs` — 模块声明 + re-export
- 创建: `crates/erp-workflow/src/workflow_state.rs``WorkflowState { db, event_bus }`
- 创建: `crates/erp-workflow/src/error.rs` — WorkflowError 枚举
- 创建: `crates/erp-workflow/src/module.rs` — WorkflowModule + ErpModule trait
- 创建: `crates/erp-workflow/src/dto.rs` — 占位
- 创建: `crates/erp-workflow/src/entity/mod.rs` — 占位
- 创建: `crates/erp-workflow/src/service/mod.rs` — 占位
- 创建: `crates/erp-workflow/src/handler/mod.rs` — 占位
- 创建: `crates/erp-workflow/src/engine/mod.rs` — 占位
- 修改: `crates/erp-server/Cargo.toml` — 确认 erp-workflow 依赖
- 修改: `crates/erp-server/src/state.rs` — 添加 `FromRef<AppState>` for WorkflowState
- 修改: `crates/erp-server/src/main.rs` — 注册 WorkflowModule
**依赖:**
```toml
erp-core.workspace = true
tokio = { workspace = true, features = ["full"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
uuid = { workspace = true, features = ["v7", "serde"] }
chrono = { workspace = true, features = ["serde"] }
axum = { workspace = true }
sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls", "with-uuid", "with-chrono", "with-json"] }
tracing = { workspace = true }
thiserror = { workspace = true }
utoipa = { workspace = true, features = ["uuid", "chrono"] }
async-trait = { workspace = true }
```
**验证:** `cargo check` 通过
---
## Task 2: 数据库迁移5 张表)
**创建文件(`crates/erp-server/migration/src/`**
- `m20260412_000018_create_process_definitions.rs`
- `m20260412_000019_create_process_instances.rs`
- `m20260412_000020_create_tokens.rs`
- `m20260412_000021_create_tasks.rs`
- `m20260412_000022_create_process_variables.rs`
- 修改: `lib.rs` — 注册新迁移
### process_definitions
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | 租户 ID |
| name | string NOT NULL | 流程名称 |
| key | string NOT NULL | 流程唯一编码 |
| version | int NOT NULL DEFAULT 1 | 版本号 |
| category | string NULL | 分类(如 leave, expense |
| description | text NULL | 描述 |
| nodes | jsonb NOT NULL DEFAULT '[]' | 节点定义BPMN 子集) |
| edges | jsonb NOT NULL DEFAULT '[]' | 连线定义 |
| status | string NOT NULL DEFAULT 'draft' | draft/published/deprecated |
| + 标准审计字段 | | |
| 唯一索引: | `(tenant_id, key, version) WHERE deleted_at IS NULL` | |
### process_instances
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| definition_id | uuid NOT NULL FK → process_definitions | |
| business_key | string NULL | 业务关联键(如请假单 ID |
| status | string NOT NULL DEFAULT 'running' | running/suspended/completed/terminated |
| started_by | uuid NOT NULL | 发起人 user_id |
| started_at | timestamptz NOT NULL DEFAULT NOW() | |
| completed_at | timestamptz NULL | |
| + 标准审计字段 | | |
| 索引: | `idx_instances_status (tenant_id, status)` | |
### tokens
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| instance_id | uuid NOT NULL FK → process_instances | |
| node_id | string NOT NULL | 当前所在节点 ID |
| status | string NOT NULL DEFAULT 'active' | active/consumed/terminated |
| created_at | timestamptz NOT NULL DEFAULT NOW() | |
| consumed_at | timestamptz NULL | |
| 索引: | `idx_tokens_instance (instance_id)` | |
### tasks
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| instance_id | uuid NOT NULL FK → process_instances | |
| token_id | uuid NOT NULL FK → tokens | |
| node_id | string NOT NULL | 对应的流程节点 |
| node_name | string NULL | 节点名称(冗余,便于查询) |
| assignee_id | uuid NULL | 指定处理人 |
| candidate_groups | jsonb NULL | 候选角色组 |
| status | string NOT NULL DEFAULT 'pending' | pending/approved/rejected/delegated |
| outcome | string NULL | 审批结果 |
| form_data | jsonb NULL | 表单数据 |
| due_date | timestamptz NULL | 到期时间 |
| completed_at | timestamptz NULL | |
| + 标准审计字段 | | |
| 索引: | `idx_tasks_assignee (tenant_id, assignee_id, status)` | |
| 索引: | `idx_tasks_instance (instance_id)` | |
### process_variables
| 列 | 类型 | 说明 |
|---|---|---|
| id | uuid PK | UUIDv7 |
| tenant_id | uuid NOT NULL | |
| instance_id | uuid NOT NULL FK → process_instances | |
| name | string NOT NULL | 变量名 |
| var_type | string NOT NULL DEFAULT 'string' | string/number/boolean/date/json |
| value_string | text NULL | |
| value_number | double precision NULL | |
| value_boolean | boolean NULL | |
| value_date | timestamptz NULL | |
| 唯一索引: | `(instance_id, name)` | |
**验证:** `cargo run -p erp-server` 启动后 `\dt` 可见新表
---
## Task 3: SeaORM Entity
**创建文件(`crates/erp-workflow/src/entity/`**
- `mod.rs` — 导出所有实体
- `process_definition.rs`
- `process_instance.rs`
- `token.rs`
- `task.rs`
- `process_variable.rs`
**模式:** 参考 `erp-config/src/entity/numbering_rule.rs`,包含 Relation 和 Related。
**验证:** `cargo check` 通过
---
## Task 4: DTO 定义
**修改文件:** `crates/erp-workflow/src/dto.rs`
**包含:**
- 流程定义:`ProcessDefinitionResp`, `CreateProcessDefinitionReq`, `UpdateProcessDefinitionReq`, `PublishDefinitionReq`
- 流程实例:`ProcessInstanceResp`, `StartInstanceReq`
- 任务:`TaskResp`, `CompleteTaskReq`(含 outcome + form_data
- 流程变量:`ProcessVariableResp`, `SetVariableReq`
- 流程图:`NodeDef`BPMN 节点), `EdgeDef`(连线), `FlowDiagram`(完整图)
**节点类型:** StartEvent, EndEvent, UserTask, ServiceTask, ExclusiveGateway, ParallelGateway
**连线条件:** `condition` 字段为可选表达式字符串(如 `amount > 1000`
**验证:** `cargo check` 通过
---
## Task 5: BPMN 解析器 + 表达式引擎
**创建文件(`crates/erp-workflow/src/engine/`**
- `model.rs` — 流程图内存模型FlowDiagram, FlowNode, FlowEdge, NodeType 枚举)
- `parser.rs` — 解析 JSON nodes/edges 为内存模型,验证流程图合法性
- `expression.rs` — 简单表达式求值器(支持比较运算和流程变量引用)
**关键逻辑:**
- `FlowDiagram::validate()` — 检查:恰好 1 个 StartEvent至少 1 个 EndEvent无悬空连线网关分支/汇合配对
- `ExpressionEvaluator::eval(expr, variables) -> bool` — 支持 `var > 1000`, `status == "approved"`, `amount <= budget` 格式
- 解析器将 `nodes``edges` jsonb 反序列化为 `Vec<FlowNode>``Vec<FlowEdge>`
**验证:** 单元测试覆盖:合法流程验证、缺少 StartEvent 报错、表达式求值
---
## Task 6: Token 驱动执行引擎
**创建文件:** `crates/erp-workflow/src/engine/executor.rs`
**核心逻辑:**
```
start(instance_id, definition) → 在 StartEvent 创建 token
advance(token_id, instance_id, definition, variables) → 消费当前 token在下一节点创建新 token
- 到达 EndEvent → 消费 token检查实例是否所有 token 都完成 → 完成实例
- 到达 UserTask → 创建 token + 创建 task 记录
- 到达 ServiceTask → 创建 token + 执行动作(占位,发布事件)
- 到达 ExclusiveGateway → 求值条件,选择一条分支
- 到达 ParallelGateway分支→ 为每条出边创建 token
- 到达 ParallelGateway汇合→ 消费当前 token等待所有入边 token 到达后创建新 token
```
**并发安全:** 使用 `pg_advisory_xact_lock` 保护 token 操作(参考 NumberingService 模式)
**验证:** 单元测试覆盖:直线流程、排他网关分支、并行网关分支与汇合
---
## Task 7: Service 层
**创建文件(`crates/erp-workflow/src/service/`**
- `definition_service.rs` — 流程定义 CRUD + 发布 + 版本管理
- `instance_service.rs` — 启动实例 + 查询 + 挂起/恢复/终止
- `task_service.rs` — 查询待办 + 完成任务 + 委派 + 查询已办
**关键逻辑:**
- `DefinitionService::publish` — draft → published验证流程图合法性
- `InstanceService::start` — 创建实例 + 初始化变量 + 调用 executor.start
- `TaskService::complete` — 更新 task 状态 + 调用 executor.advance + 处理下一节点
---
## Task 8: Handler 层
**创建文件(`crates/erp-workflow/src/handler/`**
- `definition_handler.rs` — 5 个端点
- `instance_handler.rs` — 4 个端点
- `task_handler.rs` — 4 个端点
**端点映射:**
```
GET/POST /workflow/definitions
GET /workflow/definitions/{id}
PUT /workflow/definitions/{id}
POST /workflow/definitions/{id}/publish
POST /workflow/instances
GET /workflow/instances
GET /workflow/instances/{id}
POST /workflow/instances/{id}/suspend
POST /workflow/instances/{id}/terminate
GET /workflow/tasks/pending — 我的待办
GET /workflow/tasks/completed — 我的已办
POST /workflow/tasks/{id}/complete — 完成任务
POST /workflow/tasks/{id}/delegate — 委派任务
```
**RBAC** 所有端点使用 `require_permission(&ctx, "workflow:xxx")`
---
## Task 9: 模块注册 + 种子数据
**修改文件:**
- `crates/erp-workflow/src/module.rs` — 填充真实路由12 个端点)
- `crates/erp-auth/src/service/seed.rs` — 添加工作流权限
- `crates/erp-server/src/main.rs` — 注册 WorkflowModule合并路由
**新增种子权限8 个):**
- workflow:create, workflow:list, workflow:read, workflow:update
- workflow:publish, workflow:start, workflow:approve, workflow:delegate
**验证:** `cargo check` + `cargo test` 通过
---
## Task 10: 前端 API 层 + 工作流页面
**创建文件(`apps/web/src/`**
- `api/workflowDefinitions.ts` — 流程定义 API
- `api/workflowInstances.ts` — 流程实例 API
- `api/workflowTasks.ts` — 任务 API
- `pages/Workflow.tsx` — Tab 壳页面(流程定义 | 我的待办 | 我的已办 | 流程监控)
**修改文件:**
- `App.tsx` — 添加 workflow 路由
- `MainLayout.tsx` — 侧边栏添加工作流菜单
---
## Task 11: React Flow 可视化设计器
**创建文件(`apps/web/src/pages/workflow/`**
- `ProcessDesigner.tsx` — React Flow 画布 + 节点面板 + 属性面板
- `nodes/` — 自定义节点组件StartEvent, EndEvent, UserTask, ServiceTask, Gateway
- `edges/` — 条件标签连线组件
- `hooks/useFlowValidation.ts` — 流程图前端验证
**依赖:** `@xyflow/react` npm 包
**功能:**
- 拖拽添加节点到画布
- 连线编辑(含条件表达式)
- 节点属性编辑面板
- 导出为 JSON nodes/edges 格式(匹配后端 DTO
- 流程图合法性前端验证
---
## Task 12: 流程图查看器 + 超时框架
**创建文件(`apps/web/src/pages/workflow/`**
- `ProcessViewer.tsx` — 只读 React Flow 渲染,高亮当前活跃节点
- `InstanceDetail.tsx` — 实例详情页(流程图 + 变量 + 任务历史)
**超时框架(后端占位):**
- `crates/erp-workflow/src/engine/timeout.rs` — 超时检查接口
- Task 表 `due_date` 字段已支持
**验证:** `pnpm dev` 启动,工作流设计器可拖拽节点、连线、保存
---
## 依赖图
```
Task 1骨架
|
Task 2迁移→ Task 3Entity→ Task 4DTO
|
+---------------+---------------+
| |
Task 5BPMN 解析器) Task 6执行引擎
| |
+---------------+---------------+
|
Task 7Service
|
Task 8Handler
|
Task 9集成+种子)
|
+---------------+---------------+
| |
Task 10前端页面 Task 11可视化设计器
| |
+---------------+---------------+
|
Task 12查看器+超时)
```
---
## 验证清单
- [ ] `cargo check` 全 workspace 通过
- [ ] `cargo test --workspace` 全部通过
- [ ] Docker 环境正常启动
- [ ] 所有迁移可正/反向执行
- [ ] 12 个工作流 API 端点可测试
- [ ] 前端工作流设计器可拖拽节点和连线
- [ ] 流程图保存和加载正常
- [ ] 所有代码已提交
## 关键参考文件
| 用途 | 文件路径 |
|------|----------|
| Service 模式 | `crates/erp-config/src/service/numbering_service.rs` |
| Handler 模式 | `crates/erp-config/src/handler/numbering_handler.rs` |
| State 桥接 | `crates/erp-server/src/state.rs` |
| 模块注册 | `crates/erp-config/src/module.rs` |
| 迁移模式 | `crates/erp-server/migration/src/m20260412_000016_create_settings.rs` |
| Advisory Lock | `crates/erp-config/src/service/numbering_service.rs` (generate_number) |
| 前端 Table CRUD | `apps/web/src/pages/Roles.tsx` |
| 前端树形展示 | `apps/web/src/pages/Organizations.tsx` |
| RBAC | `crates/erp-core/src/rbac.rs` |