Browse Source

Update 工作流后端代码优化,增加修改流程变量,催办,启动流程等接口

Yue 8 months ago
parent
commit
81aff210aa
29 changed files with 647 additions and 168 deletions
  1. 2 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/TaskAssigneeDTO.java
  2. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/exception/SseException.java
  3. 10 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/WorkflowService.java
  4. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/DateUtils.java
  5. 3 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/ExcelDownHandler.java
  6. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysDictTypeServiceImpl.java
  7. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysPostServiceImpl.java
  8. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysRoleServiceImpl.java
  9. 5 5
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysTaskAssigneeServiceImpl.java
  10. 1 1
      SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysUserServiceImpl.java
  11. 25 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/enums/TaskAssigneeEnum.java
  12. 22 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwInstanceController.java
  13. 17 5
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwTaskController.java
  14. 38 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowUrgeTaskBo.java
  15. 39 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowVariableBo.java
  16. 5 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/TestLeaveBo.java
  17. 23 17
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/listener/WorkflowGlobalListener.java
  18. 14 2
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwCommonService.java
  19. 17 1
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwInstanceService.java
  20. 27 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwTaskService.java
  21. 5 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/ITestLeaveService.java
  22. 15 3
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwCommonServiceImpl.java
  23. 7 8
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwDefinitionServiceImpl.java
  24. 93 14
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwInstanceServiceImpl.java
  25. 4 2
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwSpelServiceImpl.java
  26. 75 49
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskAssigneeServiceImpl.java
  27. 136 47
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskServiceImpl.java
  28. 28 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/TestLeaveServiceImpl.java
  29. 31 0
      SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/WorkflowServiceImpl.java

+ 2 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/TaskAssigneeDTO.java

@@ -1,6 +1,5 @@
 package com.vber.common.core.domain.dto;
 
-import cn.hutool.core.convert.Convert;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
@@ -56,14 +55,14 @@ public class TaskAssigneeDTO implements Serializable {
             Function<T, String> storageId,
             Function<T, String> handlerCode,
             Function<T, String> handlerName,
-            Function<T, Long> groupName,
+            Function<T, String> groupName,
             Function<T, Date> createTimeMapper) {
         return sourceList.stream()
                 .map(item -> new TaskHandler(
                         storageId.apply(item),
                         handlerCode.apply(item),
                         handlerName.apply(item),
-                        groupName != null ? Convert.toStr(groupName.apply(item)) : null,
+                        groupName.apply(item),
                         createTimeMapper.apply(item)
                 )).collect(Collectors.toList());
     }

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/exception/SseException.java

@@ -11,7 +11,7 @@ import java.io.Serial;
 /**
  * sse 特制异常
  *
- * @author LionLi
+ * @author Iwb
  */
 @Data
 @EqualsAndHashCode(callSuper = true)

+ 10 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/WorkflowService.java

@@ -13,7 +13,6 @@ import java.util.Map;
  * @author Iwb
  */
 public interface WorkflowService {
-
     /**
      * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息
      *
@@ -82,6 +81,7 @@ public interface WorkflowService {
      * completeTask.getVariables().put("ignore", true);
      *
      * @param completeTask 参数
+     * @return 结果
      */
     boolean completeTask(CompleteTaskDTO completeTask);
 
@@ -90,6 +90,15 @@ public interface WorkflowService {
      *
      * @param taskId  任务ID
      * @param message 办理意见
+     * @return 结果
      */
     boolean completeTask(Long taskId, String message);
+
+    /**
+     * 启动流程并办理第一个任务
+     *
+     * @param startProcess 参数
+     * @return 结果
+     */
+    boolean startCompleteTask(StartProcessDTO startProcess);
 }

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/DateUtils.java

@@ -293,7 +293,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
 
         // 校验时间跨度不超过最大限制
         if (diff > maxValue) {
-            throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
+            throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase());
         }
     }
 

+ 3 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/ExcelDownHandler.java

@@ -1,6 +1,7 @@
 package com.vber.common.excel.core;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.EnumUtil;
 import cn.hutool.core.util.ObjectUtil;
@@ -103,7 +104,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
                 if (StringUtils.isNotBlank(dictType)) {
                     // 如果传递了字典名,则依据字典建立下拉
                     Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
-                            .orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
+                            .orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
                             .values();
                     options = new ArrayList<>(values);
                 } else if (StringUtils.isNotBlank(converterExp)) {
@@ -115,7 +116,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
                 // 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
                 ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
                 List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
-                options = StreamUtils.toList(values, String::valueOf);
+                options = StreamUtils.toList(values, Convert::toStr);
             }
             if (ObjectUtil.isNotEmpty(options)) {
                 // 仅当下拉可选项不为空时执行

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysDictTypeServiceImpl.java

@@ -144,7 +144,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
             boolean assigned = dictDataMapper.exists(new LambdaQueryWrapper<SysDictData>()
                     .eq(SysDictData::getDictType, x.getDictType()));
             if (assigned) {
-                throw new ServiceException(String.format("%1$s已分配,不能删除", x.getDictName()));
+                throw new ServiceException("{}已分配,不能删除", x.getDictName());
             }
         });
         baseMapper.deleteByIds(dictIds);

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysPostServiceImpl.java

@@ -219,7 +219,7 @@ public class SysPostServiceImpl implements ISysPostService, PostService {
         List<SysPost> list = baseMapper.selectByIds(postIds);
         for (SysPost post : list) {
             if (this.countUserPostById(post.getPostId()) > 0) {
-                throw new ServiceException(String.format("%1$s已分配,不能删除!", post.getPostName()));
+                throw new ServiceException("{}已分配,不能删除!", post.getPostName());
             }
         }
         return baseMapper.deleteByIds(postIds);

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysRoleServiceImpl.java

@@ -433,7 +433,7 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
         for (SysRole role : roles) {
             checkRoleAllowed(BeanUtil.toBean(role, SysRoleBo.class));
             if (countUserRoleByRoleId(role.getRoleId()) > 0) {
-                throw new ServiceException(String.format("%1$s已分配,不能删除!", role.getRoleName()));
+                throw new ServiceException("{}已分配,不能删除!", role.getRoleName());
             }
         }
         // 删除角色与菜单关联

+ 5 - 5
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysTaskAssigneeServiceImpl.java

@@ -59,7 +59,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         TableDataInfo<SysRoleVo> page = roleService.selectPageRoleList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                item -> Convert.toStr(item.getRoleId()), SysRoleVo::getRoleKey, SysRoleVo::getRoleName, null, SysRoleVo::getCreateTime);
+                item -> Convert.toStr(item.getRoleId()), SysRoleVo::getRoleKey, SysRoleVo::getRoleName, item -> "", SysRoleVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 
@@ -83,12 +83,12 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         TableDataInfo<SysPostVo> page = postService.selectPagePostList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                p -> Convert.toStr(p.getPostId()), SysPostVo::getPostCategory, SysPostVo::getPostName, SysPostVo::getOrgId, SysPostVo::getCreateTime);
+                item -> Convert.toStr(item.getPostId()), SysPostVo::getPostCategory, SysPostVo::getPostName, item -> Convert.toStr(item.getOrgId()), SysPostVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 
     /**
-     * 查询组织机构并返回任务指派的列表,支持分页
+     * 查询部门并返回任务指派的列表,支持分页
      *
      * @param taskQuery 查询条件
      * @return 办理人
@@ -107,7 +107,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         TableDataInfo<SysOrgVo> page = orgService.selectPageOrgList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                d -> Convert.toStr(d.getOrgId()), SysOrgVo::getOrgCategory, SysOrgVo::getOrgName, SysOrgVo::getParentId, SysOrgVo::getCreateTime);
+                item -> Convert.toStr(item.getOrgId()), SysOrgVo::getOrgCategory, SysOrgVo::getOrgName, item -> Convert.toStr(item.getParentId()), SysOrgVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 
@@ -131,7 +131,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
         TableDataInfo<SysUserVo> page = userService.selectPageUserList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                u -> Convert.toStr(u.getUserId()), SysUserVo::getUserName, SysUserVo::getNickName, SysUserVo::getOrgId, SysUserVo::getCreateTime);
+                item -> Convert.toStr(item.getUserId()), SysUserVo::getUserName, SysUserVo::getNickName, item -> Convert.toStr(item.getOrgId()), SysUserVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 

+ 1 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-system/src/main/java/com/vber/system/service/impl/SysUserServiceImpl.java

@@ -367,7 +367,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
         // 防止错误更新后导致的数据误删除
         int flag = baseMapper.updateById(sysUser);
         if (flag < 1) {
-            throw new ServiceException("修改用户" + user.getUserName() + "信息失败");
+            throw new ServiceException("修改用户{}信息失败", user.getUserName());
         }
         return flag;
     }

+ 25 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/common/enums/TaskAssigneeEnum.java

@@ -1,6 +1,7 @@
 package com.vber.workflow.common.enums;
 
 import com.vber.common.core.exception.ServiceException;
+import com.vber.common.core.utils.StringUtils;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
@@ -110,5 +111,29 @@ public enum TaskAssigneeEnum {
                 .map(TaskAssigneeEnum::getCode)
                 .collect(Collectors.toList());
     }
+
+    /**
+     * 判断当前办理人类型是否需要调用部门服务(deptService)
+     *
+     * @return 如果类型是 USER、ORG 或 POST,则返回 true;否则返回 false
+     */
+    public boolean needsOrgService() {
+        return this == USER || this == ORG || this == POST;
+    }
+
+    /**
+     * 判断给定字符串是否符合 SPEL 表达式格式(以 $ 或 # 开头)
+     *
+     * @param value 待判断字符串
+     * @return 是否为 SPEL 表达式
+     */
+    public static boolean isSpelExpression(String value) {
+        if (value == null) {
+            return false;
+        }
+        // $前缀表示默认办理人变量策略
+        // #前缀表示spel办理人变量策略
+        return StringUtils.startsWith(value, "$") || StringUtils.startsWith(value, "#");
+    }
 }
 

