审核流程撤回时同步更新oa待办状态,已办--》待办。

This commit is contained in:
2026-11-13 18:18:24 +08:00
parent ec70ceefd5
commit b9b52f0af9
4 changed files with 144 additions and 34 deletions
@@ -8,17 +8,13 @@ import com.budwk.app.base.result.Result;
import com.budwk.app.base.vo.LabelValueVO; import com.budwk.app.base.vo.LabelValueVO;
import com.budwk.app.flow.constant.FlowConst; import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine; import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessPublisher;
import com.budwk.app.flow.engine.model.ProcessModel; import com.budwk.app.flow.engine.model.ProcessModel;
import com.budwk.app.flow.engine.model.TaskModel; import com.budwk.app.flow.engine.model.TaskModel;
import com.budwk.app.flow.entity.Candidate; import com.budwk.app.flow.entity.Candidate;
import com.budwk.app.flow.entity.ProcessDefine; import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance; import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask; import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum; import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessTaskPerformTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum; import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.FlowCommonService; import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.flow.vo.HighLightVO; import com.budwk.app.flow.vo.HighLightVO;
@@ -248,37 +244,11 @@ public class FlowCommonController {
@Aop(TransAop.READ_COMMITTED) @Aop(TransAop.READ_COMMITTED)
@ApiOperation("撤销任务") @ApiOperation("撤销任务")
public Result revokeTask(@Param("taskId") Long taskId) { public Result revokeTask(@Param("taskId") Long taskId) {
// 自己任务 if (taskId == null) {
ProcessTask selfTask = dao.fetch(ProcessTask.class, taskId); return Result.error("任务ID不能为空");
selfTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
// 撤销任务
List<ProcessTask> taskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskParentId, "=", taskId));
for (ProcessTask task : taskList) {
task.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
dao.update(task);
} }
// 会签并行任务 撤销后续任务 // taskId 为原已完成任务 ID;服务同步 OA 后返回 Result 成功结果。
if (selfTask.getPerformType().equals(ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode())) { flowCommonService.revokeTask(taskId);
List<ProcessTask> doingTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode())
.and(ProcessTask::getProcessInstanceId, "=", selfTask.getProcessInstanceId()));
for (ProcessTask doingTask : doingTasks) {
doingTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
dao.update(doingTask);
}
}
// 激活
dao.update(selfTask);
// 流程激活
dao.update(ProcessInstance.class, Chain.make("state", ProcessInstanceStateEnum.DOING.getCode()),Cnd.where(ProcessInstance::getId, "=", selfTask.getProcessInstanceId()));
// 发送任务撤回事件 确保上面执行成功
for (ProcessTask task : taskList) {
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_REVOKE).sourceId(task.getId()).build());
}
return Result.success(); return Result.success();
} }
@@ -1,5 +1,18 @@
package com.budwk.app.flow.service; package com.budwk.app.flow.service;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.flow.engine.event.ProcessEvent;
import com.budwk.app.flow.engine.event.ProcessPublisher;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessTaskPerformTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import org.nutz.dao.Dao;
import org.nutz.dao.Cnd;
import org.nutz.dao.Chain;
import java.util.List;
import cn.hutool.core.lang.Dict; import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.budwk.app.base.event.flow.jumptofirsttask.FlowJumpToFirstTaskNodePublisher; import com.budwk.app.base.event.flow.jumptofirsttask.FlowJumpToFirstTaskNodePublisher;
@@ -17,6 +30,10 @@ public class FlowCommonService {
@Inject @Inject
private FlowEngine flowEngine; private FlowEngine flowEngine;
@Inject
private Dao dao;
@Inject
private SchoolOaTodoService schoolOaTodoService;
/** /**
* 执行 WF 任务,任务状态、业务监听器和后续任务写入共用本地事务;OA 异常向外抛出触发回滚。 * 执行 WF 任务,任务状态、业务监听器和后续任务写入共用本地事务;OA 异常向外抛出触发回滚。
@@ -75,4 +92,55 @@ public class FlowCommonService {
} }
} }
/**
* 撤回已完成的 WF 任务,恢复原 OA 待办并清理被撤销的后续待办。
* @param taskId 原已完成任务 ID;沿用原任务及参与人,保持 OA uniqueId 不变
* 返回 void;失败抛出异常回滚本地事务,外部 OA 请求不参与数据库事务。
*/
@Aop(TransAop.READ_COMMITTED)
public void revokeTask(Long taskId) {
// 自己任务
ProcessTask selfTask = dao.fetch(ProcessTask.class, taskId);
if (selfTask == null) {
throw new BaseException("撤回失败:任务不存在");
}
// 防止重复撤回再次删除后续 OA 待办;原任务必须是已完成状态。
if (!ProcessTaskStateEnum.FINISHED.getCode().equals(selfTask.getTaskState())) {
throw new BaseException("撤回失败:任务不是已完成状态,请刷新后重试");
}
schoolOaTodoService.restoreTodo(selfTask);
// 撤销任务
List<ProcessTask> taskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskParentId, "=", taskId));
for (ProcessTask task : taskList) {
// 在本地状态改变前删除 OA 待办,保留 deleteTodo 对历史任务的保护。
schoolOaTodoService.deleteTodo(task);
task.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
dao.update(task);
}
// 会签并行任务 撤销后续任务
if (selfTask.getPerformType().equals(ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode())) {
List<ProcessTask> doingTasks = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode())
.and(ProcessTask::getProcessInstanceId, "=", selfTask.getProcessInstanceId()));
for (ProcessTask doingTask : doingTasks) {
schoolOaTodoService.deleteTodo(doingTask);
doingTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
dao.update(doingTask);
}
}
// 激活
selfTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
dao.update(selfTask);
// 流程激活
dao.update(ProcessInstance.class, Chain.make("state", ProcessInstanceStateEnum.DOING.getCode()),Cnd.where(ProcessInstance::getId, "=", selfTask.getProcessInstanceId()));
// 发送任务撤回事件 确保上面执行成功
for (ProcessTask task : taskList) {
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_REVOKE).sourceId(task.getId()).build());
}
}
} }
@@ -22,6 +22,13 @@ public interface SchoolOaTodoService {
*/ */
void completeTodo(ProcessTask processTask); void completeTodo(ProcessTask processTask);
/**
* 审核撤回时恢复原任务的 OA 待办,保留原任务和参与人组成的 uniqueId。
* @param processTask 需要重新办理的原审核任务,包含任务及实例 ID、节点和表单地址
* 返回 void;OA 返回非成功状态时抛出异常。
*/
void restoreTodo(ProcessTask processTask);
/** /**
* 根据本系统流程任务删除学校 OA 单条待办。 * 根据本系统流程任务删除学校 OA 单条待办。
* *
@@ -158,6 +158,71 @@ public class SchoolOaTodoServiceImpl implements SchoolOaTodoService {
} }
} }
/**
* 使用修改信息接口把原已办恢复为待办;不能重复创建同一个 uniqueId。
* @param processTask 被撤回的原审核任务,按实例读取发起人、按任务读取全部接收人
* 返回 void;state=200 表示恢复成功,其他状态保持失败并向外抛出。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void restoreTodo(ProcessTask processTask) {
if (processTask == null || processTask.getId() == null) {
throw new BaseException("学校OA待办恢复失败:流程任务为空");
}
if (!isSchoolOaEnabled("恢复待办", processTask.getId(), processTask.getProcessInstanceId())) {
return;
}
// 发起节点原本不推送 OA,因此撤回申请时也不恢复该节点的 OA 记录。
if (Long.valueOf(0L).equals(processTask.getTaskParentId()) && "startTask".equals(processTask.getTaskName())) {
return;
}
ProcessInstance instance = dao.fetch(ProcessInstance.class, processTask.getProcessInstanceId());
if (instance == null) {
throw new BaseException("学校OA待办恢复失败:流程实例不存在");
}
ProcessDefine define = dao.fetch(ProcessDefine.class, instance.getProcessDefineId());
Sys_user creator = dao.fetch(Sys_user.class, instance.getOperator());
if (define == null || creator == null || StrUtil.hasBlank(creator.getLoginname(), creator.getUsername())) {
throw new BaseException("学校OA待办恢复失败:流程定义或发起人信息不完整");
}
List<ProcessTaskActor> actors = dao.query(ProcessTaskActor.class,
Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTask.getId()));
if (actors.isEmpty()) {
throw new BaseException("学校OA待办恢复失败:流程任务接收人为空,taskId={}", processTask.getId());
}
for (ProcessTaskActor actor : actors) {
if (skipSuperAdmin(actor, "恢复待办", processTask.getId())) {
continue;
}
if (StrUtil.hasBlank(actor.getActorId(), actor.getActorAccount(), actor.getActorName())) {
throw new BaseException("学校OA待办恢复失败:接收人信息不完整,taskId={}", processTask.getId());
}
String uniqueId = processTask.getId() + "_" + actor.getActorId();
JSONObject body = new JSONObject();
body.set("flowTypeName", getFlowTypeName(define));
body.set("flowId", String.valueOf(instance.getId()));
body.set("title", getInstanceTitle(instance, define));
body.set("stepName", processTask.getDisplayName());
body.set("creatorCode", creator.getLoginname());
body.set("creatorName", creator.getUsername());
body.set("creatorTime", formatTime(instance.getCreatedAt()));
String pcUrl = buildTaskUrl(processTask.getFormKey(), processTask);
String appUrl = buildAppTaskUrl(processTask.getH5FormKey(), processTask);
body.set("pcUrl", pcUrl);
body.set("appUrl", appUrl);
body.set("pcPreviewUrl", pcUrl);
body.set("appPreviewUrl", appUrl);
body.set("uniqueId", uniqueId);
body.set("receiveCode", actor.getActorAccount());
body.set("receiveName", actor.getActorName());
body.set("receiveTime", formatTime(processTask.getCreatedAt()));
body.set("isRemark", "0");
String url = requireConfig("school-oa.todo.update-handle-url").replace("{uniqueId}", uniqueId);
String response = sendTodoRequest(url, body, "学校OA待办恢复失败", "学校OA待办恢复异常");
log.info("学校OA待办恢复成功,taskId={},uniqueId={},response={}", processTask.getId(), uniqueId, response);
}
}
@Override @Override
@Aop(TransAop.READ_COMMITTED) @Aop(TransAop.READ_COMMITTED)
public void deleteFlow(Long flowId) { public void deleteFlow(Long flowId) {