1299 lines
49 KiB
Markdown
1299 lines
49 KiB
Markdown
# FMS 工作流与审批设计 V1
|
||
|
||
## 1. 文档定位
|
||
|
||
本文是《FMS 新系统核心表结构设计》的工作流补充设计,重点解决业务单据的送审、审批、退回、驳回、撤回、转交、委托和审批追溯问题。
|
||
|
||
本文不把审批当成某个页面的附属配置,也不把审批记录直接塞进业务表。工作流被设计成独立的系统能力:
|
||
|
||
```text
|
||
业务模块
|
||
└── 流程绑定
|
||
└── 已发布流程版本
|
||
├── 节点与连线
|
||
├── 审批人解析规则
|
||
└── 条件与表单策略
|
||
|
||
业务单据提交
|
||
└── 流程实例
|
||
└── 节点运行
|
||
└── 待办任务
|
||
└── 审批动作 / 审计 / 通知
|
||
```
|
||
|
||
本文遵循以下已有设计:
|
||
|
||
- 表名使用小写 `snake_case`;工作流是独立子系统,不属于系统内核,表统一使用 `wf_` 前缀,不使用 `s_`;
|
||
- 关系型表保存稳定、需要查询和关联的对象;变化频繁的配置使用 `nvarchar(max)` JSON;
|
||
- 不依赖数据库外键、触发器和 `CHECK` 约束表达业务规则;
|
||
- 权限由 `s_power`、`s_user_power`、`s_user_field_power`、`s_user_data_power` 和业务层权限引擎共同完成;
|
||
- 通用审计使用 `s_log_audit` 和 `s_log_audit_field`,工作流动作历史单独使用 `wf_action`;
|
||
- 业务表主键可以是 bigint 雪花 ID,工作流运行对象使用字符串保存业务主键,避免依赖业务表物理主键类型。
|
||
|
||
相关文档:
|
||
|
||
- [FMS 新系统核心表结构设计](./FMS新系统核心表结构设计.md)
|
||
- [FMS 删除策略重构设计](./FMS删除策略重构设计.md)
|
||
|
||
## 2. 设计目标与边界
|
||
|
||
### 2.1 设计目标
|
||
|
||
工作流系统需要满足:
|
||
|
||
1. 一个模块可以绑定多个流程,按照业务事件、组织和业务条件选择流程;
|
||
2. 流程定义可编辑、校验、模拟、发布和停用;
|
||
3. 已发布版本不可修改,运行中的流程固定使用提交时的版本;
|
||
4. 支持串行审批、并行审批、会签、串行会签、任一人通过、全部通过、N 人通过和按比例通过;
|
||
5. 支持指定用户、发起人直属领导、部门负责人、岗位、业务角色和业务字段用户;
|
||
6. 支持条件分支,条件由结构化规则表达,不允许前端直接拼接 SQL;
|
||
7. 审批权限同时受动作权限、当前待办任务和数据范围限制;
|
||
8. 所有审批动作可追溯,能够还原当时的审批版本、审批人规则和实际审批人;
|
||
9. 流程状态、业务状态、通知状态相互解耦,但关键状态变更必须在同一事务内完成;
|
||
10. 支持高并发下的幂等、乐观锁、任务抢占和重复点击保护。
|
||
|
||
### 2.2 设计边界
|
||
|
||
工作流负责:
|
||
|
||
- 流程路由;
|
||
- 待办任务;
|
||
- 抄送记录;
|
||
- 审批人解析;
|
||
- 审批动作;
|
||
- 流程状态和业务状态投影;
|
||
- 流程审计和通知事件。
|
||
|
||
工作流不负责:
|
||
|
||
- 替代业务表保存业务数据;
|
||
- 替代通用权限系统;
|
||
- 替代 HR 系统保存完整组织主数据;
|
||
- 通过任意 SQL 直接修改业务表;
|
||
- 用通用流程配置代替业务领域中的复杂计算和结算逻辑。
|
||
|
||
## 3. 现有简单审批方案的问题
|
||
|
||
旧方案以 `sh_set` 保存“模块、级别、用户、条件”,以 `sh_record` 保存某张单据的审核过程。当前配置页面是一个基于表格的 CRUD 页面,核心字段为 `b_level`、`b_user_id`、`b_condition` 和 `b_bz`。
|
||
|
||
这种模型存在以下问题:
|
||
|
||
| 现状 | 问题 |
|
||
| --- | --- |
|
||
| `b_level` 表示审批级别 | 只能表达固定的线性流程,不能表达分支、并行、回退和循环处理 |
|
||
| `b_user_id` 保存审批人 | 不能表达部门负责人、岗位、角色、发起人领导等动态审批人 |
|
||
| 一个级别对应一行用户 | 无法可靠表达会签、或签、N 人通过和候选人抢占 |
|
||
| `b_condition` 是字符串条件 | 规则不可校验、不可视化、难以版本化,并存在 SQL 拼接风险 |
|
||
| 多个用户使用逗号分隔 | 无法建立索引、统计待办、实现转交和委托 |
|
||
| 通过最大 `b_id` 获取最新记录 | 依赖 ID 排序,面对并发、重试和回退时语义不稳定 |
|
||
| 直接更新业务表 `b_state` | 流程实例、业务状态和审批历史互相耦合 |
|
||
| 配置直接保存到当前表 | 没有草稿、发布、版本、回滚和生效时间 |
|
||
| 前端直接调用通用保存接口 | 业务状态、权限、任务状态和审计无法由一个后端事务统一控制 |
|
||
| 模块使用旧的 `b_SH`、`b_cate_id` 等字段 | 与新版 `s_module` 模型不一致,不能作为新系统的基础配置页 |
|
||
|
||
因此不建议在 `sh_set` 上继续增加“审批人 2、审批人 3、条件 2、审核方式”等字段。应将 `sh_set` 和 `sh_record` 作为迁移来源,建立新的工作流配置和运行模型。
|
||
|
||
## 4. 核心概念
|
||
|
||
### 4.1 流程定义(Definition)
|
||
|
||
流程的稳定身份,例如:
|
||
|
||
```text
|
||
expense_approval
|
||
contract_approval
|
||
customer_activation
|
||
release_order_approval
|
||
```
|
||
|
||
流程定义本身不直接表示某个版本,也不直接表示某张业务单据。
|
||
|
||
### 4.2 流程版本(Version)
|
||
|
||
流程定义的一个可执行版本。版本状态建议为:
|
||
|
||
```text
|
||
draft 草稿,可编辑
|
||
published 已发布,可被新实例使用
|
||
retired 已停用,不再接受新实例
|
||
```
|
||
|
||
已发布版本不能原地编辑。修改流程时复制为新版本,重新校验并发布。已经运行的实例继续绑定原版本。
|
||
|
||
### 4.3 流程绑定(Binding)
|
||
|
||
把模块、业务事件、组织范围和流程版本关联起来。例如:
|
||
|
||
```text
|
||
模块:cw_expense
|
||
事件:submit
|
||
条件:金额大于 10000 且公司为 company_001
|
||
流程:expense_approval v3
|
||
```
|
||
|
||
一个模块可以有多个绑定,但同一业务上下文只能选择一个最终流程。绑定的优先级、有效期和条件由后端校验,不能出现多个无法确定优先级的生效流程。
|
||
|
||
### 4.4 节点与连线(Node / Edge)
|
||
|
||
节点表达处理步骤,连线表达节点之间的路由和条件。
|
||
|
||
建议第一阶段支持:
|
||
|
||
```text
|
||
start 开始
|
||
user_task 人工审批
|
||
cc_task 抄送
|
||
exclusive_gateway 条件分支
|
||
parallel_gateway 并行分支
|
||
service_task 后端服务动作
|
||
end 结束
|
||
```
|
||
|
||
`b_level` 不再作为流程结构。流程结构由节点编码和连线表达。
|
||
|
||
### 4.5 流程实例(Instance)
|
||
|
||
某张业务单据某次提交产生的一次运行流程。一个业务对象可以有多次历史实例,但同一个业务事件通常只能有一个活动实例。
|
||
|
||
### 4.6 节点运行(Node Run)
|
||
|
||
某个节点在某次实例中的一次进入和处理记录。节点可能因退回、循环或重试被多次运行,因此不能只在实例上保存一个当前级别字段。
|
||
|
||
### 4.7 待办任务(Task)
|
||
|
||
某次节点运行分配给某个实际用户的工作项。每一个实际审批人使用独立记录,不使用逗号分隔的用户编码。
|
||
|
||
### 4.8 审批动作(Action)
|
||
|
||
用户或系统对流程执行的不可变事件,例如:
|
||
|
||
```text
|
||
submit 送审
|
||
approve 审批通过
|
||
reject 驳回并结束
|
||
return 退回修改
|
||
withdraw 发起人撤回
|
||
cancel 管理员或系统取消
|
||
claim 抢占待办
|
||
transfer 转交
|
||
delegate 委托处理
|
||
reassign 管理员转派
|
||
add_approver 加签
|
||
urge 催办
|
||
cc 抄送(系统动作)
|
||
```
|
||
|
||
数据库保存稳定编码,页面文案通过多语言资源显示。
|
||
|
||
### 4.9 抄送(Cc)
|
||
|
||
抄送把流程的进展或结果告知不参与审批的人。抄送不产生待办,不参与节点判定,只产生可查询的抄送记录和 outbox 通知。抄送记录使用独立的 `wf_cc`,不写入 `wf_task`。
|
||
|
||
## 5. 总体架构
|
||
|
||
```text
|
||
┌────────────────────────────────────────────────────────┐
|
||
│ 流程设计与发布 │
|
||
│ definition → version → node / edge → actor rule │
|
||
└───────────────────────┬────────────────────────────────┘
|
||
│ 发布版本
|
||
▼
|
||
┌────────────────────────────────────────────────────────┐
|
||
│ 流程绑定选择 │
|
||
│ module + event + organization + condition → version │
|
||
└───────────────────────┬────────────────────────────────┘
|
||
│ 业务提交
|
||
▼
|
||
┌────────────────────────────────────────────────────────┐
|
||
│ 流程运行引擎 │
|
||
│ instance → node_run → task → action │
|
||
│ │ │ │
|
||
│ ├── business status ├── audit log │
|
||
│ └── notification outbox └── timeline │
|
||
└────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
运行引擎必须在后端执行,前端只能提交意图:
|
||
|
||
```text
|
||
POST /workflow/instances/{id}/actions
|
||
{ "action": "approve", "comment": "同意", "idempotencyKey": "..." }
|
||
```
|
||
|
||
前端不能直接修改 `wf_instance`、`wf_task`、`wf_cc` 或业务表状态。
|
||
|
||
## 6. 配置模型
|
||
|
||
### 6.1 流程定义
|
||
|
||
```sql
|
||
create table dbo.wf_definition (
|
||
b_id varchar(50) not null primary key, -- 稳定流程编码
|
||
b_name nvarchar(200) not null, -- 流程名称
|
||
b_i18n varchar(150) null, -- 多语言资源键
|
||
b_canuse tinyint not null default 1,
|
||
b_bz nvarchar(2000) null,
|
||
b_created_by varchar(50) null,
|
||
b_created_at datetime2 null,
|
||
b_updated_by varchar(50) null,
|
||
b_updated_at datetime2 null
|
||
);
|
||
|
||
create index ix_wf_definition_canuse
|
||
on dbo.wf_definition (b_canuse, b_id);
|
||
```
|
||
|
||
`b_id` 创建后原则上不可修改。流程定义是逻辑身份,具体执行内容放在 `wf_version`。
|
||
|
||
### 6.2 流程版本
|
||
|
||
```sql
|
||
create table dbo.wf_version (
|
||
b_id varchar(50) not null primary key, -- 版本 ID
|
||
b_definition_id varchar(50) not null, -- 流程定义编码
|
||
b_version_no int not null, -- 版本序号
|
||
b_status varchar(20) not null default 'draft', -- 版本状态:draft(草稿)/published(已发布)/retired(已停用)
|
||
b_schema_version int not null default 1, -- 配置结构版本
|
||
b_canvas_json nvarchar(max) null, -- 设计器布局,不参与执行
|
||
b_checksum varchar(128) null, -- 发布时计算的配置摘要
|
||
b_published_by varchar(50) null,
|
||
b_published_at datetime2 null,
|
||
b_bz nvarchar(2000) null,
|
||
b_created_by varchar(50) null,
|
||
b_created_at datetime2 null,
|
||
b_updated_by varchar(50) null,
|
||
b_updated_at datetime2 null
|
||
);
|
||
|
||
create unique index ux_wf_version_no
|
||
on dbo.wf_version (b_definition_id, b_version_no);
|
||
|
||
create index ix_wf_version_status
|
||
on dbo.wf_version (b_definition_id, b_status, b_version_no);
|
||
```
|
||
|
||
发布时必须完成:
|
||
|
||
1. 校验开始节点、结束节点和连线完整性;
|
||
2. 校验所有人工节点至少有一个有效审批人规则;
|
||
3. 校验条件字段存在于对应模块的 `s_field`;
|
||
4. 校验不存在不可达节点、死循环和无出口节点;
|
||
5. 计算 `b_checksum`,发布后禁止修改节点和连线;
|
||
6. 写入发布审计和版本变更日志。
|
||
|
||
### 6.3 流程绑定
|
||
|
||
```sql
|
||
create table dbo.wf_binding (
|
||
b_id varchar(50) not null primary key, -- 绑定 ID
|
||
b_module_id varchar(50) not null, -- data / virtual 模块
|
||
b_event varchar(30) not null, -- 业务事件:submit(送审)/resubmit(重新提交)/change(变更)
|
||
b_version_id varchar(50) not null, -- 已发布流程版本
|
||
b_match_json nvarchar(max) null, -- 业务匹配条件 AST
|
||
b_org_scope_json nvarchar(max) null, -- 公司、组织范围
|
||
b_status_field varchar(50) null, -- 业务状态字段,必须来自 s_field
|
||
b_status_map_json nvarchar(max) null, -- 流程状态到业务状态的映射
|
||
b_priority int not null default 0,
|
||
b_effective_from datetime2 null,
|
||
b_effective_to datetime2 null,
|
||
b_canuse tinyint not null default 1,
|
||
b_bz nvarchar(2000) null,
|
||
b_created_by varchar(50) null,
|
||
b_created_at datetime2 null,
|
||
b_updated_by varchar(50) null,
|
||
b_updated_at datetime2 null
|
||
);
|
||
|
||
create index ix_wf_binding_select
|
||
on dbo.wf_binding (
|
||
b_module_id,
|
||
b_event,
|
||
b_canuse,
|
||
b_priority,
|
||
b_effective_from,
|
||
b_effective_to
|
||
);
|
||
```
|
||
|
||
`b_status_field` 用于兼容不同业务表已有的 `b_state`、`b_status` 等字段,但字段必须在模块元数据中登记。新建可审批业务表时,优先统一使用 `b_status`。
|
||
|
||
### 6.4 流程节点
|
||
|
||
```sql
|
||
create table dbo.wf_node (
|
||
b_id varchar(50) not null primary key, -- 节点 ID
|
||
b_version_id varchar(50) not null,
|
||
b_node_key varchar(80) not null, -- 版本内稳定编码
|
||
b_name nvarchar(200) not null,
|
||
b_i18n varchar(150) null,
|
||
b_node_type varchar(30) not null, -- 节点类型:start(开始)/user_task(人工审批)/cc_task(抄送)/exclusive_gateway(条件分支)/parallel_gateway(并行分支)/service_task(服务节点)/end(结束)
|
||
b_approval_mode varchar(30) null, -- 审批方式:single(单人)/any(或签)/all(会签)/serial(串行会签)/n_of_m(N 人通过)/percentage(按比例通过)
|
||
b_reject_mode varchar(30) null, -- 驳回策略:terminate(驳回结束)/return_initiator(退回发起人)/return_previous(退回上一节点)
|
||
b_due_hours decimal(10,2) null,
|
||
b_config_json nvarchar(max) null, -- 节点扩展配置
|
||
b_xh int not null default 0,
|
||
b_canuse tinyint not null default 1
|
||
);
|
||
|
||
create unique index ux_wf_node_key
|
||
on dbo.wf_node (b_version_id, b_node_key);
|
||
|
||
create index ix_wf_node_type
|
||
on dbo.wf_node (b_version_id, b_node_type, b_xh, b_node_key);
|
||
```
|
||
|
||
同一版本内 `b_node_key` 必须稳定。设计器可以显示名称,但节点关联和历史记录使用 `b_node_key`。
|
||
|
||
节点 `b_config_json` 可以保存以下低频配置:
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"allowInitiator": false,
|
||
"excludePreviousActors": true,
|
||
"allowClaim": false,
|
||
"commentRequired": true,
|
||
"requiredCount": 2,
|
||
"requiredRatio": 0.6,
|
||
"formPolicy": {
|
||
"readonly": ["b_amount"],
|
||
"required": ["b_reason"]
|
||
}
|
||
}
|
||
```
|
||
|
||
`requiredCount` 只对 `n_of_m` 生效,`requiredRatio` 只对 `percentage` 生效,都不能用界面约定替代后端校验。
|
||
|
||
### 6.5 节点连线
|
||
|
||
```sql
|
||
create table dbo.wf_edge (
|
||
b_id varchar(50) not null primary key,
|
||
b_version_id varchar(50) not null,
|
||
b_from_node_key varchar(80) not null,
|
||
b_to_node_key varchar(80) not null,
|
||
b_name nvarchar(200) null, -- 连线名称(画布标签,留空时前端按条件摘要显示)
|
||
b_condition_json nvarchar(max) null, -- 连线条件 AST
|
||
b_priority int not null default 0,
|
||
b_canuse tinyint not null default 1,
|
||
b_bz nvarchar(1000) null
|
||
);
|
||
|
||
create index ix_wf_edge_from
|
||
on dbo.wf_edge (b_version_id, b_from_node_key, b_canuse, b_priority, b_to_node_key);
|
||
|
||
create index ix_wf_edge_to
|
||
on dbo.wf_edge (b_version_id, b_to_node_key, b_canuse, b_priority, b_from_node_key);
|
||
```
|
||
|
||
同一个来源节点的条件连线按 `b_priority` 从小到大判断。发布校验要求条件分支具有明确的默认出口,避免所有条件不满足时流程卡死。
|
||
|
||
### 6.6 审批人规则
|
||
|
||
```sql
|
||
create table dbo.wf_actor_rule (
|
||
b_id varchar(50) not null primary key,
|
||
b_version_id varchar(50) not null,
|
||
b_node_key varchar(80) not null,
|
||
b_actor_type varchar(30) not null, -- 解析类型:user(指定用户)/initiator(发起人)/initiator_manager(发起人直属领导)/dept_manager(部门负责人)/position(岗位)/org_role(组织角色)/field_user(业务字段用户)/custom(自定义解析器)
|
||
b_actor_ref varchar(150) null, -- 用户、岗位、角色或字段编码
|
||
b_resolve_json nvarchar(max) null, -- 解析参数
|
||
b_condition_json nvarchar(max) null, -- 规则适用条件 AST
|
||
b_fallback_type varchar(30) null, -- 解析为空时的兜底:none(不兜底)/admin(管理员)/initiator(发起人)/custom(自定义)
|
||
b_fallback_ref varchar(150) null,
|
||
b_priority int not null default 0,
|
||
b_canuse tinyint not null default 1,
|
||
b_bz nvarchar(1000) null
|
||
);
|
||
|
||
create index ix_wf_actor_rule_node
|
||
on dbo.wf_actor_rule (b_version_id, b_node_key, b_canuse, b_priority, b_actor_type);
|
||
```
|
||
|
||
建议支持以下 `b_actor_type`:
|
||
|
||
| 类型 | 含义 |
|
||
| --- | --- |
|
||
| `user` | 指定用户 |
|
||
| `initiator` | 流程发起人 |
|
||
| `initiator_manager` | 发起人直属领导 |
|
||
| `dept_manager` | 发起人或业务归属部门负责人 |
|
||
| `position` | 岗位或职位 |
|
||
| `org_role` | 组织业务角色,不等同于功能权限角色 |
|
||
| `field_user` | 业务单据某字段对应的用户,例如销售员、负责人 |
|
||
| `custom` | 后端注册的审批人解析器 |
|
||
|
||
解析规则执行后,必须把规则快照和实际用户快照写入运行表。组织架构后续变化不能修改已经产生的历史审批责任。
|
||
|
||
## 7. 条件表达式
|
||
|
||
### 7.1 结构化 AST
|
||
|
||
业务条件、绑定条件和连线条件统一使用结构化 JSON。示例:
|
||
|
||
```json
|
||
{
|
||
"all": [
|
||
{ "field": "b_amount", "operator": "gt", "value": 10000 },
|
||
{ "field": "b_company_id", "operator": "eq", "value": "company_001" }
|
||
]
|
||
}
|
||
```
|
||
|
||
支持的基础逻辑:
|
||
|
||
```text
|
||
all AND
|
||
any OR
|
||
not NOT
|
||
```
|
||
|
||
支持的基础操作符:
|
||
|
||
```text
|
||
eq / ne / gt / ge / lt / le
|
||
in / not_in
|
||
like / is_null / is_not_null
|
||
between
|
||
```
|
||
|
||
### 7.2 条件安全边界
|
||
|
||
- `field` 必须来自模块 `s_field`,不能任意填写数据库列名;
|
||
- 字段类型、操作符和值类型由后端校验;
|
||
- 值可以引用流程上下文,例如 `{ "context": "initiator.deptId" }`;
|
||
- SQL 由后端编译为参数化查询;
|
||
- 普通管理员不能输入任意 SQL;
|
||
- 复杂业务条件使用后端注册的 `custom resolver`;
|
||
- 解析结果、命中的条件和选中的连线需要写入流程上下文或动作载荷,方便审计。
|
||
|
||
系统的 SQL 扩展能力仍然保留,但工作流条件不能直接采用前端字符串拼接 SQL。
|
||
|
||
## 8. 运行表结构
|
||
|
||
### 8.1 流程实例
|
||
|
||
```sql
|
||
create table dbo.wf_instance (
|
||
b_id uniqueidentifier not null primary key, -- 应用层生成 UUIDv7
|
||
b_definition_id varchar(50) not null,
|
||
b_version_id varchar(50) not null,
|
||
b_binding_id varchar(50) not null,
|
||
b_module_id varchar(50) not null,
|
||
b_event varchar(30) not null, -- 业务事件:submit(送审)/resubmit(重新提交)/change(变更)
|
||
b_business_id varchar(50) not null, -- 业务主键统一按字符串保存
|
||
b_business_no nvarchar(200) null,
|
||
b_org_id varchar(50) null,
|
||
b_initiator_id varchar(50) not null,
|
||
b_initiator_dept_id varchar(50) null,
|
||
b_status varchar(30) not null, -- 实例状态:running(运行中)/approved(已通过)/rejected(已驳回)/returned(已退回)/withdrawn(已撤回)/cancelled(已取消)/expired(已过期)/error(异常待处理)
|
||
b_context_json nvarchar(max) null, -- 运行变量
|
||
b_snapshot_json nvarchar(max) null, -- 提交时业务快照
|
||
b_current_summary_json nvarchar(max) null, -- 列表展示用的当前节点摘要
|
||
b_row_version bigint not null default 0,
|
||
b_idempotency_key varchar(100) null,
|
||
b_started_at datetime2 not null,
|
||
b_completed_at datetime2 null,
|
||
b_created_by varchar(50) null,
|
||
b_created_at datetime2 null,
|
||
b_updated_by varchar(50) null,
|
||
b_updated_at datetime2 null
|
||
);
|
||
|
||
create index ix_wf_instance_business
|
||
on dbo.wf_instance (b_module_id, b_business_id, b_event, b_status, b_id);
|
||
|
||
create index ix_wf_instance_initiator
|
||
on dbo.wf_instance (b_initiator_id, b_status, b_started_at, b_id);
|
||
|
||
create index ix_wf_instance_version
|
||
on dbo.wf_instance (b_version_id, b_started_at, b_id);
|
||
```
|
||
|
||
流程实例状态建议使用稳定编码:
|
||
|
||
```text
|
||
running 运行中
|
||
approved 已通过
|
||
rejected 已驳回并结束
|
||
returned 已退回修改
|
||
withdrawn 已撤回
|
||
cancelled 已取消
|
||
expired 已过期
|
||
error 异常待处理
|
||
```
|
||
|
||
“驳回”和“退回”必须区分:驳回通常结束本次流程,退回通常允许修改后重新提交。
|
||
|
||
### 8.2 节点运行
|
||
|
||
```sql
|
||
create table dbo.wf_node_run (
|
||
b_id uniqueidentifier not null primary key,
|
||
b_instance_id uniqueidentifier not null,
|
||
b_node_key varchar(80) not null,
|
||
b_run_no int not null default 1,
|
||
b_status varchar(30) not null, -- 节点状态:pending(待激活)/running(运行中)/completed(已完成)/skipped(已跳过)
|
||
b_required_count int null, -- 应签人数,按比例时由后端在节点激活时换算
|
||
b_completed_count int not null default 0,
|
||
b_rejected_count int not null default 0,
|
||
b_entered_at datetime2 null,
|
||
b_exited_at datetime2 null,
|
||
b_actor_snapshot_json nvarchar(max) null,
|
||
b_created_at datetime2 null
|
||
);
|
||
|
||
create index ix_wf_node_run_instance
|
||
on dbo.wf_node_run (b_instance_id, b_status, b_node_key, b_run_no, b_id);
|
||
```
|
||
|
||
`b_run_no` 用于支持退回后重新进入同一节点、循环节点和重新提交。
|
||
|
||
### 8.3 待办任务
|
||
|
||
一名实际审批人一行任务。会签、或签和串行会签通过同一个 `b_node_run_id` 进行聚合判断;串行会签的未激活任务使用 `waiting` 状态,按 `b_seq` 顺序逐个激活。
|
||
|
||
```sql
|
||
create table dbo.wf_task (
|
||
b_id uniqueidentifier not null primary key,
|
||
b_instance_id uniqueidentifier not null,
|
||
b_node_run_id uniqueidentifier not null,
|
||
b_node_key varchar(80) not null,
|
||
b_actor_rule_id varchar(50) null,
|
||
b_assignee_type varchar(30) not null, -- 受理人类型:user(指定用户)/position(岗位)/role(角色)/delegate(委托)
|
||
b_assignee_id varchar(50) not null, -- 实际用户编码
|
||
b_seq int not null default 0, -- 同节点内处理顺序,串行会签生效
|
||
b_status varchar(30) not null, -- 任务状态:waiting(未激活)/pending(待处理)/claimed(已抢占)/approved(已通过)/rejected(已驳回)/cancelled(已取消)/expired(已过期)
|
||
b_due_at datetime2 null,
|
||
b_claimed_by varchar(50) null,
|
||
b_claimed_at datetime2 null,
|
||
b_completed_by varchar(50) null,
|
||
b_completed_at datetime2 null,
|
||
b_rule_snapshot_json nvarchar(max) null,
|
||
b_created_at datetime2 null,
|
||
b_updated_at datetime2 null
|
||
);
|
||
|
||
create index ix_wf_task_assignee
|
||
on dbo.wf_task (b_assignee_id, b_status, b_due_at, b_id);
|
||
|
||
create index ix_wf_task_instance
|
||
on dbo.wf_task (b_instance_id, b_status, b_node_run_id, b_id);
|
||
|
||
create index ix_wf_task_due
|
||
on dbo.wf_task (b_status, b_due_at, b_id);
|
||
```
|
||
|
||
如果组织角色解析出大量候选人,仍然要在节点激活时形成可追溯的候选任务,不能只在页面实时计算“当前谁可以审批”。
|
||
|
||
### 8.4 审批动作
|
||
|
||
```sql
|
||
create table dbo.wf_action (
|
||
b_id uniqueidentifier not null primary key,
|
||
b_instance_id uniqueidentifier not null,
|
||
b_node_run_id uniqueidentifier null,
|
||
b_task_id uniqueidentifier null,
|
||
b_action varchar(30) not null, -- 动作编码:submit(送审)/approve(通过)/reject(驳回)/return(退回)/withdraw(撤回)/cancel(取消)/claim(抢占)/transfer(转交)/delegate(委托)/reassign(转派)/add_approver(加签)/urge(催办)/cc(抄送)
|
||
b_operator_id varchar(50) null,
|
||
b_from_status varchar(30) null,
|
||
b_to_status varchar(30) null,
|
||
b_comment nvarchar(2000) null,
|
||
b_payload_json nvarchar(max) null,
|
||
b_request_id varchar(64) null,
|
||
b_trace_id varchar(64) null,
|
||
b_occurdatetime datetime2 not null
|
||
);
|
||
|
||
create index ix_wf_action_instance
|
||
on dbo.wf_action (b_instance_id, b_occurdatetime, b_id);
|
||
|
||
create index ix_wf_action_operator
|
||
on dbo.wf_action (b_operator_id, b_occurdatetime, b_id);
|
||
```
|
||
|
||
`wf_action` 只追加,不更新历史动作。撤回、转交和管理员转派也必须形成动作记录。
|
||
|
||
### 8.5 委托、抄送和通知消息
|
||
|
||
```sql
|
||
create table dbo.wf_delegation (
|
||
b_id varchar(50) not null primary key,
|
||
b_from_user_id varchar(50) not null,
|
||
b_to_user_id varchar(50) not null,
|
||
b_scope_json nvarchar(max) null, -- 模块、动作、组织范围
|
||
b_start_at datetime2 not null,
|
||
b_end_at datetime2 not null,
|
||
b_reason nvarchar(500) null,
|
||
b_canuse tinyint not null default 1,
|
||
b_created_by varchar(50) null,
|
||
b_created_at datetime2 null,
|
||
b_updated_by varchar(50) null,
|
||
b_updated_at datetime2 null
|
||
);
|
||
|
||
create index ix_wf_delegation_user
|
||
on dbo.wf_delegation (b_from_user_id, b_canuse, b_start_at, b_end_at, b_to_user_id);
|
||
|
||
create table dbo.wf_cc (
|
||
b_id uniqueidentifier not null primary key,
|
||
b_instance_id uniqueidentifier not null,
|
||
b_node_run_id uniqueidentifier null,
|
||
b_node_key varchar(80) null, -- 来源节点
|
||
b_recipient_id varchar(50) not null, -- 抄送接收人
|
||
b_actor_rule_id varchar(50) null, -- 命中的抄送规则
|
||
b_status varchar(20) not null default 'unread', -- 阅读状态:unread(未读)/read(已读)
|
||
b_read_at datetime2 null,
|
||
b_rule_snapshot_json nvarchar(max) null,
|
||
b_created_at datetime2 null
|
||
);
|
||
|
||
create index ix_wf_cc_recipient
|
||
on dbo.wf_cc (b_recipient_id, b_status, b_created_at, b_id);
|
||
|
||
create index ix_wf_cc_instance
|
||
on dbo.wf_cc (b_instance_id, b_created_at, b_id);
|
||
|
||
create table dbo.wf_outbox (
|
||
b_id uniqueidentifier not null primary key,
|
||
b_event_code varchar(80) not null,
|
||
b_aggregate_type varchar(30) not null, -- 聚合类型:instance(流程实例)/task(待办任务)
|
||
b_aggregate_id varchar(50) not null,
|
||
b_payload_json nvarchar(max) not null,
|
||
b_status varchar(20) not null default 'pending', -- 发送状态:pending(待发送)/sent(已发送)/failed(发送失败)
|
||
b_retry_count int not null default 0,
|
||
b_next_retry_at datetime2 null,
|
||
b_last_error nvarchar(2000) null,
|
||
b_occurdatetime datetime2 not null
|
||
);
|
||
|
||
create index ix_wf_outbox_dispatch
|
||
on dbo.wf_outbox (b_status, b_next_retry_at, b_occurdatetime, b_id);
|
||
```
|
||
|
||
通知、站内信、邮件和企业微信等外部动作通过 outbox 异步发送,不阻塞审批事务。关键的实例、任务、抄送记录和业务状态仍然在同一数据库事务中提交。
|
||
|
||
## 9. 审批方式与路由语义
|
||
|
||
### 9.1 单人审批
|
||
|
||
节点解析出一个或多个候选人,但只有一个有效任务。管理员可以指定是否允许候选人抢占。
|
||
|
||
### 9.2 任一人通过
|
||
|
||
为所有候选人创建任务。任意一个人通过后,节点完成,其余待办自动变为 `cancelled`,并写入系统动作。或签的驳回同样即时生效:第一位处理人驳回时立即按 `b_reject_mode` 处理(见 9.7),其余待办自动取消。
|
||
|
||
### 9.3 全部通过
|
||
|
||
所有有效任务都通过后节点完成。任一人驳回时,根据节点 `b_reject_mode` 结束或退回。
|
||
|
||
### 9.4 N 人通过或按比例通过
|
||
|
||
节点运行记录保存 `required_count`、`completed_count` 和 `rejected_count`。计数必须由后端在锁定节点运行记录后完成,不能由前端计算。按比例通过时,节点激活必须把 `requiredRatio` 换算为 `required_count` 并写入快照,避免审批人数变化后票数漂移。
|
||
|
||
### 9.5 条件分支
|
||
|
||
条件分支通过 `wf_edge.b_condition_json` 表达。路由时记录:
|
||
|
||
```text
|
||
命中的边 ID
|
||
条件上下文
|
||
条件计算结果
|
||
计算时间
|
||
```
|
||
|
||
这样在业务条件后来变化时,历史流程仍然可以解释为什么走了某条路径。
|
||
|
||
### 9.6 串行会签
|
||
|
||
`serial` 模式按 `wf_task.b_seq` 顺序逐个激活审批人:
|
||
|
||
- 审批顺序由 `wf_actor_rule.b_priority` 和规则解析顺序决定,生成任务时写入 `b_seq`;
|
||
- 同一时刻只有一名审批人的任务为 `pending`,其余为 `waiting`;
|
||
- 当前审批人通过后激活下一名,全部通过后节点完成;
|
||
- 任一环节驳回按 `b_reject_mode` 处理(见 9.7),未激活任务置为 `cancelled`;
|
||
- 只有 `pending` 任务出现在待办列表,`waiting` 任务靠任务快照解释“即将轮到我”。
|
||
|
||
未激活任务的顺序允许管理员调整,但必须写入动作记录。
|
||
|
||
### 9.7 驳回和否决语义
|
||
|
||
驳回是即时生效的:任何审批方式下,节点内出现第一个驳回时立即按 `b_reject_mode` 处理,其余未完成任务自动置为 `cancelled` 并写入系统动作。
|
||
|
||
```text
|
||
terminate 实例进入 rejected,本次流程结束,业务状态投影为已驳回(即“一票否决”)
|
||
return_initiator 实例进入 returned,退回发起人修改后重新提交
|
||
return_previous 回到上一节点重新审批
|
||
```
|
||
|
||
- 会签、N 人通过和按比例通过节点默认使用 `terminate`,即任一驳回即一票否决;
|
||
- 驳回必须记录操作人、命中任务和当时的节点计数;
|
||
- “驳回不影响其他人”不是驳回语义;需要停止本次审批时应由发起人撤回。
|
||
|
||
### 9.8 加签
|
||
|
||
加签在当前节点运行中追加审批人:
|
||
|
||
- 加签人必须拥有 `action.{module}.add_approver`,且为当前节点待办人或流程管理员;
|
||
- 加签任务挂在同一个 `b_node_run_id` 下,按节点 `b_approval_mode` 参与判定;
|
||
- `n_of_m` 和 `percentage` 的 `required_count`、`requiredRatio` 不因加签自动改变,需要调整时必须在动作载荷中显式声明;
|
||
- 串行会签的加签任务按 `b_seq` 插入到指定位置,其余任务顺序顺延;
|
||
- 加签必须写入 `wf_action`,载荷保存加签理由、加签人和插入位置。
|
||
|
||
### 9.9 抄送
|
||
|
||
抄送节点激活后立即解析抄送人、写入 `wf_cc`、投递 outbox 通知并自动完成,不产生待办,也不参与节点计数。
|
||
|
||
- 抄送规则复用 `wf_actor_rule`,支持与审批人相同的解析类型;
|
||
- 抄送人不写入 `wf_task`,不进入审批人的待办列表;
|
||
- 抄送记录独立于审批动作历史,支持“抄送我的”查询和已读标记。
|
||
|
||
## 10. 权限和安全模型
|
||
|
||
### 10.1 三层校验
|
||
|
||
一次审批动作必须同时满足:
|
||
|
||
```text
|
||
1. 动作权限:用户拥有 action.{module}.{action}
|
||
2. 任务权限:当前存在分配给该用户的有效任务
|
||
3. 数据权限:用户对该业务记录具有相应操作范围
|
||
```
|
||
|
||
仅拥有 `action.xxx.approve` 不代表可以审批所有单据;仅出现在待办中也不代表可以绕过模块动作权限。
|
||
|
||
### 10.2 建议动作权限
|
||
|
||
```text
|
||
action.{module}.submit
|
||
action.{module}.approve
|
||
action.{module}.reject
|
||
action.{module}.return
|
||
action.{module}.withdraw
|
||
action.{module}.transfer
|
||
action.{module}.delegate
|
||
action.{module}.reassign
|
||
action.{module}.add_approver
|
||
action.{module}.urge
|
||
action.{module}.workflow_admin
|
||
```
|
||
|
||
已有系统使用 `audit` 的模块可以暂时做兼容映射,但新模块建议统一使用 `approve`。
|
||
|
||
### 10.3 职责分离
|
||
|
||
节点策略应支持:
|
||
|
||
```text
|
||
allowInitiator = false
|
||
excludePreviousActors = true
|
||
excludeCreator = true
|
||
```
|
||
|
||
这些规则必须由后端执行。前端只负责显示不可审批原因。
|
||
|
||
### 10.4 数据范围
|
||
|
||
继续复用 `s_user_data_power`:
|
||
|
||
- `read` 控制用户能否查看业务记录;
|
||
- `update` 控制业务修改;
|
||
- `action.{module}.approve` 等精确动作控制业务动作范围;
|
||
- 当前任务校验负责判断“是不是这一票的审批人”。
|
||
|
||
数据范围条件、流程条件和审批人规则不能互相替代。
|
||
|
||
## 11. 业务状态投影
|
||
|
||
工作流实例是流程事实来源,业务表的状态字段是查询和业务模块使用的投影。
|
||
|
||
推荐状态映射:
|
||
|
||
```json
|
||
{
|
||
"running": "pending_approval",
|
||
"approved": "approved",
|
||
"rejected": "rejected",
|
||
"returned": "draft",
|
||
"withdrawn": "draft",
|
||
"cancelled": "cancelled"
|
||
}
|
||
```
|
||
|
||
业务表中的状态字段不能由普通 CRUD 保存接口随意修改。工作流相关状态变更必须通过流程服务完成,并且在同一事务中:
|
||
|
||
```text
|
||
锁定业务记录
|
||
→ 校验当前状态和版本
|
||
→ 校验用户权限与任务
|
||
→ 写 wf_action
|
||
→ 更新 wf_task / wf_node_run
|
||
→ 创建下一节点任务或结束实例
|
||
→ 更新业务表状态投影
|
||
→ 写 s_log_audit / s_log_audit_field
|
||
→ 写 wf_outbox
|
||
→ 提交
|
||
```
|
||
|
||
如果业务表本身还有“结算、放单、作废”等业务状态,工作流状态不能覆盖这些业务生命周期状态。必要时使用独立的 `b_workflow_status` 或在模块配置中声明状态映射。
|
||
|
||
## 12. 并发、幂等和异常处理
|
||
|
||
### 12.1 幂等
|
||
|
||
提交和审批接口都必须支持幂等键:
|
||
|
||
```text
|
||
request_id + operator_id + action
|
||
```
|
||
|
||
同一请求重复到达时返回第一次处理结果,不重复创建实例、任务和动作。
|
||
|
||
### 12.2 乐观锁
|
||
|
||
`wf_instance.b_row_version` 用于防止多个管理员或审批人同时推进同一个实例。更新时必须带上读取时的版本号。
|
||
|
||
### 12.3 任务抢占
|
||
|
||
对于允许抢占的候选任务:
|
||
|
||
1. 后端锁定待办记录;
|
||
2. 校验任务仍为 `pending`;
|
||
3. 写入 `claimed_by` 和 `claimed_at`;
|
||
4. 修改为 `claimed`;
|
||
5. 其他用户再次操作时返回任务已被处理或已被占用。
|
||
|
||
### 12.4 审批人为空
|
||
|
||
审批人解析为空时不能自动跳过节点。应进入:
|
||
|
||
```text
|
||
error / waiting_admin_resolve
|
||
```
|
||
|
||
管理员可以通过加签补充审批人或转派,但必须形成管理员动作和审计记录。
|
||
|
||
### 12.5 外部通知失败
|
||
|
||
通知失败不回滚已经完成的审批事务。outbox 记录重试次数、下次重试时间和最后错误,超过阈值进入人工处理队列。
|
||
|
||
## 13. 后端服务接口建议
|
||
|
||
接口名称可根据现有 API 规范调整,但不能使用通用表保存接口直接承载流程动作。
|
||
|
||
```text
|
||
POST /api/workflows/definitions/{definitionId}/validate
|
||
POST /api/workflows/versions/{versionId}/publish
|
||
POST /api/workflows/instances/submit
|
||
GET /api/workflows/tasks
|
||
GET /api/workflows/instances/{instanceId}
|
||
GET /api/workflows/instances/{instanceId}/timeline
|
||
POST /api/workflows/tasks/{taskId}/actions
|
||
POST /api/workflows/instances/{instanceId}/withdraw
|
||
POST /api/workflows/tasks/{taskId}/transfer
|
||
POST /api/workflows/tasks/{taskId}/delegate
|
||
POST /api/workflows/tasks/{taskId}/add-approver
|
||
POST /api/workflows/instances/{instanceId}/urge
|
||
GET /api/workflows/cc
|
||
```
|
||
|
||
审批动作请求示例:
|
||
|
||
```json
|
||
{
|
||
"action": "approve",
|
||
"comment": "费用和附件已核对",
|
||
"payload": {},
|
||
"idempotencyKey": "req-20260922-000001"
|
||
}
|
||
```
|
||
|
||
后端返回中应包含:
|
||
|
||
```text
|
||
实例状态
|
||
当前节点摘要
|
||
下一步待办摘要
|
||
业务状态投影
|
||
动作结果和审计事件 ID
|
||
```
|
||
|
||
## 14. 前端配置和使用页面
|
||
|
||
### 14.1 流程管理页
|
||
|
||
替代原来的“模块树 + sh_set 表格”,展示:
|
||
|
||
```text
|
||
流程名称
|
||
所属业务模块
|
||
触发事件
|
||
当前发布版本
|
||
草稿版本
|
||
状态
|
||
适用组织
|
||
生效时间
|
||
最后发布人
|
||
```
|
||
|
||
操作包括:新建、复制版本、编辑草稿、校验、模拟、发布、停用、查看版本历史。
|
||
|
||
### 14.2 流程设计器
|
||
|
||
使用节点画布,而不是只编辑级别表格。节点属性面板至少包括:
|
||
|
||
- 节点名称和节点类型;
|
||
- 审批方式;
|
||
- 审批人来源;
|
||
- 条件和默认分支;
|
||
- 驳回、退回和撤回策略;
|
||
- 审批时限;
|
||
- 表单字段只读、必填和可见策略;
|
||
- 是否允许转交、委托、加签和抢占。
|
||
|
||
### 14.3 发布前模拟
|
||
|
||
管理员可以输入一组业务样例数据,查看:
|
||
|
||
```text
|
||
选择了哪个流程绑定
|
||
命中了哪些条件
|
||
每个节点解析出哪些审批人
|
||
最终生成哪些待办
|
||
是否触发申请人自审限制
|
||
```
|
||
|
||
### 14.4 流程待办和历史
|
||
|
||
业务用户使用独立的入口:
|
||
|
||
```text
|
||
我的待办 审批中心菜单,读 wf_task(当前用户的 pending/claimed 任务)
|
||
我发起的 审批中心菜单,读 wf_instance(b_initiator_id = 当前用户)
|
||
我已处理 审批中心菜单,读 wf_task(当前用户已完成的任务)
|
||
抄送我的 审批中心菜单,读 wf_cc
|
||
流程监控 不开独立菜单,作为「流程管理」页内的页签,供管理员查看
|
||
审批历史 不开独立菜单,作为业务单据详情页的时间线
|
||
```
|
||
|
||
菜单归属:
|
||
|
||
- 「我的待办 / 我发起的 / 我已处理 / 抄送我的」是**审批中心**(一级菜单)下的四个页面,面向全员,用 `menu.approval*` 授权;
|
||
- 「流程监控」并入「系统管理 → 流程管理」页,与流程配置、委派管理同页,共用 `menu.workflow` 授权。配置和监控同属管理员场景,不再单独开菜单;仅当监控受众与配置受众分离(例如业务主管要看本部门积压)时,才拆到审批中心下并单独授权;
|
||
- 「审批历史」是单据详情页里的时间线(读 `wf_action`),脱离单据没有使用场景,不做独立入口。
|
||
|
||
业务详情页只显示当前流程状态、当前待办和时间线,不直接读取流程配置表判断按钮是否可用。
|
||
|
||
## 15. 旧数据迁移
|
||
|
||
### 15.1 配置迁移
|
||
|
||
| 旧字段 | 新模型 |
|
||
| --- | --- |
|
||
| `sh_set.b_module_id` | `wf_binding.b_module_id` |
|
||
| `sh_set.b_level` | 线性节点的顺序和节点编码 |
|
||
| `sh_set.b_user_id` | `wf_actor_rule` 的 `user` 规则 |
|
||
| `sh_set.b_condition` | 条件 AST;不能自动转换的进入人工复核 |
|
||
| `sh_set.b_logic`、`b_leap` | 节点类型、连线条件或退回策略,不能机械照搬 |
|
||
| `sh_set.b_bz` | 节点或审批人规则备注 |
|
||
|
||
### 15.2 历史迁移
|
||
|
||
| 旧字段 | 新模型 |
|
||
| --- | --- |
|
||
| `sh_record.b_dj_id` | `wf_instance.b_business_id` |
|
||
| `sh_record.b_level` | `wf_node_run.b_node_key` / `b_run_no` |
|
||
| `b_tosh_useid` | 多条 `wf_task` |
|
||
| `b_link_useid` | 已处理用户快照或动作载荷 |
|
||
| `b_dosh_useid` | `wf_action.b_operator_id` 历史动作 |
|
||
| `b_sh_date` | `wf_action.b_occurdatetime` |
|
||
| `b_sh_memo` | `wf_action.b_comment` |
|
||
| 业务表 `b_state` | 保留为业务状态投影 |
|
||
|
||
历史迁移不应篡改原始业务状态和原始日志。无法完整映射的旧数据可以以“历史兼容实例”导入,并在 `b_payload_json` 中保存原始字段。
|
||
|
||
### 15.3 切换策略
|
||
|
||
建议按模块逐步切换:
|
||
|
||
1. 旧审批设置只读,停止新增配置;
|
||
2. 将旧线性配置导入新流程草稿;
|
||
3. 通过模拟和业务负责人确认审批路径;
|
||
4. 发布新版本;
|
||
5. 新提交使用新流程,历史实例继续由旧逻辑或兼容服务处理;
|
||
6. 稳定后再迁移历史查询和审批中心;
|
||
7. 最后下线旧 `sh_set`、`sh_record` 的写入入口。
|
||
|
||
## 16. 分阶段落地建议
|
||
|
||
涉及枚举的能力——审批方式(`b_approval_mode`)、节点类型(`b_node_type`)和动作编码——在第一阶段一次做完。枚举值在运行后再扩充,会导致历史数据、界面文案和后端分支各自维护,代价高于一次实现。
|
||
|
||
### 第一阶段:企业级基础内核
|
||
|
||
建议先实现:
|
||
|
||
- 流程定义、版本、发布和停用;
|
||
- 模块和业务事件绑定;
|
||
- 开始、人工审批、抄送、条件分支、结束节点;
|
||
- 单人、或签、会签、串行会签、N 人通过、按比例通过;
|
||
- 指定用户、发起人领导、部门负责人;
|
||
- 提交、通过、驳回、退回、撤回、加签、催办;
|
||
- 流程实例、节点运行、待办、抄送和动作历史;
|
||
- 动作权限、任务权限和数据范围三层校验;
|
||
- 业务状态投影、审计日志和 outbox 通知。
|
||
|
||
### 第二阶段:组织和协同能力
|
||
|
||
- 岗位和组织角色审批人;
|
||
- 委托、转交、管理员转派;
|
||
- 审批期限、提醒和升级;
|
||
- 流程模拟和审批人解析预览;
|
||
- 流程监控和 SLA 报表。
|
||
|
||
### 第三阶段:复杂业务编排
|
||
|
||
- 服务节点和外部系统回调;
|
||
- 自动补数、自动校验和自动归档;
|
||
- 多公司、多租户和跨组织审批;
|
||
- 日历工作时间和节假日 SLA;
|
||
- 流程归档、分区和历史数据治理;
|
||
- 失败补偿和人工恢复机制。
|
||
|
||
## 17. 发布前检查清单
|
||
|
||
### 流程配置
|
||
|
||
- 是否存在且只有一个开始节点;
|
||
- 是否至少存在一个结束节点;
|
||
- 所有节点是否可达;
|
||
- 所有人工节点是否存在有效审批人规则;
|
||
- 所有抄送节点是否存在有效抄送人规则;
|
||
- 串行会签节点的审批人顺序来源是否明确;
|
||
- 所有条件字段是否存在于 `s_field`;
|
||
- 是否存在没有默认出口的条件分支;
|
||
- 是否存在重复生效的流程绑定;
|
||
- 是否配置了驳回和退回策略;
|
||
- 是否存在申请人自审风险;
|
||
- 是否配置审批人为空时的处理方式。
|
||
|
||
### 运行安全
|
||
|
||
- 业务记录和流程实例是否在同一事务内更新;
|
||
- 是否有幂等键和乐观锁;
|
||
- 是否校验动作权限、任务权限和数据范围;
|
||
- 是否禁止前端直接修改流程状态;
|
||
- 是否记录审批人规则和实际审批人的快照;
|
||
- 是否写入审计日志和 outbox;
|
||
- 是否能处理重复点击、超时、转交和通知失败。
|
||
|
||
## 18. 最终建议
|
||
|
||
FMS 的审批设计应采用以下边界:
|
||
|
||
```text
|
||
权限:用户能不能做这种动作
|
||
流程:当前轮到谁做什么
|
||
业务:单据现在处于什么业务状态
|
||
审计:过去发生过什么以及当时依据是什么
|
||
```
|
||
|
||
不要把四个问题压缩到一张 `sh_set` 表或一个业务状态字段中。新系统应以“版本化流程定义 + 运行实例 + 独立待办 + 不可变动作历史”为核心,业务模块只负责绑定流程并展示状态投影。这样才能从简单的多级审核扩展到大公司需要的组织审批、条件路由、会签、委托、审计和流程治理。
|
||
|
||
## 19. 流程图编辑器选型
|
||
|
||
### 19.1 选型结论
|
||
|
||
FMS 流程设计器第一阶段选择 **LogicFlow**。
|
||
|
||
LogicFlow 用于实现流程定义的可视化编辑,包括节点拖拽、连线、条件分支、缩放、框选、撤销重做和流程图导入导出。它只负责前端编辑体验,不负责审批执行、权限判断、流程状态推进和数据库事务。
|
||
|
||
```text
|
||
LogicFlow
|
||
└── 负责流程图编辑器
|
||
|
||
FMS Workflow Service
|
||
└── 负责流程校验、发布、审批人解析、实例推进和事务
|
||
|
||
wf_definition / wf_version / wf_node / wf_edge
|
||
└── 负责保存流程领域模型
|
||
```
|
||
|
||
### 19.2 备选方案比较
|
||
|
||
| 方案 | 定位 | FMS 适配评价 |
|
||
| --- | --- | --- |
|
||
| Vue Flow | Vue 3 通用节点画布 | 上手简单,适合原型和简单流程,但流程扩展需要自行建设 |
|
||
| LogicFlow | 业务逻辑流程图编辑器 | 与审批流、条件分支、业务流程图最匹配,作为当前首选 |
|
||
| AntV X6 | 通用图形和图编辑引擎 | 扩展性和通用性最强,但需要自行实现较多流程语义和编辑器能力 |
|
||
|
||
如果未来扩展为统一企业图形平台,涉及组织架构图、数据血缘、ER 图、网络拓扑或复杂端口连接,可以再评估 AntV X6。当前业务目标是审批流和业务流程图,不提前引入 X6 的底层复杂度。
|
||
|
||
### 19.3 依赖和集成边界
|
||
|
||
前端依赖建议使用:
|
||
|
||
```text
|
||
@logicflow/core
|
||
@logicflow/extension
|
||
```
|
||
|
||
具体版本以项目锁定的 Vue 3 和 Vite 版本兼容性验证结果为准。LogicFlow 的扩展包用于节点、控制栏、小地图、菜单、选择框和流程相关扩展;流程业务规则仍由 FMS 自己实现。
|
||
|
||
流程设计器建议拆成以下组件:
|
||
|
||
```text
|
||
WorkflowDesigner
|
||
├── WorkflowToolbar
|
||
├── WorkflowNodePanel
|
||
├── WorkflowCanvas(LogicFlow)
|
||
├── WorkflowPropertyPanel
|
||
├── WorkflowValidationPanel
|
||
└── WorkflowSimulationPanel
|
||
```
|
||
|
||
### 19.4 领域模型和画布模型隔离
|
||
|
||
不能直接把 LogicFlow 的原始 JSON 当作流程定义存储。数据库和接口使用 FMS 自己的稳定模型:
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"nodes": [
|
||
{
|
||
"nodeKey": "department_manager",
|
||
"nodeType": "user_task",
|
||
"name": "部门负责人审批",
|
||
"config": {
|
||
"approvalMode": "single"
|
||
}
|
||
}
|
||
],
|
||
"edges": [
|
||
{
|
||
"from": "start",
|
||
"to": "department_manager",
|
||
"condition": null
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
映射关系如下:
|
||
|
||
| FMS 领域模型 | LogicFlow 画布模型 |
|
||
| --- | --- |
|
||
| `b_node_key` | `id` |
|
||
| `b_node_type` | 自定义节点类型 |
|
||
| `b_name` | 节点显示文本 |
|
||
| `b_config_json` | 节点属性面板数据 |
|
||
| `b_from_node_key` | `sourceNodeId` |
|
||
| `b_to_node_key` | `targetNodeId` |
|
||
| `b_condition_json` | 连线属性或条件编辑器数据 |
|
||
| 节点位置和缩放 | `b_canvas_json` |
|
||
|
||
建议建立独立的画布适配层:
|
||
|
||
```text
|
||
workflowDomainToLogicFlow(version)
|
||
logicFlowToWorkflowDraft(graph)
|
||
validateWorkflowDomain(version)
|
||
```
|
||
|
||
`wf_node` 和 `wf_edge` 是可执行流程的来源,`b_canvas_json` 只保存节点坐标、画布缩放、分组位置等界面布局信息。即使以后更换为 X6,也不能改变流程节点编码、审批规则和运行实例语义。
|
||
|
||
### 19.5 LogicFlow 节点约定
|
||
|
||
画布节点类型与工作流节点类型保持一一映射:
|
||
|
||
```text
|
||
fms-start
|
||
fms-user-task
|
||
fms-cc-task
|
||
fms-exclusive-gateway
|
||
fms-parallel-gateway
|
||
fms-service-task
|
||
fms-end
|
||
```
|
||
|
||
LogicFlow 的节点类型只是前端渲染类型,后端落库仍使用:
|
||
|
||
```text
|
||
start
|
||
user_task
|
||
cc_task
|
||
exclusive_gateway
|
||
parallel_gateway
|
||
service_task
|
||
end
|
||
```
|
||
|
||
人工审批节点的审批人、审批方式、时限和表单策略在右侧属性面板编辑,保存到 `wf_node` 和 `wf_actor_rule`,不保存为节点显示文本。
|
||
|
||
### 19.6 编辑器校验边界
|
||
|
||
LogicFlow 可以做即时交互校验,例如:
|
||
|
||
- 禁止连接到自身;
|
||
- 禁止从结束节点继续连线;
|
||
- 限制不同节点类型的连接方向;
|
||
- 删除节点时提示是否同时删除相关连线;
|
||
- 连线重复时提示用户。
|
||
|
||
但以下校验必须由后端在保存和发布时再次执行:
|
||
|
||
- 是否存在唯一开始节点;
|
||
- 是否存在可达的结束节点;
|
||
- 所有人工节点是否存在审批人规则;
|
||
- 条件字段和操作符是否合法;
|
||
- 是否存在死循环、不可达节点和无默认出口;
|
||
- 流程绑定是否存在条件冲突;
|
||
- 发布版本是否可以被当前组织范围使用。
|
||
|
||
前端校验用于提升体验,后端校验才是最终规则。
|
||
|
||
### 19.7 迁移和长期替换策略
|
||
|
||
当前选择 LogicFlow 不意味着将流程数据绑定到 LogicFlow。以后如果需要改用 AntV X6,只替换:
|
||
|
||
```text
|
||
LogicFlowCanvasAdapter → X6CanvasAdapter
|
||
```
|
||
|
||
以下内容不应改变:
|
||
|
||
- `wf_definition`;
|
||
- `wf_version`;
|
||
- `wf_node`;
|
||
- `wf_edge`;
|
||
- `wf_actor_rule`;
|
||
- `wf_instance`、`wf_task`、`wf_action`;
|
||
- 流程发布和运行接口;
|
||
- 审批历史和审计记录。
|
||
|
||
因此,LogicFlow 是当前阶段的流程设计器选型,而不是 FMS 工作流领域模型的依赖。
|
||
|
||
## 20. 签核能力清单
|
||
|
||
本节把常见签核能力映射到本设计的配置、动作和表,作为能力边界声明。
|
||
|
||
| 能力 | 支持情况 | 实现方式 |
|
||
| --- | --- | --- |
|
||
| 会签(全部通过) | 支持 | `b_approval_mode = 'all'`,同节点多任务聚合,见 9.3 |
|
||
| 或签(任一人通过) | 支持 | `'any'`,第一票决定,其余任务自动取消,见 9.2 |
|
||
| 穿行会签(串行会签) | 支持 | `'serial'`,按 `wf_task.b_seq` 逐个激活,见 9.6 |
|
||
| N 人通过 | 支持 | `'n_of_m'`,`requiredCount` 由后端锁定计算,见 9.4 |
|
||
| 比例签 | 支持 | `'percentage'`,`requiredRatio` 在节点激活时换算为 `required_count`,见 9.4 |
|
||
| 一票否决 | 支持 | 驳回即时生效,`b_reject_mode = 'terminate'`,见 9.7 |
|
||
| 转办/转交 | 支持 | 动作 `transfer`,任务级转交并强制留痕,见 8.4 |
|
||
| 委托 | 支持 | `wf_delegation` + 动作 `delegate`,支持时间范围和范围过滤,见 8.5 |
|
||
| 加签 | 支持 | 动作 `add_approver`,向当前节点运行追加任务,见 9.8 |
|
||
| 催办 | 支持 | 动作 `urge`,通过 outbox 发送提醒,不改变任务状态 |
|
||
| 抄送 | 支持 | `cc_task` 节点 + `wf_cc` 记录 + outbox 通知,见 9.9 |
|
||
| 跳转(指定任意节点) | 不支持 | 见下方说明 |
|
||
|
||
跳转不是遗漏,而是有意排除:流程推进只允许沿已发布版本的连线进行。允许运行中跳到任意节点,会绕过节点校验、审批人解析和动作权限,并让历史流程无法解释为什么走了这条路径。需要类似效果时使用:
|
||
|
||
- 发起人撤回后重新提交,产生新实例和新快照;
|
||
- 管理员使用任务级 `reassign` 转派,并写入管理员动作;
|
||
- 业务上确需提前结束时使用 `cancel`,由系统记录取消原因。
|