+ 22 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwInstanceController.java

@@ -11,6 +11,7 @@ import com.vber.workflow.common.ConditionalOnEnable;
 import com.vber.workflow.domain.bo.FlowCancelBo;
 import com.vber.workflow.domain.bo.FlowInstanceBo;
 import com.vber.workflow.domain.bo.FlowInvalidBo;
+import com.vber.workflow.domain.bo.FlowVariableBo;
 import com.vber.workflow.domain.vo.FlowInstanceVo;
 import com.vber.workflow.service.IFlwInstanceService;
 import lombok.RequiredArgsConstructor;
@@ -88,6 +89,16 @@ public class FlwInstanceController extends BaseController {
         return toAjax(flwInstanceService.deleteByInstanceIds(instanceIds));
     }
 
+    /**
+     * 按照实例id删除已完成得流程实例
+     *
+     * @param instanceIds 实例id
+     */
+    @DeleteMapping("/deleteHisByInstanceIds/{instanceIds}")
+    public R<Void> deleteHisByInstanceIds(@PathVariable List<Long> instanceIds) {
+        return toAjax(flwInstanceService.deleteHisByInstanceIds(instanceIds));
+    }
+
     /**
      * 撤销流程
      *
@@ -142,6 +153,17 @@ public class FlwInstanceController extends BaseController {
         return R.ok(flwInstanceService.instanceVariable(instanceId));
     }
 
+    /**
+     * 修改流程变量
+     *
+     * @param bo 参数
+     */
+    @RepeatSubmit()
+    @PutMapping("/updateVariable")
+    public R<Void> updateVariable(@Validated @RequestBody FlowVariableBo bo) {
+        return toAjax(flwInstanceService.updateVariable(bo));
+    }
+
     /**
      * 作废流程
      *

+ 17 - 5
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/controller/FlwTaskController.java

@@ -191,12 +191,12 @@ public class FlwTaskController extends BaseController {
     /**
      * 获取可驳回的前置节点
      *
-     * @param definitionId 流程定义id
-     * @param nowNodeCode  当前节点
+     * @param taskId      任务id
+     * @param nowNodeCode 当前节点
      */
-    @GetMapping("/getBackTaskNode/{definitionId}/{nowNodeCode}")
-    public R<List<Node>> getBackTaskNode(@PathVariable Long definitionId, @PathVariable String nowNodeCode) {
-        return R.ok(flwTaskService.getBackTaskNode(definitionId, nowNodeCode));
+    @GetMapping("/getBackTaskNode/{taskId}/{nowNodeCode}")
+    public R<List<Node>> getBackTaskNode(@PathVariable Long taskId, @PathVariable String nowNodeCode) {
+        return R.ok(flwTaskService.getBackTaskNode(taskId, nowNodeCode));
     }
 
     /**
@@ -209,4 +209,16 @@ public class FlwTaskController extends BaseController {
         return R.ok(flwTaskService.currentTaskAllUser(List.of(taskId)));
     }
 
+    /**
+     * 催办任务
+     *
+     * @param bo 参数
+     * @return 结果
+     */
+    @PostMapping("/urgeTask")
+    public R<Void> urgeTask(@RequestBody FlowUrgeTaskBo bo) {
+        return toAjax(flwTaskService.urgeTask(bo));
+    }
+
+
 }

+ 38 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowUrgeTaskBo.java

@@ -0,0 +1,38 @@
+package com.vber.workflow.domain.bo;
+
+import com.vber.common.core.validate.AddGroup;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 流程变量参数
+ *
+ * @author Iwb
+ */
+@Data
+public class FlowUrgeTaskBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 任务id
+     */
+    @NotNull(message = "任务id为空", groups = AddGroup.class)
+    private List<Long> taskIdList;
+
+    /**
+     * 消息类型
+     */
+    private List<String> messageType;
+
+    /**
+     * 催办内容
+     */
+    @NotNull(message = "催办内容为空", groups = AddGroup.class)
+    private String message;
+}

+ 39 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/FlowVariableBo.java

@@ -0,0 +1,39 @@
+package com.vber.workflow.domain.bo;
+
+import com.vber.common.core.validate.AddGroup;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 流程变量参数
+ *
+ * @author Iwb
+ */
+@Data
+public class FlowVariableBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 流程实例id
+     */
+    @NotNull(message = "流程实例id为空", groups = AddGroup.class)
+    private Long instanceId;
+
+    /**
+     * 流程变量key
+     */
+    @NotNull(message = "流程变量key为空", groups = AddGroup.class)
+    private String key;
+
+    /**
+     * 流程变量value
+     */
+    @NotNull(message = "流程变量value为空", groups = AddGroup.class)
+    private String value;
+
+}

+ 5 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/domain/bo/TestLeaveBo.java

@@ -31,6 +31,11 @@ public class TestLeaveBo extends BaseEntity {
     @NotNull(message = "主键不能为空", groups = {EditGroup.class})
     private Long id;
 
+    /**
+     * 流程code
+     */
+    private String flowCode;
+
     /**
      * 请假类型
      */

+ 23 - 17
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/listener/WorkflowGlobalListener.java

@@ -16,7 +16,6 @@ import com.vber.workflow.service.IFlwInstanceService;
 import com.vber.workflow.service.IFlwTaskService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.dromara.warm.flow.core.FlowEngine;
 import org.dromara.warm.flow.core.dto.FlowParams;
 import org.dromara.warm.flow.core.entity.Definition;
 import org.dromara.warm.flow.core.entity.Instance;
@@ -24,8 +23,6 @@ import org.dromara.warm.flow.core.entity.Task;
 import org.dromara.warm.flow.core.listener.GlobalListener;
 import org.dromara.warm.flow.core.listener.ListenerVariable;
 import org.dromara.warm.flow.core.service.InsService;
-import org.dromara.warm.flow.orm.entity.FlowInstance;
-import org.dromara.warm.flow.orm.entity.FlowTask;
 import org.springframework.stereotype.Component;
 
 import java.util.HashMap;
@@ -105,6 +102,7 @@ public class WorkflowGlobalListener implements GlobalListener {
         Instance instance = listenerVariable.getInstance();
         Definition definition = listenerVariable.getDefinition();
         Task task = listenerVariable.getTask();
+        List<Task> nextTasks = listenerVariable.getNextTasks();
         Map<String, Object> params = new HashMap<>();
         FlowParams flowParams = listenerVariable.getFlowParams();
         Map<String, Object> variable = new HashMap<>();
@@ -127,10 +125,22 @@ public class WorkflowGlobalListener implements GlobalListener {
             if (StringUtils.isNotBlank(status)) {
                 flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, params, false);
             }
+            if (!BusinessStatusEnum.initialState(instance.getFlowStatus())) {
+                if (task != null && CollUtil.isNotEmpty(nextTasks) && nextTasks.size() == 1
+                        && flwCommonService.applyNodeCode(definition.getId()).equals(nextTasks.get(0).getNodeCode())) {
+                    // 如果为画线指定驳回 线条指定为驳回 驳回得节点为申请人节点 则修改流程状态为退回
+                    flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, BusinessStatusEnum.BACK.getStatus(), params, false);
+                    // 修改流程实例状态
+                    instance.setFlowStatus(BusinessStatusEnum.BACK.getStatus());
+                    insService.updateById(instance);
+                }
+            }
         }
         //发布任务事件
