فهرست منبع

feat: 【工作流】--减签

kehaiyou 1 سال پیش
والد
کامیت
80d532b0f1

+ 1 - 0
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java

@@ -53,6 +53,7 @@ public interface ErrorCodeConstants {
     ErrorCode TASK_ADD_SIGN_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在");
     ErrorCode TASK_ADD_SIGN_TYPE_ERROR = new ErrorCode(1_009_005_010, "任务加签:当前任务已经{},不能{}");
     ErrorCode TASK_ADD_SIGN_USER_REPEAT = new ErrorCode(1_009_005_011, "任务加签失败,加签人与现有审批人[{}]重复");
+    ErrorCode TASK_SUB_SIGN_NO_PARENT = new ErrorCode(1_009_005_011, "任务减签失败,被减签的任务必须是通过加签生成的任务");
     // ========== 流程任务分配规则 1-009-006-000 ==========
     ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1_009_006_000, "流程({}) 的任务({}) 已经存在分配规则");
     ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1_009_006_001, "流程任务分配规则不存在");

+ 16 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java

@@ -103,8 +103,23 @@ public class BpmTaskController {
     @PutMapping("/add-sign")
     @Operation(summary = "加签", description = "before, after为前加签后加签")
     @PreAuthorize("@ss.hasPermission('bpm:task:add-sign')")
-    public CommonResult<Boolean> addSign(@RequestBody @Valid BpmTaskAddSignReqVO reqVO) {
+    public CommonResult<Boolean> addSign(@Valid @RequestBody BpmTaskAddSignReqVO reqVO) {
         taskService.addSign(reqVO,getLoginUserId());
         return success(true);
     }
+
+    @PutMapping("/sub-sign")
+    @Operation(summary = "减签", description = "")
+    @PreAuthorize("@ss.hasPermission('bpm:task:sub-sign')")
+    public CommonResult<Boolean> subSign(@Valid @RequestBody  BpmTaskSubSignReqVO bpmTaskSubSignReqVO) {
+        taskService.subSign(bpmTaskSubSignReqVO,getLoginUserId());
+        return success(true);
+    }
+    @GetMapping("/get-sub-sign")
+    @Operation(summary = "获取能被减签的任务", description = "")
+    @PreAuthorize("@ss.hasPermission('bpm:task:sub-sign')")
+    public CommonResult<List<BpmTaskSubSignRespVO>> getChildrenTaskList(@RequestParam("taskId") String taskId) {
+        return success(taskService.getChildrenTaskList(taskId));
+    }
+
 }

+ 5 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java

@@ -19,6 +19,11 @@ public class BpmTaskRespVO extends BpmTaskDonePageItemRespVO {
      */
     private User assigneeUser;
 
+    /**
+     * 父任务ID
+     */
+    private String parentTaskId;
+
     @Schema(description = "用户信息")
     @Data
     public static class User {

+ 19 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - 减签流程任务的 Request VO")
+@Data
+public class BpmTaskSubSignReqVO {
+
+    @Schema(description = "被减签的任务 ID")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "加签原因")
+    @NotEmpty(message = "加签原因不能为空")
+    private String reason;
+}

+ 15 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignRespVO.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 减签流程任务的 Response VO")
+@Data
+public class BpmTaskSubSignRespVO {
+    @Schema(description = "审核的用户信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "小李")
+    private BpmTaskRespVO.User assigneeUser;
+    @Schema(description = "任务 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "12312")
+    private String id;
+    @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批")
+    private String name;
+}

+ 14 - 4
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java

@@ -4,10 +4,7 @@ import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
-import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO;
-import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
-import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskSimpleRespVO;
-import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@@ -162,4 +159,17 @@ public interface BpmTaskConvert {
         task.setCreateTime(new Date());
         return task;
     }
+
+    default List<BpmTaskSubSignRespVO> convertList(List<BpmTaskExtDO> bpmTaskExtDOList, Map<Long, AdminUserRespDTO> userMap){
+        return CollectionUtils.convertList(bpmTaskExtDOList, task->{
+            BpmTaskSubSignRespVO bpmTaskSubSignRespVO = new BpmTaskSubSignRespVO();
+            bpmTaskSubSignRespVO.setName(task.getName());
+            bpmTaskSubSignRespVO.setId(task.getTaskId());
+            AdminUserRespDTO assignUser = userMap.get(task.getAssigneeUserId());
+            if (assignUser != null) {
+                bpmTaskSubSignRespVO.setAssigneeUser(convert3(assignUser));
+            }
+            return bpmTaskSubSignRespVO;
+        });
+    }
 }

+ 13 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java

@@ -163,4 +163,17 @@ public interface BpmTaskService {
      */
     void addSign(BpmTaskAddSignReqVO reqVO, Long userId);
 
+    /**
+     * 减签
+     * @param bpmTaskSubSignReqVO 被减签的任务ID,理由
+     * @param loginUserId 当前用户ID
+     */
+    void subSign(BpmTaskSubSignReqVO bpmTaskSubSignReqVO, Long loginUserId);
+
+    /**
+     * 获取指定任务的子任务和审批人信息
+     * @param taskId 指定任务ID
+     * @return 子任务列表
+     */
+    List<BpmTaskSubSignRespVO> getChildrenTaskList(String taskId);
 }

+ 109 - 14
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
 import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
 import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
+import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.bpm.enums.task.*;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
@@ -264,7 +265,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
             long subTaskCount = getSubTaskCount(parentTaskId);
             if (subTaskCount == 0) {
                 //2. 获取父任务
-                Task parentTask = getTask(parentTaskId);
+                Task parentTask = validateTaskExist(parentTaskId);
                 String scopeType = parentTask.getScopeType();
                 //3. 处理向前加签
                 if (BpmTaskAddSignTypeEnum.BEFORE.getType().equals(scopeType)) {
@@ -319,7 +320,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
             //此时,D 任务完成,要一直往上找到祖先任务 A ,调用 complete 方法完成 A 任务
             boolean allChildrenTaskFinish = true;
             while (StrUtil.isNotBlank(parentTask.getParentTaskId())) {
-                parentTask = getTask(parentTask.getParentTaskId());
+                parentTask = validateTaskExist(parentTask.getParentTaskId());
                 BpmTaskExtDO bpmTaskExtDO = taskExtMapper.selectByTaskId(parentTask.getId());
                 if (bpmTaskExtDO == null) {
                     break;
@@ -375,7 +376,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         String comment = StrUtil.format("[{}]完成委派任务,任务重新回到[{}]手中,审批意见为:{}", currentUser.getNickname(),
                 sourceApproveUser.getNickname(), reqVO.getReason());
         taskService.addComment(reqVO.getId(), task.getProcessInstanceId(),
-                BpmCommentTypeEnum.DELEGATE.getDesc(), comment);
+                BpmCommentTypeEnum.DELEGATE.getResult().toString(), comment);
 
         // 2.1 调用 resolveTask 完成任务。
         // 底层调用 TaskHelper.changeTaskAssignee(task, task.getOwner()):将 owner 设置为 assignee
@@ -425,10 +426,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
      * @param taskId task id
      */
     private Task validateTask(Long userId, String taskId) {
-        Task task = getTask(taskId);
-        if (task == null) {
-            throw exception(TASK_NOT_EXISTS);
-        }
+        Task task = validateTaskExist(taskId);
         if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) {
             throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
         }
@@ -507,6 +505,14 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         });
     }
 
+    private Task validateTaskExist(String id){
+        Task task = taskService.createTaskQuery().taskId(id).singleResult();
+        if(task == null){
+            throw exception(TASK_NOT_EXISTS);
+        }
+        return task;
+    }
+
     private Task getTask(String id) {
         return taskService.createTaskQuery().taskId(id).singleResult();
     }
@@ -518,10 +524,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
     @Override
     public List<BpmTaskSimpleRespVO> getReturnTaskList(String taskId) {
         // 1. 校验当前任务 task 存在
-        Task task = getTask(taskId);
-        if (task == null) {
-            throw exception(TASK_NOT_EXISTS);
-        }
+        Task task = validateTaskExist(taskId);
         // 根据流程定义获取流程模型信息
         BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
         FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
@@ -609,7 +612,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
                 return;
             }
             taskService.addComment(task.getId(), currentTask.getProcessInstanceId(),
-                    BpmCommentTypeEnum.BACK.getDesc(), reqVO.getReason());
+                    BpmCommentTypeEnum.BACK.getResult().toString(), reqVO.getReason());
         });
 
         // 3. 执行驳回
