完成票签功能支持

This commit is contained in:
hubin 2023-11-07 12:07:14 +08:00
parent 7287f379a8
commit 095b95b0c7
8 changed files with 76 additions and 30 deletions

View File

@ -28,7 +28,7 @@ FlowLong🐉飞龙工作流
| 顺序会签 | 指同一个审批节点设置多个人如A、B、C三人三人按顺序依次收到待办即A先审批A提交后B才能审批需全部同意之后审批才可到下一审批节点。 | ✅ | | 顺序会签 | 指同一个审批节点设置多个人如A、B、C三人三人按顺序依次收到待办即A先审批A提交后B才能审批需全部同意之后审批才可到下一审批节点。 | ✅ |
| 并行会签 | 指同一个审批节点设置多个人如A、B、C三人三人会同时收到待办任务需全部同意之后审批才可到下一审批节点。 | ✅ | | 并行会签 | 指同一个审批节点设置多个人如A、B、C三人三人会同时收到待办任务需全部同意之后审批才可到下一审批节点。 | ✅ |
| 或签 | 一个流程审批节点里有多个处理人,任意一个人处理后就能进入下一个节点 | ✅ | | 或签 | 一个流程审批节点里有多个处理人,任意一个人处理后就能进入下一个节点 | ✅ |
| 票签 | 指同一个审批节点设置多个人如A、B、C三人分别定义不同的权重当投票权重比例大于 50% 就能进入下一个节点 | ◻️ | | 票签 | 指同一个审批节点设置多个人如A、B、C三人分别定义不同的权重当投票权重比例大于 50% 就能进入下一个节点 | |
| 抄送 | 将审批结果通知给抄送列表对应的人 | ✅ | | 抄送 | 将审批结果通知给抄送列表对应的人 | ✅ |
| 驳回 | 将审批重置发送给某节点,重新审批。驳回也叫退回,也可以分退回申请人、退回上一步、任意退回等 | ✅ | | 驳回 | 将审批重置发送给某节点,重新审批。驳回也叫退回,也可以分退回申请人、退回上一步、任意退回等 | ✅ |
| 分配 | 允许用户自行决定任务转办、委派、主办 及其它 | ✅ | | 分配 | 允许用户自行决定任务转办、委派、主办 及其它 | ✅ |

View File

@ -1,4 +1,4 @@
/* /*
* Copyright 2023-2025 Licensed under the AGPL License * Copyright 2023-2025 Licensed under the AGPL License
*/ */
package com.flowlong.bpm.engine; package com.flowlong.bpm.engine;
@ -43,6 +43,14 @@ public interface TaskService {
return this.complete(taskId, flowCreator, null); return this.complete(taskId, flowCreator, null);
} }
/**
* 完成指定实例ID活动任务
*
* @param instanceId 实例ID
* @return
*/
boolean completeActiveTasksByInstanceId(Long instanceId, FlowCreator flowCreator);
/** /**
* 更新任务对象 * 更新任务对象
* *

View File

@ -168,14 +168,18 @@ public class FlowLongEngineImpl implements FlowLongEngine {
* 票签 总权重大于 50% 表示通过 * 票签 总权重大于 50% 表示通过
*/ */
if (performType == PerformType.voteSign) { if (performType == PerformType.voteSign) {
List<FlwTaskActor> flwTaskActors = queryService().getTaskActorsByTaskId(flwTask.getId()); Optional<List<FlwTaskActor>> flwTaskActorsOptional = queryService().getActiveTaskActorsByInstanceId(flwInstance.getId());
if (ObjectUtils.isNotEmpty(flwTaskActors)) { if (flwTaskActorsOptional.isPresent()) {
NodeModel nodeModel = process.getProcessModel().getNode(flwTask.getTaskName()); NodeModel nodeModel = process.getProcessModel().getNode(flwTask.getTaskName());
int passWeight = nodeModel.getPassWeight() == null ? 50 : nodeModel.getPassWeight(); int passWeight = nodeModel.getPassWeight() == null ? 50 : nodeModel.getPassWeight();
int votedWeight = 100 - flwTaskActors.stream().mapToInt(t -> t.getWeight() == null ? 0 : t.getWeight()).sum(); int votedWeight = 100 - flwTaskActorsOptional.get().stream().mapToInt(t -> t.getWeight() == null ? 0 : t.getWeight()).sum();
if (votedWeight < passWeight) { if (votedWeight < passWeight) {
// 投票权重小于节点权重继续投票 // 投票权重小于节点权重继续投票
return; return;
} else {
// 投票完成关闭投票状态进入下一个节点
Assert.isFalse(taskService().completeActiveTasksByInstanceId(flwInstance.getId(), flowCreator),
"Failed to close voting status");
} }
} }
} }