-        if (task != null) {
-            flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, task.getId(), params);
+        if (CollUtil.isNotEmpty(nextTasks)) {
+            for (Task nextTask : nextTasks) {
+                flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, nextTask.getId(), params);
+            }
         }
         if (ObjectUtil.isNull(flowParams)) {
             return;
@@ -150,7 +160,7 @@ public class WorkflowGlobalListener implements GlobalListener {
             flwTaskService.setCopy(task, flowCopyList);
         }
         if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) {
-            List<String> messageType = MapUtil.get(variable, FlowConstant.FLOW_COPY_LIST, new TypeReference<>() {
+            List<String> messageType = MapUtil.get(variable, FlowConstant.MESSAGE_TYPE, new TypeReference<>() {
             });
             String notice = MapUtil.getStr(variable, FlowConstant.MESSAGE_NOTICE);
             // 消息通知
@@ -158,15 +168,12 @@ public class WorkflowGlobalListener implements GlobalListener {
                 flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
             }
         }
-        FlowInstance ins = new FlowInstance();
-        Map<String, Object> variableMap = instance.getVariableMap();
-        variableMap.remove(FlowConstant.FLOW_COPY_LIST);
-        variableMap.remove(FlowConstant.MESSAGE_TYPE);
-        variableMap.remove(FlowConstant.MESSAGE_NOTICE);
-        variableMap.remove(FlowConstant.SUBMIT);
-        ins.setId(instance.getId());
-        ins.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
-        insService.updateById(ins);
+        insService.removeVariables(instance.getId(),
+                FlowConstant.FLOW_COPY_LIST,
+                FlowConstant.MESSAGE_TYPE,
+                FlowConstant.MESSAGE_NOTICE,
+                FlowConstant.SUBMIT
+        );
     }
 
     /**
@@ -182,8 +189,7 @@ public class WorkflowGlobalListener implements GlobalListener {
             return flowStatus;
         } else {
             Long instanceId = instance.getId();
-            List<FlowTask> flowTasks = flwTaskService.selectByInstId(instanceId);
-            if (CollUtil.isEmpty(flowTasks)) {
+            if (flwTaskService.isTaskEnd(instanceId)) {
                 String status = BusinessStatusEnum.FINISH.getStatus();
                 // 更新流程状态为已完成
                 instanceService.updateStatus(instanceId, status);

+ 14 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwCommonService.java

@@ -1,15 +1,16 @@
 package com.vber.workflow.service;
 
+import com.vber.common.core.domain.dto.UserDTO;
+
 import java.util.List;
 
 /**
  * 通用 工作流服务
  *
- * @author LionLi
+ * @author Iwb
  */
 public interface IFlwCommonService {
 
-
     /**
      * 发送消息
      *
@@ -20,6 +21,17 @@ public interface IFlwCommonService {
      */
     void sendMessage(String flowName, Long instId, List<String> messageType, String message);
 
+    /**
+     * 发送消息
+     *
+     * @param messageType 消息类型
+     * @param message     消息内容
+     * @param subject     邮件标题
+     * @param userList    接收用户
+     */
+    void sendMessage(List<String> messageType, String message, String subject, List<UserDTO> userList);
+
+
     /**
      * 申请人节点编码
      *

+ 17 - 1
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwInstanceService.java

@@ -5,6 +5,7 @@ import com.vber.common.mybatis.core.page.TableDataInfo;
 import com.vber.workflow.domain.bo.FlowCancelBo;
 import com.vber.workflow.domain.bo.FlowInstanceBo;
 import com.vber.workflow.domain.bo.FlowInvalidBo;
+import com.vber.workflow.domain.bo.FlowVariableBo;
 import com.vber.workflow.domain.vo.FlowInstanceVo;
 import org.dromara.warm.flow.orm.entity.FlowInstance;
 
@@ -84,6 +85,14 @@ public interface IFlwInstanceService {
      */
     boolean deleteByInstanceIds(List<Long> instanceIds);
 
+    /**
+     * 按照实例id删除已完成得流程实例
+     *
+     * @param instanceIds 删除的实例id
+     * @return 删除结果
+     */
+    boolean deleteHisByInstanceIds(List<Long> instanceIds);
+
     /**
      * 撤销流程
      *
@@ -125,6 +134,14 @@ public interface IFlwInstanceService {
      */
     Map<String, Object> instanceVariable(Long instanceId);
 
+    /**
+     * 更新流程变量
+     *
+     * @param bo 参数
+     * @return 结果
+     */
+    boolean updateVariable(FlowVariableBo bo);
+
     /**
      * 设置流程变量
      *
@@ -141,7 +158,6 @@ public interface IFlwInstanceService {
      */
     FlowInstance selectByTaskId(Long taskId);
 
-
     /**
      * 作废流程
      *

+ 27 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/IFlwTaskService.java

@@ -111,11 +111,11 @@ public interface IFlwTaskService {
     /**
      * 获取可驳回的前置节点
      *
-     * @param definitionId 流程定义id
-     * @param nowNodeCode  当前节点
+     * @param taskId      任务id
+     * @param nowNodeCode 当前节点
      * @return 结果
      */
-    List<Node> getBackTaskNode(Long definitionId, String nowNodeCode);
+    List<Node> getBackTaskNode(Long taskId, String nowNodeCode);
 
     /**
      * 终止任务
@@ -165,6 +165,22 @@ public interface IFlwTaskService {
      */
     List<FlowTask> selectByInstId(Long instanceId);
 
+    /**
+     * 按照实例id查询任务
+     *
+     * @param instanceIds 列表
+     * @return 结果
+     */
+    List<FlowTask> selectByInstIds(List<Long> instanceIds);
+
+    /**
+     * 判断流程是否已结束(即该流程实例下是否还有未完成的任务)
+     *
+     * @param instanceId 流程实例ID
+     * @return true 表示任务已全部结束;false 表示仍有任务存在
+     */
+    boolean isTaskEnd(Long instanceId);
+
     /**
      * 任务操作
      *
@@ -190,4 +206,12 @@ public interface IFlwTaskService {
      * @return 节点
      */
     FlowNode getByNodeCode(String nodeCode, Long definitionId);
+
+    /**
+     * 催办任务
+     *
+     * @param bo 参数
+     * @return 结果
+     */
+    boolean urgeTask(FlowUrgeTaskBo bo);
 }

+ 5 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/ITestLeaveService.java

@@ -35,6 +35,11 @@ public interface ITestLeaveService {
      */
     TestLeaveVo insertByBo(TestLeaveBo bo);
 
+    /**
+     * 提交请假并发起流程
+     */
+    TestLeaveVo submitAndFlowStart(TestLeaveBo bo);
+
     /**
      * 修改请假
      */

+ 15 - 3
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwCommonServiceImpl.java

@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
 /**
  * 工作流工具
  *
- * @author LionLi
+ * @author Iwb
  */
 @ConditionalOnEnable
 @Slf4j
@@ -55,6 +55,19 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
         if (CollUtil.isEmpty(userList)) {
             return;
         }
+        sendMessage(messageType, message, "单据审批提醒", userList);
+    }
+
+    /**
+     * 发送消息
+     *
+     * @param messageType 消息类型
+     * @param message     消息内容
+     * @param subject     邮件标题
+     * @param userList    接收用户
+     */
+    @Override
+    public void sendMessage(List<String> messageType, String message, String subject, List<UserDTO> userList) {
         for (String code : messageType) {
             MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code);
             if (ObjectUtil.isEmpty(messageTypeEnum)) {
@@ -68,7 +81,7 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
                     SseMessageUtils.publishMessage(dto);
                 }
                 case EMAIL_MESSAGE -> {
-                    MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), "单据审批提醒", message);
+                    MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), subject, message);
                 }
                 case SMS_MESSAGE -> {
                     //todo 短信发送
@@ -76,7 +89,6 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
                 default -> throw new IllegalStateException("Unexpected value: " + messageTypeEnum);
             }
         }
