243 lines
9.9 KiB
Markdown
243 lines
9.9 KiB
Markdown
|
|
# 会签配置详解
|
|||
|
|
|
|||
|
|
会签(多实例任务):一个审批节点同时由多人处理,根据完成比例决定是否通过。
|
|||
|
|
|
|||
|
|
**重要前提:** 不启动多实例(不加 `multiInstanceLoopCharacteristics`),则只会创建一个任务,默认不启动。不启动多实例时,会签相关配置都无效。
|
|||
|
|
|
|||
|
|
## 1. 会签通用结构
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<bpmn2:userTask id="task_xxx" name="会签节点"
|
|||
|
|
flowable:assignee="${assigneeUserId}"
|
|||
|
|
flowable:countersignRule="${RULE}">
|
|||
|
|
<bpmn2:extensionElements>
|
|||
|
|
<flowable:taskExtendJson value="{...}" />
|
|||
|
|
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
|
|||
|
|
<flowable:taskCountersignExtendJson value="${BASE64_CONFIG}" />
|
|||
|
|
</bpmn2:extensionElements>
|
|||
|
|
<bpmn2:multiInstanceLoopCharacteristics isSequential="false"
|
|||
|
|
flowable:collection="${COLLECTION_EXPR}"
|
|||
|
|
flowable:elementVariable="assigneeUserId">
|
|||
|
|
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
|
|||
|
|
${COMPLETION_CONDITION}
|
|||
|
|
</bpmn2:completionCondition>
|
|||
|
|
</bpmn2:multiInstanceLoopCharacteristics>
|
|||
|
|
</bpmn2:userTask>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 2. 会签规则(countersignRule)
|
|||
|
|
|
|||
|
|
| 规则值 | 说明 | completionCondition |
|
|||
|
|
|--------|------|---------------------|
|
|||
|
|
| `countersign_all` | 全部通过 | `${nrOfCompletedInstances/nrOfInstances==1}` |
|
|||
|
|
| `countersign_one` | 一人通过即可 | `${nrOfCompletedInstances/nrOfInstances>0}` |
|
|||
|
|
| `countersign_half` | 半数通过 | `${nrOfCompletedInstances/nrOfInstances>=0.5}` |
|
|||
|
|
| `countersign_proportion` | 按比例通过(自定义比例) | `${nrOfCompletedInstances/nrOfInstances>=0.N}` |
|
|||
|
|
| `countersign_custom` | 自定义选人 | 自定义 |
|
|||
|
|
|
|||
|
|
> **注意:** XML 中 `>=` 需要写成 `>=`,如 `${nrOfCompletedInstances/nrOfInstances>=0.6}`
|
|||
|
|
|
|||
|
|
## 3. 顺序会签 vs 并行会签
|
|||
|
|
|
|||
|
|
| 对比项 | 并行会签(isSequential="false") | 顺序会签(isSequential="true") |
|
|||
|
|
|--------|-------------------------------|-------------------------------|
|
|||
|
|
| 任务创建 | 同时为所有审批人创建任务 | 按顺序逐个创建,前一个完成才创建下一个 |
|
|||
|
|
| nrOfActiveInstances | 等于尚未完成的审批人数 | 始终为 1 |
|
|||
|
|
| 适用场景 | 多人同时审批,互不影响 | 按层级逐级审批 |
|
|||
|
|
| 完成条件触发 | 每完成一个就检查条件 | 每完成一个就检查条件 |
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- 并行会签(默认,所有人同时收到任务) -->
|
|||
|
|
<bpmn2:multiInstanceLoopCharacteristics isSequential="false" ...>
|
|||
|
|
<!-- 顺序会签(按顺序逐个审批) -->
|
|||
|
|
<bpmn2:multiInstanceLoopCharacteristics isSequential="true" ...>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 4. 多实例内置流程变量
|
|||
|
|
|
|||
|
|
多实例自动创建以下 3 个流程变量,用于 `completionCondition` 表达式:
|
|||
|
|
|
|||
|
|
| 变量名 | 类型 | 说明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| `nrOfInstances` | int | 实例总数(审批人总数) |
|
|||
|
|
| `nrOfActiveInstances` | int | 当前活跃的(未完成的)实例数。**顺序会签时此值始终为 1** |
|
|||
|
|
| `nrOfCompletedInstances` | int | 已完成的实例个数 |
|
|||
|
|
|
|||
|
|
**完成条件示例:**
|
|||
|
|
```
|
|||
|
|
${nrOfCompletedInstances/nrOfInstances==1} → 全部完成
|
|||
|
|
${nrOfCompletedInstances/nrOfInstances>0} → 至少一人完成
|
|||
|
|
${nrOfCompletedInstances/nrOfInstances>=0.5} → 50%完成
|
|||
|
|
${nrOfCompletedInstances/nrOfInstances>=0.6} → 60%完成时,删除其他未完成任务,继续下一步
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 5. 会签工具 Bean — flowUtil
|
|||
|
|
|
|||
|
|
`flowUtil` 是系统暴露的 Spring Bean,提供会签人员集合获取方法:
|
|||
|
|
|
|||
|
|
| 方法 | 说明 | 用于 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `${flowUtil.stringToList('user1,user2')}` | 将逗号分隔字符串转为 List 集合 | 固定人员会签(candidateUsers) |
|
|||
|
|
| `${flowUtil.getAssigneeUsers(execution,'BASE64配置')}` | 根据 Base64 编码的配置动态获取审批人列表 | 部门/岗位/表单字段会签 |
|
|||
|
|
|
|||
|
|
**`stringToList` 的变量写法:**
|
|||
|
|
```xml
|
|||
|
|
<!-- 固定人员 -->
|
|||
|
|
flowable:collection="${flowUtil.stringToList('admin,jeecg,zhangsan')}"
|
|||
|
|
<!-- 使用流程变量(动态) -->
|
|||
|
|
flowable:collection="${flowUtil.stringToList(assigneeUserIdList)}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**`getAssigneeUsers` 的 Base64 配置:** 将 taskCountersignExtendJson 的 JSON(去掉 timestamp 和 countersignProportion)进行 Base64 编码后作为参数传入。
|
|||
|
|
|
|||
|
|
## 4. 会签审批人类型(auditorUserType)
|
|||
|
|
|
|||
|
|
taskCountersignExtendJson 是 Base64 编码的 JSON,解码后结构如下:
|
|||
|
|
|
|||
|
|
### 人员会签(candidateUsers)
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"auditorUserType": "candidateUsers",
|
|||
|
|
"auditorUserIds": ["jeecg", "admin"],
|
|||
|
|
"countersignProportion": "0.2",
|
|||
|
|
"timestamp": 1758257673121
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
对应 XML collection:
|
|||
|
|
```xml
|
|||
|
|
flowable:collection="${flowUtil.stringToList('jeecg,admin')}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 部门会签(candidateDepts)
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"auditorUserType": "candidateDepts",
|
|||
|
|
"auditorDeptIds": ["部门ID1", "部门ID2"],
|
|||
|
|
"timestamp": 1758257664644
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
对应 XML collection:
|
|||
|
|
```xml
|
|||
|
|
flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 职务会签(candidatePosts)
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"auditorUserType": "candidatePosts",
|
|||
|
|
"auditorPostIds": ["职务ID1", "职务ID2"],
|
|||
|
|
"timestamp": 1758202368122
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
对应 XML collection:
|
|||
|
|
```xml
|
|||
|
|
flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 表单字段会签(formData)
|
|||
|
|
|
|||
|
|
从表单中的用户选择控件动态获取会签人:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"auditorUserType": "formData",
|
|||
|
|
"auditorCountersignFormField": "select_user_xxx",
|
|||
|
|
"auditorCountersignFormFieldType": "select-user",
|
|||
|
|
"timestamp": 1758257668105
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
对应 XML collection:
|
|||
|
|
```xml
|
|||
|
|
flowable:collection="${flowUtil.getAssigneeUsers(execution,'BASE64编码的配置')}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 7. 完整会签示例 — 人员会签(并行,全部通过)
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<bpmn2:userTask id="task_countersign" name="部门会签"
|
|||
|
|
flowable:assignee="${assigneeUserId}"
|
|||
|
|
flowable:countersignRule="countersign_all">
|
|||
|
|
<bpmn2:extensionElements>
|
|||
|
|
<flowable:taskCountersignExtendJson value="eyJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVVc2VycyIsImF1ZGl0b3JVc2VySWRzIjpbImFkbWluIiwiamVlY2ciXSwidGltZXN0YW1wIjoxNzU4MjU3NjczMTIxfQ==" />
|
|||
|
|
</bpmn2:extensionElements>
|
|||
|
|
<bpmn2:multiInstanceLoopCharacteristics
|
|||
|
|
flowable:collection="${flowUtil.stringToList('admin,jeecg')}"
|
|||
|
|
flowable:elementVariable="assigneeUserId">
|
|||
|
|
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
|
|||
|
|
${nrOfCompletedInstances/nrOfInstances==1}
|
|||
|
|
</bpmn2:completionCondition>
|
|||
|
|
</bpmn2:multiInstanceLoopCharacteristics>
|
|||
|
|
</bpmn2:userTask>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 8. 完整会签示例 — 人员比例通过(并行,20%)
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<bpmn2:userTask id="task_vote" name="投票表决"
|
|||
|
|
flowable:assignee="${assigneeUserId}"
|
|||
|
|
flowable:countersignRule="countersign_proportion"
|
|||
|
|
flowable:countersignProportion="0.2">
|
|||
|
|
<bpmn2:extensionElements>
|
|||
|
|
<flowable:taskExtendJson value="{"sameMode":0,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":false,"isSkipApproval":false,"isAssignedByPreviousNode":false,"isEmptyAssignedByPreviousNode":false,"isSkipApprovedOnCountersignReturn":false}" />
|
|||
|
|
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
|
|||
|
|
<flowable:taskCountersignExtendJson value="eyJjb3VudGVyc2lnblByb3BvcnRpb24iOiIwLjIiLCJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVVc2VycyIsImF1ZGl0b3JVc2VySWRzIjpbImplZWNnIl0sInRpbWVzdGFtcCI6MTc1ODI1NzY3MzEyMX0=" />
|
|||
|
|
</bpmn2:extensionElements>
|
|||
|
|
<bpmn2:multiInstanceLoopCharacteristics
|
|||
|
|
flowable:collection="${flowUtil.stringToList('jeecg')}"
|
|||
|
|
flowable:elementVariable="assigneeUserId">
|
|||
|
|
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
|
|||
|
|
${nrOfCompletedInstances/nrOfInstances>=0.2}
|
|||
|
|
</bpmn2:completionCondition>
|
|||
|
|
</bpmn2:multiInstanceLoopCharacteristics>
|
|||
|
|
</bpmn2:userTask>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 9. 完整会签示例 — 岗位会签(顺序,比例20%)
|
|||
|
|
|
|||
|
|
来自生产环境的岗位(candidatePosts)顺序会签示例,使用 `flowUtil.getAssigneeUsers` 动态获取审批人:
|
|||
|
|
|
|||
|
|
**taskCountersignExtendJson Base64 解码后:**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"countersignProportion": "0.2",
|
|||
|
|
"auditorUserType": "candidatePosts",
|
|||
|
|
"auditorPostIds": ["2032387176954642433", "1958471111989067778"],
|
|||
|
|
"timestamp": 1773418938149
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**getAssigneeUsers 参数 Base64 解码后(不含 timestamp 和 countersignProportion):**
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"auditorUserType": "candidatePosts",
|
|||
|
|
"auditorPostIds": ["2032387176954642433", "1958471111989067778"]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<bpmn2:userTask id="task_dept_mgr" name="部门经理审批"
|
|||
|
|
flowable:assignee="${assigneeUserId}"
|
|||
|
|
flowable:countersignRule="countersign_proportion">
|
|||
|
|
<bpmn2:extensionElements>
|
|||
|
|
<flowable:taskExtendJson value="{"sameMode":0,"isSkipAssigneeEmpty":false,"isSkipAssigneeOnePersion":false,"isSkipApproval":false,"isAssignedByPreviousNode":false,"isEmptyAssignedByPreviousNode":false,"isSkipApprovedOnCountersignReturn":false}" />
|
|||
|
|
<flowable:taskListener class="org.jeecg.modules.extbpm.listener.task.TaskSkipApprovalListener" event="create" />
|
|||
|
|
<flowable:taskCountersignExtendJson value="eyJjb3VudGVyc2lnblByb3BvcnRpb24iOiIwLjIiLCJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVQb3N0cyIsImF1ZGl0b3JQb3N0SWRzIjpbIjIwMzIzODcxNzY5NTQ2NDI0MzMiLCIxOTU4NDcxMTExOTg5MDY3Nzc4Il0sInRpbWVzdGFtcCI6MTc3MzQxODkzODE0OX0=" />
|
|||
|
|
</bpmn2:extensionElements>
|
|||
|
|
<bpmn2:multiInstanceLoopCharacteristics isSequential="true"
|
|||
|
|
flowable:collection="${flowUtil.getAssigneeUsers(execution,'eyJhdWRpdG9yVXNlclR5cGUiOiJjYW5kaWRhdGVQb3N0cyIsImF1ZGl0b3JQb3N0SWRzIjpbIjIwMzIzODcxNzY5NTQ2NDI0MzMiLCIxOTU4NDcxMTExOTg5MDY3Nzc4Il19')}"
|
|||
|
|
flowable:elementVariable="assigneeUserId" />
|
|||
|
|
</bpmn2:userTask>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键差异:**
|
|||
|
|
- `isSequential="true"` — 顺序逐个审批
|
|||
|
|
- 没有 `completionCondition` — 当 countersignRule 为 proportion 但未写 completionCondition 时,系统通过 countersignProportion 属性和 taskCountersignExtendJson 中的 countersignProportion 值自动处理
|
|||
|
|
- `flowUtil.getAssigneeUsers(execution, 'BASE64')` — 运行时根据岗位ID动态获取用户列表
|