View File

@ -30,6 +30,7 @@ public class FlwHisTaskActor extends FlwTaskActor {
hisTaskActor.actorType = taskActor.getActorType(); hisTaskActor.actorType = taskActor.getActorType();
hisTaskActor.actorId = taskActor.getActorId(); hisTaskActor.actorId = taskActor.getActorId();
hisTaskActor.actorName = taskActor.getActorName(); hisTaskActor.actorName = taskActor.getActorName();
hisTaskActor.weight = taskActor.getWeight();
return hisTaskActor; return hisTaskActor;
} }
} }

View File

@ -55,7 +55,7 @@ public class FlwTaskActor implements Serializable {
/** /**
* 票签权重 * 票签权重
*/ */
private Integer weight; protected Integer weight;
public static FlwTaskActor ofUser(String actorId, String actorName) { public static FlwTaskActor ofUser(String actorId, String actorName) {
return of(actorId, actorName, 0, null); return of(actorId, actorName, 0, null);

View File

@ -1,4 +1,4 @@
/* /*
* Copyright 2023-2025 Licensed under the AGPL License * Copyright 2023-2025 Licensed under the AGPL License
*/ */
package com.flowlong.bpm.mybatisplus.service; package com.flowlong.bpm.mybatisplus.service;
@ -87,31 +87,44 @@ public class TaskServiceImpl implements TaskService {
flwTask.setVariable(args); flwTask.setVariable(args);
Assert.isFalse(isAllowed(flwTask, flowCreator.getCreateId()), "当前参与者 [" + flowCreator.getCreateBy() + "]不允许执行任务[taskId=" + taskId + "]"); Assert.isFalse(isAllowed(flwTask, flowCreator.getCreateId()), "当前参与者 [" + flowCreator.getCreateBy() + "]不允许执行任务[taskId=" + taskId + "]");
// 迁移 task 信息到 flw_his_task // 迁移任务至历史表
FlwHisTask hisTask = FlwHisTask.of(flwTask); this.moveToHisTask(flwTask, taskState, flowCreator);
hisTask.setFinishTime(DateUtils.getCurrentDate());
hisTask.setTaskState(taskState);
hisTask.setCreateId(flowCreator.getCreateId());
hisTask.setCreateBy(flowCreator.getCreateBy());
hisTaskMapper.insert(hisTask);
// 迁移任务参与者
List<FlwTaskActor> taskActors = taskActorMapper.selectListByTaskId(taskId);
if (ObjectUtils.isNotEmpty(taskActors)) {
// task 参与者信息迁移到 flw_his_task_actor
taskActors.forEach(t -> hisTaskActorMapper.insert(FlwHisTaskActor.of(t)));
// 移除 flw_task_actor task 参与者信息
taskActorMapper.deleteByTaskId(taskId);
}
// 删除 flw_task 中指定 task 信息
taskMapper.deleteById(taskId);
// 任务监听器通知 // 任务监听器通知
this.taskNotify(eventType, flwTask); this.taskNotify(eventType, flwTask);
return flwTask; return flwTask;
} }
/**
* 迁移任务至历史表
*
* @param flwTask 执行任务
* @param taskState 任务状态
* @param flowCreator 任务创建者
* @return
*/
protected boolean moveToHisTask(FlwTask flwTask, TaskState taskState, FlowCreator flowCreator) {
// 迁移 task 信息到 flw_his_task
FlwHisTask hisTask = FlwHisTask.of(flwTask);
hisTask.setFinishTime(DateUtils.getCurrentDate());
hisTask.setTaskState(taskState);
hisTask.setCreateId(flowCreator.getCreateId());
hisTask.setCreateBy(flowCreator.getCreateBy());
Assert.isFalse(hisTaskMapper.insert(hisTask) > 0, "Migration to FlwHisTask table failed");
// 迁移任务参与者
List<FlwTaskActor> taskActors = taskActorMapper.selectListByTaskId(flwTask.getId());
if (ObjectUtils.isNotEmpty(taskActors)) {
// task 参与者信息迁移到 flw_his_task_actor
taskActors.forEach(t -> Assert.isFalse(hisTaskActorMapper.insert(FlwHisTaskActor.of(t)) > 0,
"Migration to FlwHisTaskActor table failed"));
// 移除 flw_task_actor task 参与者信息
Assert.isFalse(taskActorMapper.deleteByTaskId(flwTask.getId()), "Delete FlwTaskActor table failed");
}
// 删除 flw_task 中指定 task 信息
return taskMapper.deleteById(flwTask.getId()) > 0;
}
protected void taskNotify(EventType eventType, FlwTask flwTask) { protected void taskNotify(EventType eventType, FlwTask flwTask) {
if (null != taskListener) { if (null != taskListener) {
@ -119,6 +132,26 @@ public class TaskServiceImpl implements TaskService {
} }
} }
/**
* 完成指定实例ID活动任务
*
* @param instanceId 实例ID
* @return
*/
@Override
public boolean completeActiveTasksByInstanceId(Long instanceId, FlowCreator flowCreator) {
List<FlwTask> flwTasks = taskMapper.selectList(Wrappers.<FlwTask>lambdaQuery().eq(FlwTask::getInstanceId, instanceId));
if (ObjectUtils.isNotEmpty(flwTasks)) {
for (FlwTask flwTask : flwTasks) {
// 迁移任务至历史表设置任务状态为终止
if (!this.moveToHisTask(flwTask, TaskState.termination, flowCreator)) {
return false;
}
}
}
return true;
}
/** /**
* 更新任务对象的finish_TimecreateByexpire_Timeversionvariable * 更新任务对象的finish_TimecreateByexpire_Timeversionvariable
* *
@ -134,7 +167,7 @@ public class TaskServiceImpl implements TaskService {
/** /**
* 查看任务设置为已阅状态 * 查看任务设置为已阅状态
* *
* @param taskId 任务ID * @param taskId 任务ID
* @param taskActor 任务参与者 * @param taskActor 任务参与者
* @return * @return
*/ */

View File

@ -33,10 +33,10 @@ public class TestVoteSign extends MysqlTest {
this.executeActiveTasks(instance.getId(), testCreator); this.executeActiveTasks(instance.getId(), testCreator);
// test1 领导审批同意 // test1 领导审批同意
this.executeActiveTasks(instance.getId(), FlowCreator.of(testUser1, "青苗")); this.executeTask(instance.getId(), FlowCreator.of(testUser1, "青苗"));
// test3 领导审批同意 // test3 领导审批同意
this.executeActiveTasks(instance.getId(), FlowCreator.of(testUser3, "聂秋秋")); this.executeTask(instance.getId(), FlowCreator.of(testUser3, "聂秋秋"));
// test2 不在执行达到票签值 // test2 不在执行达到票签值
// 抄送人力资源流程自动结束 // 抄送人力资源流程自动结束

View File

@ -1,6 +1,6 @@
{ {
"id": 1, "id": 1,
"name": "报销审批(或签)", "name": "人事审批(票签)",
"nodeConfig": { "nodeConfig": {
"nodeName": "发起人", "nodeName": "发起人",
"type": 0, "type": 0,