-
     }
 
 

+ 7 - 8
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwDefinitionServiceImpl.java

@@ -8,7 +8,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.vber.common.core.exception.ServiceException;
-import com.vber.common.core.utils.ObjectUtils;
 import com.vber.common.core.utils.StreamUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.json.utils.JsonUtils;
@@ -26,6 +25,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.warm.flow.core.dto.DefJson;
 import org.dromara.warm.flow.core.enums.NodeType;
+import org.dromara.warm.flow.core.enums.PublishStatus;
 import org.dromara.warm.flow.core.service.DefService;
 import org.dromara.warm.flow.orm.entity.FlowDefinition;
 import org.dromara.warm.flow.orm.entity.FlowHisTask;
@@ -42,6 +42,7 @@ import org.springframework.web.multipart.MultipartFile;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import static com.vber.common.core.constant.TenantConstants.DEFAULT_TENANT_ID;
@@ -56,7 +57,6 @@ import static com.vber.common.core.constant.TenantConstants.DEFAULT_TENANT_ID;
 @RequiredArgsConstructor
 @Service
 public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
-
     private final DefService defService;
     private final FlowDefinitionMapper flowDefinitionMapper;
     private final FlowHisTaskMapper flowHisTaskMapper;
@@ -75,7 +75,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     @Override
     public TableDataInfo<FlowDefinitionVo> queryList(FlowDefinition flowDefinition, PageQuery pageQuery) {
         LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
-//        wrapper.eq(FlowDefinition::getIsPublish, PublishStatus.PUBLISHED.getKey());
+        wrapper.eq(FlowDefinition::getIsPublish, PublishStatus.PUBLISHED.getKey());
         Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
         List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
         return new TableDataInfo<>(list, page.getTotal());
@@ -91,7 +91,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
     @Override
     public TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery) {
         LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
-//        wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
+        wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
         Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
         List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
         return new TableDataInfo<>(list, page.getTotal());
@@ -105,7 +105,6 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
             List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowDefinition.getCategory()));
             wrapper.in(FlowDefinition::getCategory, StreamUtils.toList(categoryIds, Convert::toStr));
         }
-        wrapper.eq(ObjectUtils.isNotEmpty(flowDefinition.getIsPublish()), FlowDefinition::getIsPublish, flowDefinition.getIsPublish());
         wrapper.orderByDesc(FlowDefinition::getCreateTime);
         return wrapper;
     }
@@ -128,7 +127,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
                 }
             }
             if (CollUtil.isNotEmpty(errorMsg)) {
-                throw new ServiceException("节点【" + StringUtils.join(errorMsg, ",") + "】未配置办理人!");
+                throw new ServiceException("节点【{}】未配置办理人!", String.join(",", errorMsg));
             }
         }
         return defService.publish(id);