@@ -637,7 +640,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
                 delegateUser.getNickname(), reqVO.getReason());
         String taskId = reqVO.getId();
         taskService.addComment(taskId, task.getProcessInstanceId(),
-                BpmCommentTypeEnum.DELEGATE.getDesc(), comment);
+                BpmCommentTypeEnum.DELEGATE.getResult().toString(), comment);
 
         // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
         taskService.setOwner(taskId, task.getAssignee());
@@ -702,7 +705,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         String comment = StrUtil.format("[{}]{}给了[{}],理由为:{}", currentUser.getNickname(), BpmTaskAddSignTypeEnum.formatDesc(reqVO.getType()),
                 String.join(SymbolConstant.D, convertList(userList, AdminUserRespDTO::getNickname)), reqVO.getReason());
         taskService.addComment(reqVO.getId(), taskEntity.getProcessInstanceId(),
-                BpmCommentTypeEnum.ADD_SIGN.getDesc(), comment);
+                BpmCommentTypeEnum.ADD_SIGN.getResult().toString(), comment);
     }
 
 
@@ -776,6 +779,86 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         taskService.saveTask(task);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void subSign(BpmTaskSubSignReqVO reqVO,Long userId) {
+        Task task = validateSubSign(reqVO.getId());
+        AdminUserRespDTO user = adminUserApi.getUser(userId);
+        AdminUserRespDTO cancelUser = null;
+        if(StrUtil.isNotBlank(task.getAssignee())){
+            cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee()));
+        }
+        if(cancelUser == null && StrUtil.isNotBlank(task.getOwner())){
+            cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner()));
+        }
+        Assert.notNull(cancelUser,"任务中没有所有者和审批人,数据错误");
+        //1. 获取所有需要删除的任务 ID ,包含当前任务和所有子任务
+        List<String> allTaskIdList = getAllChildTaskIds(task.getId());
+        //2. 删除任务和所有子任务
+        taskService.deleteTasks(allTaskIdList);
+        //3. 修改扩展表状态为取消
+        taskExtMapper.updateBatchByTaskIdList(allTaskIdList,new BpmTaskExtDO().setResult(BpmProcessInstanceResultEnum.CANCEL.getResult())
+                .setReason(StrUtil.format("由于{}操作[减签],任务被取消",user.getNickname())));
+        //4. 判断当前任务的父任务是否还有子任务
+        Task parentTask = validateTaskExist(task.getParentTaskId());
+        Long subTaskCount = getSubTaskCount(task.getParentTaskId());
+        if(subTaskCount == 0){
+            if(BpmTaskAddSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())){
+                //4.1 父任务是往前加签的,则进入判断,将当前任务的状态设置为进行中,并将 owner 设置回 assignee
+                taskExtMapper.updateByTaskId(new BpmTaskExtDO().setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()).setTaskId(task.getParentTaskId()));
+                parentTask.setAssignee(parentTask.getOwner());
+                parentTask.setOwner(null);
+            }
+            //4.2 清空 scopeType 字段,修改task
+            clearTaskScopeTypeAndSave(parentTask);
+        }
+        //5.记录日志到父任务中
+        String comment = StrUtil.format("{}操作了【减签】,审批人{}的任务被取消",user.getNickname(),cancelUser.getNickname());
+        taskService.addComment(parentTask.getId(),parentTask.getProcessInstanceId(),
+                BpmCommentTypeEnum.SUB_SIGN.getResult().toString(),comment);
+    }
+
+    /**
+     * 校验任务是否能被减签
+     * @param id 任务ID
+     * @return 任务信息
+     */
+    private Task validateSubSign(String id) {
+        Task task = validateTaskExist(id);
+        //必须有parentId
+        if(StrUtil.isEmpty(task.getParentTaskId())){
+            throw exception(TASK_SUB_SIGN_NO_PARENT);
+        }
+        return task;
+    }
+
+    /**
+     * 获取所有要被取消的删除的任务 ID 集合
+     * @param parentTaskId 父级任务ID
+     * @return 所有任务ID
+     */
+    public List<String> getAllChildTaskIds(String parentTaskId) {
+        List<String> allChildTaskIds = new ArrayList<>();
+        //1. 先将自己放入
+        allChildTaskIds.add(parentTaskId);
+        //2. 递归获取子级
+        recursiveGetChildTaskIds(parentTaskId, allChildTaskIds);
+        return allChildTaskIds;
+    }
+
+    /**
+     * 递归处理子级任务
+     * @param taskId 当前任务ID
+     * @param taskIds 结果
+     */
+    private void recursiveGetChildTaskIds(String taskId, List<String> taskIds) {
+        List<String> childrenTaskIdList = getChildrenTaskIdList(taskId);
+        for (String childTaskId : childrenTaskIdList) {
+            taskIds.add(childTaskId); // 将子任务的ID添加到集合中
+            recursiveGetChildTaskIds(childTaskId, taskIds); // 递归获取子任务的子任务
+        }
+    }
+
     /**
      * 获取指定父级任务的所有子任务 ID 集合
      * @param parentTaskId 父任务 ID
@@ -787,4 +870,16 @@ public class BpmTaskServiceImpl implements BpmTaskService {
         List<Task> childrenTaskList = taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list();
         return convertList(childrenTaskList, Task::getId);
     }
+
+    @Override
+    public List<BpmTaskSubSignRespVO> getChildrenTaskList(String taskId){
+        List<String> childrenTaskIdList = getChildrenTaskIdList(taskId);
+        if(CollUtil.isEmpty(childrenTaskIdList)){
+            return Collections.emptyList();
+        }
+        List<BpmTaskExtDO> bpmTaskExtDOList = taskExtMapper.selectListByTaskIds(childrenTaskIdList);
+        Set<Long> assigneeUserIdSet = convertSet(bpmTaskExtDOList, BpmTaskExtDO::getAssigneeUserId);
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIdSet);
+        return BpmTaskConvert.INSTANCE.convertList(bpmTaskExtDOList,userMap);
+    }
 }