@@ -188,7 +187,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
             if (CollUtil.isNotEmpty(flowDefinitions)) {
                 String join = StreamUtils.join(flowDefinitions, FlowDefinition::getFlowCode);
                 log.info("流程定义【{}】已被使用不可被删除!", join);
-                throw new ServiceException("流程定义【" + join + "】已被使用不可被删除!");
+                throw new ServiceException("流程定义【{}】已被使用不可被删除!", join);
             }
         }
         try {
@@ -231,7 +230,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
             flowDefinition.setId(null);
             flowDefinition.setTenantId(tenantId);
             flowDefinition.setIsPublish(0);
-            flowDefinition.setCategory(String.valueOf(flowCategory.getCategoryId()));
+            flowDefinition.setCategory(Convert.toStr(flowCategory.getCategoryId()));
             int insert = flowDefinitionMapper.insert(flowDefinition);
             if (insert <= 0) {
                 log.info("同步流程定义【{}】失败!", definition.getFlowCode());

+ 93 - 14
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwInstanceServiceImpl.java

@@ -21,6 +21,7 @@ import com.vber.workflow.common.enums.TaskStatusEnum;
 import com.vber.workflow.domain.bo.FlowCancelBo;
 import com.vber.workflow.domain.bo.FlowInstanceBo;
 import com.vber.workflow.domain.bo.FlowInvalidBo;
+import com.vber.workflow.domain.bo.FlowVariableBo;
 import com.vber.workflow.domain.vo.FlowHisTaskVo;
 import com.vber.workflow.domain.vo.FlowInstanceVo;
 import com.vber.workflow.handler.FlowProcessEventHandler;
@@ -109,7 +110,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
      */
     @Override
     public FlowInstanceVo queryByBusinessId(Long businessId) {
-        FlowInstance instance = this.selectInstByBusinessId(String.valueOf(businessId));
+        FlowInstance instance = this.selectInstByBusinessId(Convert.toStr(businessId));
         FlowInstanceVo instanceVo = BeanUtil.toBean(instance, FlowInstanceVo.class);
         Definition definition = defService.getById(instanceVo.getDefinitionId());
         instanceVo.setFlowName(definition.getFlowName());
@@ -210,18 +211,71 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
                 Function.identity()
         );
 
-        // 逐一触发删除事件
-        instances.forEach(instance -> {
-            Definition definition = definitionMap.get(instance.getDefinitionId());
-            if (ObjectUtil.isNull(definition)) {
-                log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
-                return;
+        try {
+            // 逐一触发删除事件
+            instances.forEach(instance -> {
+                Definition definition = definitionMap.get(instance.getDefinitionId());
+                if (ObjectUtil.isNull(definition)) {
+                    log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+                    return;
+                }
+                flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+            });
+            // 删除实例
+            boolean remove = insService.remove(instanceIds);
+            if (!remove) {
+                log.warn("删除流程实例失败!");
+                throw new ServiceException("删除流程实例失败");
             }
-            flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
-        });
+        } catch (Exception e) {
+            log.warn("操作失败!{}", e.getMessage());
+            throw new ServiceException(e.getMessage());
+        }
+        return true;
+    }
 
-        // 删除实例
-        return insService.remove(instanceIds);
+    /**
+     * 按照实例id删除已完成的流程实例
+     *
+     * @param instanceIds 实例id
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteHisByInstanceIds(List<Long> instanceIds) {
+        // 获取实例信息
+        List<Instance> instances = insService.getByIds(instanceIds);
+        if (CollUtil.isEmpty(instances)) {
+            log.warn("未找到对应的流程实例信息,无法执行删除操作。");
+            return false;
+        }
+        // 获取定义信息
+        Map<Long, Definition> definitionMap = StreamUtils.toMap(
+                defService.getByIds(StreamUtils.toList(instances, Instance::getDefinitionId)),
+                Definition::getId,
+                Function.identity()
+        );
+        try {
+            // 逐一触发删除事件
+            instances.forEach(instance -> {
+                Definition definition = definitionMap.get(instance.getDefinitionId());
+                if (ObjectUtil.isNull(definition)) {
+                    log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+                    return;
+                }
+                flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
+            });
+            List<FlowTask> flowTaskList = flwTaskService.selectByInstIds(instanceIds);
+            if (CollUtil.isNotEmpty(flowTaskList)) {
+                FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+            }
+            FlowEngine.taskService().deleteByInsIds(instanceIds);
+            FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
+            FlowEngine.insService().removeByIds(instanceIds);
+        } catch (Exception e) {
+            log.warn("操作失败!{}", e.getMessage());
+            throw new ServiceException(e.getMessage());
+        }
+        return true;
     }
 
     /**
@@ -304,9 +358,6 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
                 if (CollUtil.isNotEmpty(users)) {
                     vo.setApprover(StreamUtils.join(users, User::getProcessedBy));
                 }
-                if (BusinessStatusEnum.isDraftOrCancelOrBack(flowInstance.getFlowStatus())) {
-                    vo.setApprover(LoginHelper.getUserIdStr());
-                }
             }
         }
 
@@ -359,6 +410,34 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
         return Map.of("variableList", variableList, "variable", flowInstance.getVariable());
     }
 
+    /**
+     * 设置流程变量
+     *
+     * @param bo 参数
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateVariable(FlowVariableBo bo) {
+        FlowInstance flowInstance = flowInstanceMapper.selectById(bo.getInstanceId());
+        if (flowInstance == null) {
+            throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
+        }
+        try {
+            Map<String, Object> variableMap = new HashMap<>(Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()));
+            if (!variableMap.containsKey(bo.getKey())) {
+                log.error("变量不存在: {}", bo.getKey());
+                return false;
+            }
+            variableMap.put(bo.getKey(), bo.getValue());
+            flowInstance.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
+            flowInstanceMapper.updateById(flowInstance);
+        } catch (Exception e) {
+            log.error("设置流程变量失败: {}", e.getMessage(), e);
+            throw new ServiceException(e.getMessage());
+        }
+        return true;
+    }
+
     /**
      * 设置流程变量
      *

+ 4 - 2
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwSpelServiceImpl.java

@@ -161,7 +161,7 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
         TableDataInfo<FlowSpelVo> page = this.queryPageList(bo, pageQuery);
         // 使用封装的字段映射方法进行转换
         List<TaskAssigneeDTO.TaskHandler> handlers = TaskAssigneeDTO.convertToHandlerList(page.getRows(),
-                FlowSpelVo::getViewSpel, c -> "", FlowSpelVo::getRemark, null, FlowSpelVo::getCreateTime);
+                FlowSpelVo::getViewSpel, item -> "", FlowSpelVo::getRemark, item -> "", FlowSpelVo::getCreateTime);
         return new TaskAssigneeDTO(page.getTotal(), handlers);
     }
 
@@ -181,7 +181,9 @@ public class FlwSpelServiceImpl implements IFlwSpelService {
                         .select(FlowSpel::getViewSpel, FlowSpel::getRemark)
                         .in(FlowSpel::getViewSpel, viewSpels)
         );
-        return StreamUtils.toMap(list, FlowSpel::getViewSpel, FlowSpel::getRemark);
+        return StreamUtils.toMap(list, FlowSpel::getViewSpel, x ->
+                StringUtils.isEmpty(x.getRemark()) ? "" : x.getRemark()
+        );
     }
 
 }

+ 75 - 49
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskAssigneeServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Pair;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import com.vber.common.core.domain.dto.OrgDTO;
 import com.vber.common.core.domain.dto.TaskAssigneeDTO;
@@ -12,6 +13,7 @@ import com.vber.common.core.domain.model.TaskAssigneeBody;
 import com.vber.common.core.enums.FormatsType;
 import com.vber.common.core.service.*;
 import com.vber.common.core.utils.DateUtils;
+import com.vber.common.core.utils.StreamUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.workflow.common.ConditionalOnEnable;
 import com.vber.workflow.common.enums.TaskAssigneeEnum;
@@ -28,6 +30,7 @@ import org.dromara.warm.flow.ui.vo.HandlerSelectVo;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 流程设计器-获取办理人权限设置列表
@@ -59,7 +62,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
     }
 
     /**
-     * 获取办理列表, 同时构建左侧组织机构树状结构
+     * 获取办理列表, 同时构建左侧部门树状结构
      *
      * @param query 查询条件
      * @return HandlerSelectVo
@@ -90,30 +93,25 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
             return Collections.emptyList();
         }
         // 解析并归类 ID,同时记录原始顺序和对应解析结果
-        Map<TaskAssigneeEnum, List<Long>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
-        Map<String, Pair<TaskAssigneeEnum, Long>> parsedMap = new LinkedHashMap<>();
-        List<String> spelList = new ArrayList<>();
+        Map<TaskAssigneeEnum, List<String>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
+        Map<String, Pair<TaskAssigneeEnum, String>> parsedMap = new LinkedHashMap<>();
         for (String storageId : storageIds) {
-            Pair<TaskAssigneeEnum, Long> parsed = this.parseStorageId(storageId);
+            Pair<TaskAssigneeEnum, String> parsed = this.parseStorageId(storageId);
             parsedMap.put(storageId, parsed);
             if (parsed != null) {
                 typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue());
-            } else if (StringUtils.startsWith(storageId, "#")) {
-                // #前缀表示SpEL办理人变量策略
-                spelList.add(storageId);
             }
         }
 
         // 查询所有类型对应的 ID 名称映射
-        Map<TaskAssigneeEnum, Map<Long, String>> nameMap = new EnumMap<>(TaskAssigneeEnum.class);
+        Map<TaskAssigneeEnum, Map<String, String>> nameMap = new EnumMap<>(TaskAssigneeEnum.class);
         typeIdMap.forEach((type, ids) -> nameMap.put(type, this.getNamesByType(type, ids)));
-        Map<String, String> spelMap = spelService.selectRemarksBySpels(spelList);
         // 组装返回结果,保持原始顺序
         return parsedMap.entrySet().stream()
                 .map(entry -> {
                     String storageId = entry.getKey();
-                    Pair<TaskAssigneeEnum, Long> parsed = entry.getValue();
-                    String handlerName = (parsed == null) ? spelMap.get(storageId)
+                    Pair<TaskAssigneeEnum, String> parsed = entry.getValue();
+                    String handlerName = (parsed == null) ? null
                             : nameMap.getOrDefault(parsed.getKey(), Collections.emptyMap())
                             .get(parsed.getValue());
                     return new HandlerFeedBackVo(storageId, handlerName);
@@ -134,23 +132,40 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
     }
 
     /**
-     * 根据任务办理类型获取组织机构数据
+     * 根据任务办理类型获取部门数据
      */
     private List<OrgDTO> fetchOrgData(TaskAssigneeEnum type) {
-        if (type == TaskAssigneeEnum.USER || type == TaskAssigneeEnum.ORG || type == TaskAssigneeEnum.POST) {
+        if (type.needsOrgService()) {
             return orgService.selectOrgsByList();
         }
         return new ArrayList<>();
     }
 
     /**
-     * 构建组织机构树状结构
+     * 获取权限分组名称
+     *
+     * @param type      任务分配人枚举
+     * @param groupName 权限分组
+     * @return 权限分组名称
+     */
+    private String getGroupName(TaskAssigneeEnum type, String groupName) {
+        if (StringUtils.isEmpty(groupName)) {
+            return DEFAULT_GROUP_NAME;
+        }
+        if (type.needsOrgService()) {
+            return orgService.selectOrgNameByIds(groupName);
+        }
+        return DEFAULT_GROUP_NAME;
+    }
+
+    /**
+     * 构建部门树状结构
      */
     private TreeFunDto<OrgDTO> buildOrgTree(List<OrgDTO> orgs) {
         return new TreeFunDto<>(orgs)
-                .setId(org -> String.valueOf(org.getOrgId()))
+                .setId(org -> Convert.toStr(org.getOrgId()))
                 .setName(OrgDTO::getOrgName)
-                .setParentId(org -> String.valueOf(org.getParentId()));
+                .setParentId(org -> Convert.toStr(org.getParentId()));
     }
 
     /**
@@ -161,10 +176,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
                 .setStorageId(assignee -> type.getCode() + assignee.getStorageId())
                 .setHandlerCode(assignee -> StringUtils.blankToDefault(assignee.getHandlerCode(), "无"))
                 .setHandlerName(assignee -> StringUtils.blankToDefault(assignee.getHandlerName(), "无"))
-                .setGroupName(assignee -> StringUtils.defaultIfBlank(
-                        Optional.ofNullable(assignee.getGroupName())
-                                .map(orgService::selectOrgNameByIds)
-                                .orElse(DEFAULT_GROUP_NAME), DEFAULT_GROUP_NAME))
+                .setGroupName(assignee -> this.getGroupName(type, assignee.getGroupName()))
                 .setCreateTime(assignee -> DateUtils.parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, assignee.getCreateTime()));
     }
 
@@ -181,9 +193,9 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
         if (StringUtils.isEmpty(storageIds)) {
             return List.of();
         }
-        Map<TaskAssigneeEnum, List<Long>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
+        Map<TaskAssigneeEnum, List<String>> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class);
         for (String storageId : storageIds.split(StringUtils.SEPARATOR)) {
-            Pair<TaskAssigneeEnum, Long> parsed = this.parseStorageId(storageId);
+            Pair<TaskAssigneeEnum, String> parsed = this.parseStorageId(storageId);
             if (parsed != null) {
                 typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue());
             }
@@ -197,38 +209,56 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
     /**
      * 根据指定的任务分配类型(TaskAssigneeEnum)和 ID 列表,获取对应的用户信息列表
      *
-     * @param type 任务分配类型,表示用户、角色、组织机构或其他(TaskAssigneeEnum 枚举值)
-     * @param ids  与指定分配类型关联的 ID 列表(例如用户ID、角色ID、组织机构ID等)
+     * @param type 任务分配类型,表示用户、角色、部门或其他(TaskAssigneeEnum 枚举值)
+     * @param ids  与指定分配类型关联的 ID 列表(例如用户ID、角色ID、部门ID等)
      * @return 返回包含用户信息的列表。如果类型为用户(USER),则通过用户ID列表查询;
      * 如果类型为角色(ROLE),则通过角色ID列表查询;
-     * 如果类型为组织机构(DEPT),则通过组织机构ID列表查询;
+     * 如果类型为部门(DEPT),则通过部门ID列表查询;
      * 如果类型为岗位(POST)或无法识别的类型,则返回空列表
      */
-    private List<UserDTO> getUsersByType(TaskAssigneeEnum type, List<Long> ids) {
+    private List<UserDTO> getUsersByType(TaskAssigneeEnum type, List<String> ids) {
+        if (type == TaskAssigneeEnum.SPEL) {
+            return new ArrayList<>();
+        }
+        List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
         return switch (type) {
-            case USER -> userService.selectListByIds(ids);
-            case ROLE -> userService.selectUsersByRoleIds(ids);
-            case ORG -> userService.selectUsersByOrgIds(ids);
-            case POST -> userService.selectUsersByPostIds(ids);
-            case SPEL -> new ArrayList<>();
+            case USER -> userService.selectListByIds(longIds);
+            case ROLE -> userService.selectUsersByRoleIds(longIds);
+            case ORG -> userService.selectUsersByOrgIds(longIds);
+            case POST -> userService.selectUsersByPostIds(longIds);
+            default -> new ArrayList<>();
         };
     }
 
     /**
      * 根据任务分配类型和对应 ID 列表,批量查询名称映射关系
      *
-     * @param type 分配类型(用户、角色、组织机构、岗位)
+     * @param type 分配类型(用户、角色、部门、岗位)
      * @param ids  ID 列表(如用户ID、角色ID等)
      * @return 返回 Map,其中 key 为 ID,value 为对应的名称
      */
-    private Map<Long, String> getNamesByType(TaskAssigneeEnum type, List<Long> ids) {
-        return switch (type) {
-            case USER -> userService.selectUserNamesByIds(ids);
-            case ROLE -> roleService.selectRoleNamesByIds(ids);
-            case ORG -> orgService.selectOrgNamesByIds(ids);
-            case POST -> postService.selectPostNamesByIds(ids);
-            case SPEL -> new HashMap<>();
+    private Map<String, String> getNamesByType(TaskAssigneeEnum type, List<String> ids) {
+        if (type == TaskAssigneeEnum.SPEL) {
+            return spelService.selectRemarksBySpels(ids);
+        }
+
+        List<Long> longIds = StreamUtils.toList(ids, Convert::toLong);
+        Map<Long, String> rawMap = switch (type) {
+            case USER -> userService.selectUserNamesByIds(longIds);
+            case ROLE -> roleService.selectRoleNamesByIds(longIds);
+            case ORG -> orgService.selectOrgNamesByIds(longIds);
+            case POST -> postService.selectPostNamesByIds(longIds);
+            default -> Collections.emptyMap();
         };
+        if (MapUtil.isEmpty(rawMap)) {
+            return Collections.emptyMap();
+        }
+        return rawMap.entrySet()
+                .stream()
+                .collect(Collectors.toMap(
+                        e -> Convert.toStr(e.getKey()),
+                        Map.Entry::getValue
+                ));
     }
 
     /**
@@ -237,24 +267,20 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
      * @param storageId 例如 "user:123" 或 "456"
      * @return Pair(TaskAssigneeEnum, Long),如果格式非法返回 null
      */
-    private Pair<TaskAssigneeEnum, Long> parseStorageId(String storageId) {
+    private Pair<TaskAssigneeEnum, String> parseStorageId(String storageId) {
         if (StringUtils.isBlank(storageId)) {
             return null;
         }
-        // 跳过以 $ 或 # 开头的字符串
-        if (StringUtils.startsWith(storageId, "$") || StringUtils.startsWith(storageId, "#")) {
-            // $前缀表示默认办理人变量策略
-            // #前缀表示spel办理人变量策略
-            log.debug("跳过 storageId 解析,检测到内置变量表达式:{}", storageId);
-            return null;
+        if (TaskAssigneeEnum.isSpelExpression(storageId)) {
+            return Pair.of(TaskAssigneeEnum.SPEL, storageId);
         }
         try {
             String[] parts = storageId.split(StrUtil.COLON, 2);
             if (parts.length < 2) {
-                return Pair.of(TaskAssigneeEnum.USER, Convert.toLong(parts[0]));
+                return Pair.of(TaskAssigneeEnum.USER, parts[0]);
             } else {
                 TaskAssigneeEnum type = TaskAssigneeEnum.fromCode(parts[0] + StrUtil.COLON);
-                return Pair.of(type, Convert.toLong(parts[1]));
+                return Pair.of(type, parts[1]);
             }
         } catch (Exception e) {
             log.warn("解析 storageId 失败,格式非法:{},错误信息:{}", storageId, e.getMessage());

+ 136 - 47
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/FlwTaskServiceImpl.java

@@ -3,6 +3,7 @@ package com.vber.workflow.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Dict;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -19,6 +20,7 @@ import com.vber.common.core.utils.StringUtils;
 import com.vber.common.core.utils.ValidatorUtils;
 import com.vber.common.core.validate.AddGroup;
 import com.vber.common.core.validate.EditGroup;
+import com.vber.common.json.utils.JsonUtils;
 import com.vber.common.mybatis.core.page.PageQuery;
 import com.vber.common.mybatis.core.page.TableDataInfo;
 import com.vber.common.satoken.utils.LoginHelper;
@@ -42,14 +44,12 @@ import org.dromara.warm.flow.core.dto.FlowParams;
 import org.dromara.warm.flow.core.entity.*;
 import org.dromara.warm.flow.core.enums.NodeType;
 import org.dromara.warm.flow.core.enums.SkipType;
+import org.dromara.warm.flow.core.enums.UserType;
 import org.dromara.warm.flow.core.service.*;
 import org.dromara.warm.flow.core.utils.ExpressionUtil;
 import org.dromara.warm.flow.core.utils.MapUtil;
 import org.dromara.warm.flow.orm.entity.*;
-import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
-import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
-import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
-import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
+import org.dromara.warm.flow.orm.mapper.*;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -61,7 +61,7 @@ import static com.vber.workflow.common.constant.FlowConstant.*;
 /**
  * 任务 服务层实现
  *
- * @author may
+ * @author Iwb
  */
 @ConditionalOnEnable
 @Slf4j
@@ -85,6 +85,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     private final IFlwTaskAssigneeService flwTaskAssigneeService;
     private final IFlwCommonService flwCommonService;
     private final IFlwNodeExtService flwNodeExtService;
+    private final FlowDefinitionMapper flowDefinitionMapper;
 
     /**
      * 启动任务
@@ -102,7 +103,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         Map<String, Object> variables = startProcessBo.getVariables();
         // 流程发起人
         variables.put(INITIATOR, LoginHelper.getUserIdStr());
-        // 发起人组织机构id
+        // 发起人部门id
         variables.put(INITIATOR_ORG_ID, LoginHelper.getOrgId());
         // 业务id
         variables.put(BUSINESS_ID, businessId);
@@ -118,6 +119,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             dto.setTaskId(taskList.get(0).getId());
             return dto;
         }
+        // 将流程定义内的扩展参数设置到变量中
+        Definition definition = FlowEngine.defService().getPublishByFlowCode(startProcessBo.getFlowCode());
+        Dict dict = JsonUtils.parseMap(definition.getExt());
+        boolean autoPass = !ObjectUtil.isNull(dict) && dict.getBool(FlowConstant.AUTO_PASS);
+        variables.put(FlowConstant.AUTO_PASS, autoPass);
+
         FlowParams flowParams = FlowParams.build()
                 .flowCode(startProcessBo.getFlowCode())
                 .variable(startProcessBo.getVariables())
@@ -155,11 +162,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             // 获取抄送人
             List<FlowCopyBo> flowCopyList = completeTaskBo.getFlowCopyList();
             // 设置抄送人
-            completeTaskBo.getVariables().put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
+            Map<String, Object> variables = completeTaskBo.getVariables();
+            variables.put(FlowConstant.FLOW_COPY_LIST, flowCopyList);
             // 消息类型
-            completeTaskBo.getVariables().put(FlowConstant.MESSAGE_TYPE, messageType);
+            variables.put(FlowConstant.MESSAGE_TYPE, messageType);
             // 消息通知
-            completeTaskBo.getVariables().put(FlowConstant.MESSAGE_NOTICE, notice);
+            variables.put(FlowConstant.MESSAGE_NOTICE, notice);
 
 
             FlowTask flowTask = flowTaskMapper.selectById(taskId);
@@ -169,23 +177,23 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             Instance ins = insService.getById(flowTask.getInstanceId());
             // 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听
             if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
-                completeTaskBo.getVariables().put(FlowConstant.SUBMIT, true);
+                variables.put(FlowConstant.SUBMIT, true);
             }
             // 设置弹窗处理人
             Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
             if (CollUtil.isNotEmpty(assigneeMap)) {
-                completeTaskBo.getVariables().putAll(assigneeMap);
+                variables.putAll(assigneeMap);
             }
             // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
             FlowParams flowParams = FlowParams.build()
-                    .variable(completeTaskBo.getVariables())
+                    .variable(variables)
                     .skipType(SkipType.PASS.getKey())
                     .message(completeTaskBo.getMessage())
                     .flowStatus(BusinessStatusEnum.WAITING.getStatus())
                     .hisStatus(TaskStatusEnum.PASS.getStatus())
                     .hisTaskExt(completeTaskBo.getFileId());
-            // 执行任务跳转,并根据返回的处理人设置下一步处理人
-            taskService.skip(taskId, flowParams);
+            Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
+            skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
             return true;
         } catch (Exception e) {
             log.error(e.getMessage(), e);
@@ -193,6 +201,43 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         }
     }
 
+    /**
+     * 流程办理
+     *
+     * @param taskId     任务ID
+     * @param flowParams 参数
+     * @param instanceId 实例ID
+     * @param autoPass   自动审批
+     */
+    private void skipTask(Long taskId, FlowParams flowParams, Long instanceId, Boolean autoPass) {
+        // 执行任务跳转,并根据返回的处理人设置下一步处理人
+        taskService.skip(taskId, flowParams);
+        List<FlowTask> flowTaskList = selectByInstId(instanceId);
+        if (CollUtil.isEmpty(flowTaskList)) {
+            return;
+        }
+        List<User> userList = FlowEngine.userService()
+                .getByAssociateds(StreamUtils.toList(flowTaskList, FlowTask::getId));
+        if (CollUtil.isEmpty(userList)) {
+            return;
+        }
+        for (FlowTask task : flowTaskList) {
+            if (!task.getId().equals(taskId) && autoPass) {
+                List<User> users = StreamUtils.filter(userList, e -> ObjectUtil.equals(task.getId(), e.getAssociated()) && ObjectUtil.equal(e.getProcessedBy(), LoginHelper.getUserIdStr()));
+                if (CollUtil.isEmpty(users)) {
+                    continue;
+                }
+                flowParams.
+                        message("流程引擎自动审批!").
+                        variable(Map.of(
+                                FlowConstant.SUBMIT, false,
+                                FlowConstant.FLOW_COPY_LIST, Collections.emptyList(),
+                                FlowConstant.MESSAGE_NOTICE, StringUtils.EMPTY));
+                skipTask(task.getId(), flowParams, instanceId, true);
+            }
+        }
+    }
+
     /**
      * 设置弹窗处理人
      *
@@ -215,7 +260,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                     List<String> variableUserIds = Arrays.asList(userIds.split(StringUtils.SEPARATOR));
                     hashSet.addAll(popUserIds);
                     hashSet.addAll(variableUserIds);
-                    map.put(entry.getKey(), String.join(StringUtils.SEPARATOR, hashSet));
+                    map.put(entry.getKey(), StringUtils.joinComma(hashSet));
                 }
             } else {
                 map.put(entry.getKey(), entry.getValue());
@@ -236,7 +281,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             return;
         }
         // 添加抄送人记录
-        FlowHisTask flowHisTask = flowHisTaskMapper.selectList(new LambdaQueryWrapper<>(FlowHisTask.class).eq(FlowHisTask::getTaskId, task.getId())).get(0);
+        FlowHisTask flowHisTask = flowHisTaskMapper.selectList(
+                new LambdaQueryWrapper<>(FlowHisTask.class)
+                        .eq(FlowHisTask::getTaskId, task.getId())).get(0);
         FlowNode flowNode = new FlowNode();
         flowNode.setNodeCode(flowHisTask.getTargetNodeCode());
         flowNode.setNodeName(flowHisTask.getTargetNodeName());
@@ -256,9 +303,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         List<User> userList = StreamUtils.toList(flowCopyList, x ->
                 new FlowUser()
                         .setType(TaskAssigneeType.COPY.getCode())
-                        .setProcessedBy(String.valueOf(x.getUserId()))
-                        .setAssociated(taskId)
-        );
+                        .setProcessedBy(Convert.toStr(x.getUserId()))
+                        .setAssociated(taskId));
         // 批量保存抄送人员
         FlowEngine.userService().saveBatch(userList);
     }
@@ -320,8 +366,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
         if (CollUtil.isEmpty(taskList)) {
             return;
         }
-        List<User> associatedUsers = FlowEngine.userService()
-                .getByAssociateds(StreamUtils.toList(taskList, FlowTaskVo::getId));
+        List<User> associatedUsers = FlowEngine.userService().getByAssociateds(StreamUtils.toList(taskList, FlowTaskVo::getId));
         Map<Long, List<User>> taskUserMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated);
         // 组装用户数据回任务列表
         for (FlowTaskVo task : taskList) {
@@ -418,22 +463,28 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     /**
      * 获取可驳回的前置节点
      *
-     * @param definitionId 流程定义id
-     * @param nowNodeCode  当前节点
+     * @param taskId      任务id
+     * @param nowNodeCode 当前节点
      */
     @Override
-    public List<Node> getBackTaskNode(Long definitionId, String nowNodeCode) {
-        List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), definitionId);
+    public List<Node> getBackTaskNode(Long taskId, String nowNodeCode) {
+        FlowTask task = flowTaskMapper.selectById(taskId);
+        List<Node> nodeCodes = nodeService.getByNodeCodes(Collections.singletonList(nowNodeCode), task.getDefinitionId());
         if (!CollUtil.isNotEmpty(nodeCodes)) {
             return nodeCodes;
         }
+        List<User> userList = FlowEngine.userService()
+                .getByAssociateds(Collections.singletonList(task.getId()), UserType.DEPUTE.getKey());
+        if (CollUtil.isNotEmpty(userList)) {
+            return nodeCodes;
+        }
         //判断是否配置了固定驳回节点
         Node node = nodeCodes.get(0);
         if (StringUtils.isNotBlank(node.getAnyNodeSkip())) {
-            return nodeService.getByNodeCodes(Collections.singletonList(node.getAnyNodeSkip()), definitionId);
+            return nodeService.getByNodeCodes(Collections.singletonList(node.getAnyNodeSkip()), task.getDefinitionId());
         }
         //获取可驳回的前置节点
-        List<Node> nodes = nodeService.previousNodeList(definitionId, nowNodeCode);
+        List<Node> nodes = nodeService.previousNodeList(task.getDefinitionId(), nowNodeCode);
         if (CollUtil.isNotEmpty(nodes)) {
             return StreamUtils.filter(nodes, e -> NodeType.BETWEEN.getKey().equals(e.getNodeType()));
         }
@@ -477,8 +528,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     public List<FlowTask> selectByIdList(List<Long> taskIdList) {
-        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class)
-                .in(FlowTask::getId, taskIdList));
+        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).in(FlowTask::getId, taskIdList));
     }
 
     /**
@@ -535,17 +585,14 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
             //办理人变量替换
             ExpressionUtil.evalVariable(buildNextTaskList, FlowParams.build().variable(mergeVariable));
             for (FlowNode flowNode : nextFlowNodes) {
-                Optional<Task> first = buildNextTaskList.stream()
-                        .filter(t -> t.getNodeCode().equals(flowNode.getNodeCode()))
-                        .findFirst();
-                first.ifPresent(t -> {
-                    if (CollUtil.isNotEmpty(t.getPermissionList())) {
-                        List<UserDTO> users = flwTaskAssigneeService.fetchUsersByStorageIds(String.join(StringUtils.SEPARATOR, t.getPermissionList()));
-                        if (CollUtil.isNotEmpty(users)) {
-                            flowNode.setPermissionFlag(StreamUtils.join(users, e -> String.valueOf(e.getUserId())));
-                        }
+                Task first = StreamUtils.findFirst(buildNextTaskList, t -> t.getNodeCode().equals(flowNode.getNodeCode()));
+                if (ObjectUtil.isNotNull(first) && CollUtil.isNotEmpty(first.getPermissionList())) {
+                    List<UserDTO> users = flwTaskAssigneeService.fetchUsersByStorageIds(StringUtils.joinComma(first.getPermissionList()));
+                    if (CollUtil.isNotEmpty(users)) {
+                        flowNode.setPermissionFlag(StreamUtils.join(users, e -> Convert.toStr(e.getUserId())));
                     }
-                });
+                }
+
             }
         }
         return nextFlowNodes;
@@ -559,8 +606,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     public FlowHisTask selectHisTaskById(Long taskId) {
-        return flowHisTaskMapper.selectOne(new LambdaQueryWrapper<>(FlowHisTask.class)
-                .eq(FlowHisTask::getId, taskId));
+        return flowHisTaskMapper.selectOne(new LambdaQueryWrapper<>(FlowHisTask.class).eq(FlowHisTask::getId, taskId));
     }
 
     /**
@@ -570,8 +616,29 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
      */
     @Override
     public List<FlowTask> selectByInstId(Long instanceId) {
-        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class)
-                .eq(FlowTask::getInstanceId, instanceId));
+        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).eq(FlowTask::getInstanceId, instanceId));
+    }
+
+    /**
+     * 按照实例id查询任务
+     *
+     * @param instanceIds 流程实例id
+     */
+    @Override
+    public List<FlowTask> selectByInstIds(List<Long> instanceIds) {
+        return flowTaskMapper.selectList(new LambdaQueryWrapper<>(FlowTask.class).in(FlowTask::getInstanceId, instanceIds));
+    }
+
+    /**
+     * 判断流程是否已结束(即该流程实例下是否还有未完成的任务)
+     *
+     * @param instanceId 流程实例ID
+     * @return true 表示任务已全部结束;false 表示仍有任务存在
+     */
+    @Override
+    public boolean isTaskEnd(Long instanceId) {
+        boolean exists = flowTaskMapper.exists(new LambdaQueryWrapper<FlowTask>().eq(FlowTask::getInstanceId, instanceId));
+        return !exists;
     }
 
     /**
@@ -583,8 +650,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
-        FlowParams flowParams = FlowParams.build()
-                .message(bo.getMessage());
+        FlowParams flowParams = FlowParams.build().message(bo.getMessage());
         if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
             flowParams.ignore(true);
         }
@@ -667,8 +733,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                         new FlowUser()
                                 .setType(TaskAssigneeType.APPROVER.getCode())
                                 .setProcessedBy(userId)
-                                .setAssociated(flowTask.getId())
-                );
+                                .setAssociated(flowTask.getId()));
                 if (CollUtil.isNotEmpty(userList)) {
                     FlowEngine.userService().saveBatch(userList);
                 }
@@ -708,4 +773,28 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
                 .eq(FlowNode::getDefinitionId, definitionId));
     }
 
+    /**
+     * 催办任务
+     *
+     * @param bo 参数
+     */
+    @Override
+    public boolean urgeTask(FlowUrgeTaskBo bo) {
+        try {
+            if (CollUtil.isEmpty(bo.getTaskIdList())) {
+                return false;
+            }
+            List<UserDTO> userList = this.currentTaskAllUser(bo.getTaskIdList());
+            if (CollUtil.isEmpty(userList)) {
+                return false;
+            }
+            List<String> messageType = bo.getMessageType();
+            String message = bo.getMessage();
+            flwCommonService.sendMessage(messageType, message, "单据审批提醒", userList);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new ServiceException(e.getMessage());
+        }
+        return true;
+    }
 }

+ 28 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/TestLeaveServiceImpl.java

@@ -7,10 +7,12 @@ import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.vber.common.core.domain.dto.StartProcessDTO;
 import com.vber.common.core.domain.event.ProcessDeleteEvent;
 import com.vber.common.core.domain.event.ProcessEvent;
 import com.vber.common.core.domain.event.ProcessTaskEvent;
 import com.vber.common.core.enums.BusinessStatusEnum;
+import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.service.WorkflowService;
 import com.vber.common.core.utils.MapstructUtils;
 import com.vber.common.core.utils.StringUtils;
@@ -114,6 +116,32 @@ public class TestLeaveServiceImpl implements ITestLeaveService {
         return MapstructUtils.convert(add, TestLeaveVo.class);
     }
 
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public TestLeaveVo submitAndFlowStart(TestLeaveBo bo) {
+        long day = DateUtil.betweenDay(bo.getStartDate(), bo.getEndDate(), true);
+        // 截止日期也算一天
+        bo.setLeaveDays((int) day + 1);
+        TestLeave leave = MapstructUtils.convert(bo, TestLeave.class);
+        boolean flag = baseMapper.insertOrUpdate(leave);
+        if (flag) {
+            bo.setId(leave.getId());
+            // 后端发起需要忽略权限
+            bo.getParams().put("ignore", true);
+
+
+            boolean flag1 = workflowService.startCompleteTask(new StartProcessDTO() {{
+                setBusinessId(leave.getId().toString());
+                setFlowCode(StringUtils.isEmpty(bo.getFlowCode()) ? "leave1" : bo.getFlowCode());
+                setVariables(bo.getParams());
+            }});
+            if (!flag1) {
+                throw new ServiceException("流程发起异常");
+            }
+        }
+        return MapstructUtils.convert(leave, TestLeaveVo.class);
+    }
+
     /**
      * 修改请假
      */

+ 31 - 0
SERVER/VberAdminPlusV3/vber-modules/vber-workflow/src/main/java/com/vber/workflow/service/impl/WorkflowServiceImpl.java

@@ -5,9 +5,11 @@ import cn.hutool.core.util.ObjectUtil;
 import com.vber.common.core.domain.dto.CompleteTaskDTO;
 import com.vber.common.core.domain.dto.StartProcessDTO;
 import com.vber.common.core.domain.dto.StartProcessReturnDTO;
+import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.service.WorkflowService;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.workflow.common.ConditionalOnEnable;
+import com.vber.workflow.common.enums.MessageTypeEnum;
 import com.vber.workflow.domain.bo.CompleteTaskBo;
 import com.vber.workflow.domain.bo.StartProcessBo;
 import com.vber.workflow.service.IFlwDefinitionService;
@@ -16,7 +18,9 @@ import com.vber.workflow.service.IFlwTaskService;
 import lombok.RequiredArgsConstructor;
 import org.dromara.warm.flow.orm.entity.FlowInstance;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -148,4 +152,31 @@ public class WorkflowServiceImpl implements WorkflowService {
         return flwTaskService.completeTask(completeTask);
     }
 
+    /**
+     * 启动流程并办理第一个任务
+     *
+     * @param startProcess 参数
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean startCompleteTask(StartProcessDTO startProcess) {
+        try {
+            StartProcessReturnDTO result = flwTaskService.startWorkFlow(new StartProcessBo() {{
+                setBusinessId(startProcess.getBusinessId());
+                setFlowCode(startProcess.getFlowCode());
+                setVariables(startProcess.getVariables());
+            }});
+            boolean flag = flwTaskService.completeTask(new CompleteTaskBo() {{
+                setTaskId(result.getTaskId());
+                setMessageType(Collections.singletonList(MessageTypeEnum.SYSTEM_MESSAGE.getCode()));
+                setVariables(startProcess.getVariables());
+            }});
+            if (!flag) {
+                throw new ServiceException("流程发起异常");
+            }
+            return true;
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
 }