From 7f49b7882c9e7e0dde7fca7445b2bfc7d6cf51a0 Mon Sep 17 00:00:00 2001 From: lcj <2331845269@qq.com> Date: Wed, 2 Jul 2025 11:18:59 +0800 Subject: [PATCH] =?UTF-8?q?[add]=20=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xinnengyuan/.gitignore | 2 +- xinnengyuan/pom.xml | 2 +- .../src/main/resources/application.yml | 10 +- .../common/core/domain/dto/DictDataDTO.java | 41 +++ .../common/core/domain/dto/DictTypeDTO.java | 41 +++ .../core/domain/event/ProcessEvent.java | 19 +- .../core/domain/event/ProcessTaskEvent.java | 17 +- .../core/exception/file/FileException.java | 21 ++ .../FileNameLengthLimitExceededException.java | 18 ++ .../file/FileSizeLimitExceededException.java | 18 ++ .../common/core/service/DictService.java | 20 ++ .../common/core/service/UserService.java | 33 +++ .../common/core/service/WorkflowService.java | 11 +- .../common/core/utils/TreeBuildUtils.java | 27 ++ .../common/core/utils/file/FileUtils.java | 74 +++++ .../common/core/utils/file/MimeTypeUtils.java | 40 +++ .../service/impl/SysDictTypeServiceImpl.java | 27 ++ .../service/impl/SysUserServiceImpl.java | 81 +++++- .../workflow/common/ConditionalOnEnable.java | 15 + .../common/constant/FlowConstant.java | 35 ++- .../common/enums/ButtonPermissionEnum.java | 65 +++++ .../workflow/common/enums/NodeExtEnum.java | 32 +++ .../controller/FlwCategoryController.java | 4 + .../controller/FlwInstanceController.java | 6 +- .../controller/FlwTaskController.java | 11 + .../dromara/workflow/domain/FlowCategory.java | 9 + .../workflow/domain/bo/BackProcessBo.java | 2 - .../workflow/domain/bo/CompleteTaskBo.java | 9 +- .../workflow/domain/bo/FlowNextNodeBo.java | 38 +++ .../workflow/domain/bo/TestLeaveBo.java | 3 + .../domain/vo/ButtonPermissionVo.java | 43 +++ .../workflow/domain/vo/FlowCategoryVo.java | 8 +- .../workflow/domain/vo/FlowTaskVo.java | 11 + .../handler/FlowProcessEventHandler.java | 39 ++- .../handler/WorkflowPermissionHandler.java | 52 ++-- .../listener/WorkflowGlobalListener.java | 105 +++++-- .../workflow/mapper/FlwCategoryMapper.java | 4 +- .../workflow/service/IFlwCommonService.java | 37 +++ .../service/IFlwDefinitionService.java | 1 - .../workflow/service/IFlwInstanceService.java | 2 +- .../workflow/service/IFlwNodeExtService.java | 22 ++ .../service/IFlwTaskAssigneeService.java | 10 +- .../workflow/service/IFlwTaskService.java | 27 ++ .../impl/CategoryNameTranslationImpl.java | 8 +- .../service/impl/FlwCategoryServiceImpl.java | 45 ++- .../service/impl/FlwChartExtServiceImpl.java | 247 +++++++++++++++++ .../service/impl/FlwCommonServiceImpl.java | 122 +++++++++ .../impl/FlwDefinitionServiceImpl.java | 23 +- .../service/impl/FlwInstanceServiceImpl.java | 73 ++--- .../service/impl/FlwNodeExtServiceImpl.java | 243 +++++++++++++++++ .../impl/FlwTaskAssigneeServiceImpl.java | 123 +++++++-- .../service/impl/FlwTaskServiceImpl.java | 258 +++++++++++------- .../service/impl/TestLeaveServiceImpl.java | 30 +- .../service/impl/WorkflowServiceImpl.java | 19 ++ .../dromara/workflow/utils/WorkflowUtils.java | 206 -------------- .../mapper/workflow/FlwCategoryMapper.xml | 4 - .../mapper/workflow/FlwTaskMapper.xml | 2 +- xinnengyuan/script/leave/leave1.json | 18 +- xinnengyuan/script/leave/leave2.json | 20 +- xinnengyuan/script/leave/leave3.json | 24 +- xinnengyuan/script/leave/leave4.json | 20 +- xinnengyuan/script/leave/leave5.json | 24 +- xinnengyuan/script/leave/leave6.json | 215 +++++++++++++++ xinnengyuan/script/sql/ry_workflow.sql | 36 +-- 64 files changed, 2250 insertions(+), 602 deletions(-) create mode 100644 xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java create mode 100644 xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java create mode 100644 xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java create mode 100644 xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java create mode 100644 xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java create mode 100644 xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java create mode 100644 xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermissionVo.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java create mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java delete mode 100644 xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java create mode 100644 xinnengyuan/script/leave/leave6.json diff --git a/xinnengyuan/.gitignore b/xinnengyuan/.gitignore index 516ce9ab..7ee651bd 100644 --- a/xinnengyuan/.gitignore +++ b/xinnengyuan/.gitignore @@ -51,4 +51,4 @@ nbdist/ .run logs/ docs -file +/file diff --git a/xinnengyuan/pom.xml b/xinnengyuan/pom.xml index 596eabc4..a58859e2 100644 --- a/xinnengyuan/pom.xml +++ b/xinnengyuan/pom.xml @@ -49,7 +49,7 @@ 8.7.2-20250101 - 1.6.6 + 1.7.4 3.2.2 diff --git a/xinnengyuan/ruoyi-admin/src/main/resources/application.yml b/xinnengyuan/ruoyi-admin/src/main/resources/application.yml index d141ea8f..a889ef7b 100644 --- a/xinnengyuan/ruoyi-admin/src/main/resources/application.yml +++ b/xinnengyuan/ruoyi-admin/src/main/resources/application.yml @@ -119,7 +119,7 @@ security: - /error - /*/api-docs - /*/api-docs/** - - /warm-flow-ui/token-name + - /warm-flow-ui/config - /other/ys7Device/webhook # todo 仅测试 - /facility/matrix/** @@ -301,3 +301,11 @@ warm-flow: ui: true # 默认Authorization,如果有多个token,用逗号分隔 token-name: ${sa-token.token-name},clientid + # 流程状态对应的三元色 + chart-status-color: + ## 未办理 + - 62,62,62 + ## 待办理 + - 255,205,23 + ## 已办理 + - 157,255,0 diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java new file mode 100644 index 00000000..dff1a75d --- /dev/null +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictDataDTO.java @@ -0,0 +1,41 @@ +package org.dromara.common.core.domain.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 字典数据DTO + * + * @author AprilWind + */ +@Data +@NoArgsConstructor +public class DictDataDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 字典标签 + */ + private String dictLabel; + + /** + * 字典键值 + */ + private String dictValue; + + /** + * 是否默认(Y是 N否) + */ + private String isDefault; + + /** + * 备注 + */ + private String remark; + +} diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java new file mode 100644 index 00000000..43ab142c --- /dev/null +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/dto/DictTypeDTO.java @@ -0,0 +1,41 @@ +package org.dromara.common.core.domain.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 字典类型DTO + * + * @author AprilWind + */ +@Data +@NoArgsConstructor +public class DictTypeDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 字典主键 + */ + private Long dictId; + + /** + * 字典名称 + */ + private String dictName; + + /** + * 字典类型 + */ + private String dictType; + + /** + * 备注 + */ + private String remark; + +} diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java index 6329b9c0..7b15b85a 100644 --- a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessEvent.java @@ -33,7 +33,22 @@ public class ProcessEvent implements Serializable { private String businessId; /** - * 状态 + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 流程节点编码 + */ + private String nodeCode; + + /** + * 流程节点名称 + */ + private String nodeName; + + /** + * 流程状态 */ private String status; @@ -45,6 +60,6 @@ public class ProcessEvent implements Serializable { /** * 当为true时为申请人节点办理 */ - private boolean submit; + private Boolean submit; } diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java index 33bc6e5a..67cf7385 100644 --- a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/ProcessTaskEvent.java @@ -27,10 +27,20 @@ public class ProcessTaskEvent implements Serializable { private String flowCode; /** - * 审批节点编码 + * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关) + */ + private Integer nodeType; + + /** + * 流程节点编码 */ private String nodeCode; + /** + * 流程节点名称 + */ + private String nodeName; + /** * 任务id */ @@ -41,4 +51,9 @@ public class ProcessTaskEvent implements Serializable { */ private String businessId; + /** + * 流程状态 + */ + private String status; + } diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java new file mode 100644 index 00000000..d374fc01 --- /dev/null +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java @@ -0,0 +1,21 @@ +package org.dromara.common.core.exception.file; + +import org.dromara.common.core.exception.base.BaseException; + +import java.io.Serial; + +/** + * 文件信息异常类 + * + * @author ruoyi + */ +public class FileException extends BaseException { + + @Serial + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) { + super("file", code, args, null); + } + +} diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 00000000..af98124f --- /dev/null +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,18 @@ +package org.dromara.common.core.exception.file; + +import java.io.Serial; + +/** + * 文件名称超长限制异常类 + * + * @author ruoyi + */ +public class FileNameLengthLimitExceededException extends FileException { + + @Serial + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) { + super("upload.filename.exceed.length", new Object[]{defaultFileNameLength}); + } +} diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 00000000..1eb8d40d --- /dev/null +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,18 @@ +package org.dromara.common.core.exception.file; + +import java.io.Serial; + +/** + * 文件名大小限制异常类 + * + * @author ruoyi + */ +public class FileSizeLimitExceededException extends FileException { + + @Serial + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) { + super("upload.exceed.maxSize", new Object[]{defaultMaxSize}); + } +} diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java index b78a7f25..d80395cc 100644 --- a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java @@ -1,5 +1,9 @@ package org.dromara.common.core.service; +import org.dromara.common.core.domain.dto.DictDataDTO; +import org.dromara.common.core.domain.dto.DictTypeDTO; + +import java.util.List; import java.util.Map; /** @@ -64,4 +68,20 @@ public interface DictService { */ Map getAllDictByDictType(String dictType); + /** + * 根据字典类型查询详细信息 + * + * @param dictType 字典类型 + * @return 字典类型详细信息 + */ + DictTypeDTO getDictType(String dictType); + + /** + * 根据字典类型查询字典数据列表 + * + * @param dictType 字典类型 + * @return 字典数据列表 + */ + List getDictData(String dictType); + } diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java index 67cd54fb..4903c386 100644 --- a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java @@ -3,6 +3,7 @@ package org.dromara.common.core.service; import org.dromara.common.core.domain.dto.UserDTO; import java.util.List; +import java.util.Map; /** * 通用 用户服务 @@ -91,4 +92,36 @@ public interface UserService { */ List selectUsersByPostIds(List postIds); + /** + * 根据用户 ID 列表查询用户名称映射关系 + * + * @param userIds 用户 ID 列表 + * @return Map,其中 key 为用户 ID,value 为对应的用户名称 + */ + Map selectUserNamesByIds(List userIds); + + /** + * 根据角色 ID 列表查询角色名称映射关系 + * + * @param roleIds 角色 ID 列表 + * @return Map,其中 key 为角色 ID,value 为对应的角色名称 + */ + Map selectRoleNamesByIds(List roleIds); + + /** + * 根据部门 ID 列表查询部门名称映射关系 + * + * @param deptIds 部门 ID 列表 + * @return Map,其中 key 为部门 ID,value 为对应的部门名称 + */ + Map selectDeptNamesByIds(List deptIds); + + /** + * 根据岗位 ID 列表查询岗位名称映射关系 + * + * @param postIds 岗位 ID 列表 + * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称 + */ + Map selectPostNamesByIds(List postIds); + } diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java index abbcbff1..9d1a9019 100644 --- a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/WorkflowService.java @@ -78,9 +78,18 @@ public interface WorkflowService { /** * 办理任务 + * 系统后台发起审批 无用户信息 需要忽略权限 + * completeTask.getVariables().put("ignore", true); * * @param completeTask 参数 - * @return 结果 */ boolean completeTask(CompleteTaskDTO completeTask); + + /** + * 办理任务 + * + * @param taskId 任务ID + * @param message 办理意见 + */ + boolean completeTask(Long taskId, String message); } diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java index 2ab42cbc..5f60ebff 100644 --- a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java @@ -10,6 +10,8 @@ import lombok.NoArgsConstructor; import org.dromara.common.core.utils.reflect.ReflectUtils; import java.util.List; +import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -60,6 +62,31 @@ public class TreeBuildUtils extends TreeUtil { return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser); } + /** + * 构建多根节点的树结构(支持多个顶级节点) + * + * @param list 原始数据列表 + * @param getId 获取节点 ID 的方法引用,例如:node -> node.getId() + * @param getParentId 获取节点父级 ID 的方法引用,例如:node -> node.getParentId() + * @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点 + * @param 原始数据类型(如实体类、DTO 等) + * @param 节点 ID 类型(如 Long、String) + * @return 构建完成的树形结构(可能包含多个顶级根节点) + */ + public static List> buildMultiRoot(List list, Function getId, Function getParentId, NodeParser parser) { + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + + Set rootParentIds = StreamUtils.toSet(list, getParentId); + rootParentIds.removeAll(StreamUtils.toSet(list, getId)); + + // 构建每一个根 parentId 下的树,并合并成最终结果列表 + return rootParentIds.stream() + .flatMap(rootParentId -> TreeUtil.build(list, rootParentId, parser).stream()) + .collect(Collectors.toList()); + } + /** * 获取节点列表中所有节点的叶子节点 * diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java new file mode 100644 index 00000000..1e5d1606 --- /dev/null +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java @@ -0,0 +1,74 @@ +package org.dromara.common.core.utils.file; + +import cn.hutool.core.io.FileUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * 文件处理工具类 + * + * @author Lion Li + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class FileUtils extends FileUtil { + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) { + String percentEncodedFileName = percentEncode(realFileName); + String contentDispositionValue = "attachment; filename=%s;filename*=utf-8''%s".formatted(percentEncodedFileName, percentEncodedFileName); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 删除目录 + * + * @param path 路径 + * @throws IOException I/O异常 + */ + public static void deleteDirectory(Path path) throws IOException { + // walkFileTree会递归遍历path下所有文件和文件夹 + Files.walkFileTree(path, new SimpleFileVisitor<>() { + // 先删除文件 + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + // 然后删除目录 + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } +} diff --git a/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java new file mode 100644 index 00000000..23fa2cfe --- /dev/null +++ b/xinnengyuan/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java @@ -0,0 +1,40 @@ +package org.dromara.common.core.utils.file; + +/** + * 媒体类型工具类 + * + * @author ruoyi + */ +public class MimeTypeUtils { + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"}; + + public static final String[] FLASH_EXTENSION = {"swf", "flv"}; + + public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb"}; + + public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"}; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf"}; + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java index 1be0b7f4..29f64ab7 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java @@ -1,5 +1,6 @@ package org.dromara.system.service.impl; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -8,6 +9,8 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.CacheNames; +import org.dromara.common.core.domain.dto.DictDataDTO; +import org.dromara.common.core.domain.dto.DictTypeDTO; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.service.DictService; import org.dromara.common.core.utils.MapstructUtils; @@ -255,4 +258,28 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService return StreamUtils.toMap(list, SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel); } + /** + * 根据字典类型查询详细信息 + * + * @param dictType 字典类型 + * @return 字典类型详细信息 + */ + @Override + public DictTypeDTO getDictType(String dictType) { + SysDictTypeVo vo = SpringUtils.getAopProxy(this).selectDictTypeByType(dictType); + return BeanUtil.toBean(vo, DictTypeDTO.class); + } + + /** + * 根据字典类型查询字典数据列表 + * + * @param dictType 字典类型 + * @return 字典数据列表 + */ + @Override + public List getDictData(String dictType) { + List list = SpringUtils.getAopProxy(this).selectDictDataByType(dictType); + return BeanUtil.copyToList(list, DictDataDTO.class); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java index 6587a3d9..0237e5be 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java @@ -38,10 +38,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** @@ -746,4 +743,80 @@ public class SysUserServiceImpl implements ISysUserService, UserService { return selectListByIds(new ArrayList<>(userIds)); } + /** + * 根据用户 ID 列表查询用户名称映射关系 + * + * @param userIds 用户 ID 列表 + * @return Map,其中 key 为用户 ID,value 为对应的用户名称 + */ + @Override + public Map selectUserNamesByIds(List userIds) { + if (CollUtil.isEmpty(userIds)) { + return Collections.emptyMap(); + } + return baseMapper.selectList( + new LambdaQueryWrapper() + .select(SysUser::getUserId, SysUser::getNickName) + .in(SysUser::getUserId, userIds) + ).stream() + .collect(Collectors.toMap(SysUser::getUserId, SysUser::getNickName)); + } + + /** + * 根据角色 ID 列表查询角色名称映射关系 + * + * @param roleIds 角色 ID 列表 + * @return Map,其中 key 为角色 ID,value 为对应的角色名称 + */ + @Override + public Map selectRoleNamesByIds(List roleIds) { + if (CollUtil.isEmpty(roleIds)) { + return Collections.emptyMap(); + } + return roleMapper.selectList( + new LambdaQueryWrapper() + .select(SysRole::getRoleId, SysRole::getRoleName) + .in(SysRole::getRoleId, roleIds) + ).stream() + .collect(Collectors.toMap(SysRole::getRoleId, SysRole::getRoleName)); + } + + /** + * 根据部门 ID 列表查询部门名称映射关系 + * + * @param deptIds 部门 ID 列表 + * @return Map,其中 key 为部门 ID,value 为对应的部门名称 + */ + @Override + public Map selectDeptNamesByIds(List deptIds) { + if (CollUtil.isEmpty(deptIds)) { + return Collections.emptyMap(); + } + return deptMapper.selectList( + new LambdaQueryWrapper() + .select(SysDept::getDeptId, SysDept::getDeptName) + .in(SysDept::getDeptId, deptIds) + ).stream() + .collect(Collectors.toMap(SysDept::getDeptId, SysDept::getDeptName)); + } + + /** + * 根据岗位 ID 列表查询岗位名称映射关系 + * + * @param postIds 岗位 ID 列表 + * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称 + */ + @Override + public Map selectPostNamesByIds(List postIds) { + if (CollUtil.isEmpty(postIds)) { + return Collections.emptyMap(); + } + return postMapper.selectList( + new LambdaQueryWrapper() + .select(SysPost::getPostId, SysPost::getPostName) + .in(SysPost::getPostId, postIds) + ).stream() + .collect(Collectors.toMap(SysPost::getPostId, SysPost::getPostName)); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java index 5d24b350..e844398e 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/ConditionalOnEnable.java @@ -7,6 +7,21 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * 自定义条件注解,用于基于配置启用或禁用特定功能 + *

+ * 该注解只会在配置文件中 `warm-flow.enabled=true` 时,标注了此注解的类或方法才会被 Spring 容器加载 + *

+ * 示例配置: + *

+ * warm-flow:
+ *   enabled: true  # 设置为 true 时,启用工作流功能
+ * 
+ *

+ * 使用此注解时,可以动态控制工作流功能是否启用,而不需要修改代码逻辑 + * + * @author Lion Li + */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true") diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java index 1b10eb8e..aaa640bf 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/constant/FlowConstant.java @@ -13,21 +13,11 @@ public interface FlowConstant { */ String INITIATOR = "initiator"; - /** - * 流程实例id - */ - String PROCESS_INSTANCE_ID = "processInstanceId"; - /** * 业务id */ String BUSINESS_ID = "businessId"; - /** - * 任务id - */ - String TASK_ID = "taskId"; - /** * 委托 */ @@ -63,4 +53,29 @@ public interface FlowConstant { */ Long FLOW_CATEGORY_ID = 100L; + /** + * 是否为申请人提交常量 + */ + String SUBMIT = "submit"; + + /** + * 抄送常量 + */ + String FLOW_COPY_LIST = "flowCopyList"; + + /** + * 消息类型常量 + */ + String MESSAGE_TYPE = "messageType"; + + /** + * 消息通知常量 + */ + String MESSAGE_NOTICE = "messageNotice"; + + /** + * 任务状态 + */ + String WF_TASK_STATUS = "wf_task_status"; + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java new file mode 100644 index 00000000..598cd05c --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java @@ -0,0 +1,65 @@ +package org.dromara.workflow.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 按钮权限枚举 + * + * @author AprilWind + */ +@Getter +@AllArgsConstructor +public enum ButtonPermissionEnum implements NodeExtEnum { + + /** + * 是否弹窗选人 + */ + POP("是否弹窗选人", "pop", false), + + /** + * 是否能委托 + */ + TRUST("是否能委托", "trust", false), + + /** + * 是否能转办 + */ + TRANSFER("是否能转办", "transfer", false), + + /** + * 是否能抄送 + */ + COPY("是否能抄送", "copy", false), + + /** + * 是否显示退回 + */ + BACK("是否显示退回", "back", true), + + /** + * 是否能加签 + */ + ADD_SIGN("是否能加签", "addSign", false), + + /** + * 是否能减签 + */ + SUB_SIGN("是否能减签", "subSign", false), + + /** + * 是否能终止 + */ + TERMINATION("是否能终止", "termination", true), + + /** + * 是否能上传附件 + */ + FILE("是否能上传附件", "file", true); + + private final String label; + private final String value; + private final boolean selected; + +} + diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java new file mode 100644 index 00000000..9926a8eb --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java @@ -0,0 +1,32 @@ +package org.dromara.workflow.common.enums; + +/** + * 节点扩展属性枚举 + * + * @author AprilWind + */ +public interface NodeExtEnum { + + /** + * 选项label + * + * @return 选项label + */ + String getLabel(); + + /** + * 选项值 + * + * @return 选项值 + */ + String getValue(); + + /** + * 是否默认选中 + * + * @return 是否默认选中 + */ + boolean isSelected(); + +} + diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java index 37d414fc..3018b084 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java @@ -14,6 +14,7 @@ import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.web.core.BaseController; import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.common.constant.FlowConstant; import org.dromara.workflow.domain.bo.FlowCategoryBo; import org.dromara.workflow.domain.vo.FlowCategoryVo; import org.dromara.workflow.service.IFlwCategoryService; @@ -110,6 +111,9 @@ public class FlwCategoryController extends BaseController { @Log(title = "流程分类", businessType = BusinessType.DELETE) @DeleteMapping("/{categoryId}") public R remove(@PathVariable Long categoryId) { + if (FlowConstant.FLOW_CATEGORY_ID.equals(categoryId)) { + return R.warn("默认流程分类,不允许删除"); + } if (flwCategoryService.hasChildByCategoryId(categoryId)) { return R.warn("存在下级流程分类,不允许删除"); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java index ae99c16f..e7ea5e4c 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java @@ -127,9 +127,9 @@ public class FlwInstanceController extends BaseController { * * @param businessId 业务id */ - @GetMapping("/flowImage/{businessId}") - public R> flowImage(@PathVariable String businessId) { - return R.ok(flwInstanceService.flowImage(businessId)); + @GetMapping("/flowHisTaskList/{businessId}") + public R> flowHisTaskList(@PathVariable String businessId) { + return R.ok(flwInstanceService.flowHisTaskList(businessId)); } /** diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java index 463916b1..5534b673 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java @@ -12,6 +12,7 @@ import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.web.core.BaseController; import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.orm.entity.FlowNode; import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.domain.bo.*; import org.dromara.workflow.domain.vo.FlowHisTaskVo; @@ -127,6 +128,16 @@ public class FlwTaskController extends BaseController { return R.ok(flwTaskService.selectById(taskId)); } + /** + * 获取下一节点信息 + * + * @param bo 参数 + */ + @PostMapping("/getNextNodeList") + public R> getNextNodeList(@RequestBody FlowNextNodeBo bo) { + return R.ok(flwTaskService.getNextNodeList(bo)); + } + /** * 终止任务 * diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java index 86ac1ac3..28918f1d 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/FlowCategory.java @@ -1,5 +1,6 @@ package org.dromara.workflow.domain; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; @@ -8,6 +9,8 @@ import lombok.EqualsAndHashCode; import org.dromara.common.tenant.core.TenantEntity; import java.io.Serial; +import java.util.ArrayList; +import java.util.List; /** * 流程分类对象 wf_category @@ -55,4 +58,10 @@ public class FlowCategory extends TenantEntity { @TableLogic private String delFlag; + /** + * 子菜单 + */ + @TableField(exist = false) + private List children = new ArrayList<>(); + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java index 3117a33f..a67a1f78 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/BackProcessBo.java @@ -1,6 +1,5 @@ package org.dromara.workflow.domain.bo; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; import org.dromara.common.core.validate.AddGroup; @@ -43,7 +42,6 @@ public class BackProcessBo implements Serializable { /** * 驳回的节点id(目前未使用,直接驳回到申请人) */ - @NotBlank(message = "驳回的节点不能为空", groups = AddGroup.class) private String nodeCode; /** diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java index 9fdf4847..360fc3b8 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java @@ -58,15 +58,20 @@ public class CompleteTaskBo implements Serializable { */ private Map variables; + /** + * 弹窗选择的办理人 + */ + private Map assigneeMap; + /** * 扩展变量(此处为逗号分隔的ossId) - * @return */ private String ext; public Map getVariables() { if (variables == null) { - return new HashMap<>(16); + variables = new HashMap<>(16); + return variables; } variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); return variables; diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java new file mode 100644 index 00000000..12f0653e --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java @@ -0,0 +1,38 @@ +package org.dromara.workflow.domain.bo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 下一节点信息 + * + * @author may + */ +@Data +public class FlowNextNodeBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * 任务id + */ + private Long taskId; + + /** + * 流程变量 + */ + private Map variables; + + public Map getVariables() { + if (variables == null) { + return new HashMap<>(16); + } + variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + return variables; + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java index a1a4b596..395f71d9 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/TestLeaveBo.java @@ -10,6 +10,7 @@ import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; import org.dromara.common.mybatis.core.domain.BaseEntity; import org.dromara.workflow.domain.TestLeave; +import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; @@ -40,6 +41,7 @@ public class TestLeaveBo extends BaseEntity { * 开始时间 */ @NotNull(message = "开始时间不能为空", groups = {AddGroup.class, EditGroup.class}) + @DateTimeFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd") private Date startDate; @@ -47,6 +49,7 @@ public class TestLeaveBo extends BaseEntity { * 结束时间 */ @NotNull(message = "结束时间不能为空", groups = {AddGroup.class, EditGroup.class}) + @DateTimeFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd") private Date endDate; diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermissionVo.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermissionVo.java new file mode 100644 index 00000000..7175e5e0 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermissionVo.java @@ -0,0 +1,43 @@ +package org.dromara.workflow.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 按钮权限 + * + * @author may + * @date 2025-02-28 + */ +@Data +public class ButtonPermissionVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 唯一编码 + */ + private String code; + + /** + * 选项值 + */ + private String value; + + /** + * 是否显示 + */ + private Boolean show; + + public ButtonPermissionVo() { + } + + public ButtonPermissionVo(String code, Boolean show) { + this.code = code; + this.show = show; + } + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java index c5d27855..37d1bc80 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowCategoryVo.java @@ -4,13 +4,14 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; +import org.dromara.common.translation.annotation.Translation; +import org.dromara.workflow.common.constant.FlowConstant; import org.dromara.workflow.domain.FlowCategory; import java.io.Serial; import java.io.Serializable; import java.util.Date; - /** * 流程分类视图对象 wf_category * @@ -32,13 +33,14 @@ public class FlowCategoryVo implements Serializable { private Long categoryId; /** - * 父级id + * 父级分类id */ private Long parentId; /** - * 父类别名称 + * 父级分类名称 */ + @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "parentId") private String parentName; /** diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java index 3fb08d95..07a22c4f 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java @@ -173,4 +173,15 @@ public class FlowTaskVo implements Serializable { */ @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy") private String createByName; + + /** + * 是否为申请人节点 + */ + private Boolean applyNode; + + /** + * 按钮权限 + */ + private List buttonList; + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java index 4b215ef6..c465271c 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/FlowProcessEventHandler.java @@ -1,11 +1,12 @@ package org.dromara.workflow.handler; import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.event.ProcessTaskEvent; import org.dromara.common.core.domain.event.ProcessDeleteEvent; import org.dromara.common.core.domain.event.ProcessEvent; -import org.dromara.common.core.domain.event.ProcessTaskEvent; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.warm.flow.core.entity.Instance; import org.dromara.workflow.common.ConditionalOnEnable; import org.springframework.stereotype.Component; @@ -23,20 +24,25 @@ import java.util.Map; public class FlowProcessEventHandler { /** - * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成等) + * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等) * * @param flowCode 流程定义编码 - * @param businessId 业务id - * @param status 状态 + * @param instance 实例数据 + * @param status 流程状态 + * @param params 办理参数 * @param submit 当为true时为申请人节点办理 */ - public void processHandler(String flowCode, String businessId, String status, Map params, boolean submit) { + public void processHandler(String flowCode, Instance instance, String status, Map params, boolean submit) { String tenantId = TenantHelper.getTenantId(); - log.info("发布流程事件,租户ID: {}, 流程状态: {}, 流程编码: {}, 业务ID: {}, 是否申请人节点办理: {}", tenantId, status, flowCode, businessId, submit); + log.info("【流程事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 流程状态: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 是否申请人节点: {}, 参数: {}", + tenantId, flowCode, instance.getBusinessId(), status, instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), submit, params); ProcessEvent processEvent = new ProcessEvent(); processEvent.setTenantId(tenantId); processEvent.setFlowCode(flowCode); - processEvent.setBusinessId(businessId); + processEvent.setBusinessId(instance.getBusinessId()); + processEvent.setNodeType(instance.getNodeType()); + processEvent.setNodeCode(instance.getNodeCode()); + processEvent.setNodeName(instance.getNodeName()); processEvent.setStatus(status); processEvent.setParams(params); processEvent.setSubmit(submit); @@ -44,22 +50,25 @@ public class FlowProcessEventHandler { } /** - * 执行办理任务监听 + * 执行创建任务监听 * * @param flowCode 流程定义编码 - * @param nodeCode 审批节点编码 + * @param instance 实例数据 * @param taskId 任务id - * @param businessId 业务id */ - public void processTaskHandler(String flowCode, String nodeCode, Long taskId, String businessId) { + public void processTaskHandler(String flowCode, Instance instance, Long taskId) { String tenantId = TenantHelper.getTenantId(); - log.info("发布流程任务事件, 租户ID: {}, 流程编码: {}, 节点编码: {}, 任务ID: {}, 业务ID: {}", tenantId, flowCode, nodeCode, taskId, businessId); + log.info("【流程任务事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 任务ID: {}", + tenantId, flowCode, instance.getBusinessId(), instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), taskId); ProcessTaskEvent processTaskEvent = new ProcessTaskEvent(); processTaskEvent.setTenantId(tenantId); processTaskEvent.setFlowCode(flowCode); - processTaskEvent.setNodeCode(nodeCode); + processTaskEvent.setBusinessId(instance.getBusinessId()); + processTaskEvent.setNodeType(instance.getNodeType()); + processTaskEvent.setNodeCode(instance.getNodeCode()); + processTaskEvent.setNodeName(instance.getNodeName()); processTaskEvent.setTaskId(taskId); - processTaskEvent.setBusinessId(businessId); + processTaskEvent.setStatus(instance.getFlowStatus()); SpringUtils.context().publishEvent(processTaskEvent); } @@ -71,7 +80,7 @@ public class FlowProcessEventHandler { */ public void processDeleteHandler(String flowCode, String businessId) { String tenantId = TenantHelper.getTenantId(); - log.info("发布删除流程事件, 租户ID: {}, 流程编码: {}, 业务ID: {}", tenantId, flowCode, businessId); + log.info("【流程删除事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}", tenantId, flowCode, businessId); ProcessDeleteEvent processDeleteEvent = new ProcessDeleteEvent(); processDeleteEvent.setTenantId(tenantId); processDeleteEvent.setFlowCode(flowCode); diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java index c18e4ed5..f9ede15c 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/handler/WorkflowPermissionHandler.java @@ -1,22 +1,17 @@ package org.dromara.workflow.handler; -import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.collection.CollUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.dromara.common.core.domain.model.LoginUser; -import org.dromara.workflow.common.ConditionalOnEnable; -import org.dromara.workflow.common.enums.TaskAssigneeEnum; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.warm.flow.core.dto.FlowParams; import org.dromara.warm.flow.core.handler.PermissionHandler; -import org.dromara.warm.flow.core.service.impl.TaskServiceImpl; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.service.IFlwCommonService; import org.springframework.stereotype.Component; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * 办理人权限处理器 @@ -29,35 +24,16 @@ import java.util.stream.Stream; @Slf4j public class WorkflowPermissionHandler implements PermissionHandler { + private final IFlwCommonService flwCommonService; + /** - * 审批前获取当前办理人,办理时会校验的该权限集合 - * 后续在{@link TaskServiceImpl#checkAuth(Task, FlowParams)} 中调用 + * 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理任务 + * 后续在{@link FlowParams#getPermissionFlag} 中获取 * 返回当前用户权限集合 */ @Override public List permissions() { - LoginUser loginUser = LoginHelper.getLoginUser(); - if (ObjectUtil.isNull(loginUser)) { - return new ArrayList<>(); - } - // 使用一个流来构建权限列表 - return Stream.of( - // 角色权限前缀 - loginUser.getRoles().stream() - .map(role -> TaskAssigneeEnum.ROLE.getCode() + role.getRoleId()), - - // 岗位权限前缀 - Stream.ofNullable(loginUser.getPosts()) - .flatMap(Collection::stream) - .map(post -> TaskAssigneeEnum.POST.getCode() + post.getPostId()), - - // 用户和部门权限 - Stream.of(String.valueOf(loginUser.getUserId()), - TaskAssigneeEnum.DEPT.getCode() + loginUser.getDeptId() - ) - ) - .flatMap(stream -> stream) - .collect(Collectors.toList()); + return Collections.singletonList(LoginHelper.getUserIdStr()); } /** @@ -70,4 +46,14 @@ public class WorkflowPermissionHandler implements PermissionHandler { return LoginHelper.getUserIdStr(); } + /** + * 转换办理人,比如设计器中预设了能办理的人,如果其中包含角色或者部门id等,可以通过此接口进行转换成用户id + */ + @Override + public List convertPermissions(List permissions) { + if (CollUtil.isNotEmpty(permissions)) { + permissions = flwCommonService.buildUser(permissions); + } + return permissions; + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java index b1878548..c1ef2622 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java @@ -1,20 +1,28 @@ package org.dromara.workflow.listener; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjectUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.enums.BusinessStatusEnum; import org.dromara.common.core.utils.StringUtils; +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; 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.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.common.constant.FlowConstant; +import org.dromara.workflow.common.enums.TaskStatusEnum; +import org.dromara.workflow.domain.bo.FlowCopyBo; import org.dromara.workflow.handler.FlowProcessEventHandler; +import org.dromara.workflow.service.IFlwCommonService; import org.dromara.workflow.service.IFlwInstanceService; import org.dromara.workflow.service.IFlwTaskService; import org.springframework.stereotype.Component; @@ -34,9 +42,11 @@ import java.util.Map; @RequiredArgsConstructor public class WorkflowGlobalListener implements GlobalListener { - private final IFlwTaskService taskService; + private final IFlwTaskService flwTaskService; private final IFlwInstanceService instanceService; private final FlowProcessEventHandler flowProcessEventHandler; + private final IFlwCommonService flwCommonService; + private final InsService insService; /** * 创建监听器,任务创建时执行 @@ -45,15 +55,7 @@ public class WorkflowGlobalListener implements GlobalListener { */ @Override public void create(ListenerVariable listenerVariable) { - Instance instance = listenerVariable.getInstance(); - Definition definition = listenerVariable.getDefinition(); - String businessId = instance.getBusinessId(); - String flowStatus = instance.getFlowStatus(); - Task task = listenerVariable.getTask(); - if (task != null && BusinessStatusEnum.WAITING.getStatus().equals(flowStatus)) { - // 判断流程状态(发布审批中事件) - flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), task.getNodeCode(), task.getId(), businessId); - } + } /** @@ -72,6 +74,25 @@ public class WorkflowGlobalListener implements GlobalListener { */ @Override public void assignment(ListenerVariable listenerVariable) { + Map variable = listenerVariable.getVariable(); + List nextTasks = listenerVariable.getNextTasks(); + FlowParams flowParams = listenerVariable.getFlowParams(); + Definition definition = listenerVariable.getDefinition(); + Instance instance = listenerVariable.getInstance(); + String applyNodeCode = flwCommonService.applyNodeCode(definition.getId()); + for (Task flowTask : nextTasks) { + // 如果办理或者退回并行存在需要指定办理人,则直接覆盖办理人 + if (variable.containsKey(flowTask.getNodeCode()) && (TaskStatusEnum.PASS.getStatus().equals(flowParams.getHisStatus()) + || TaskStatusEnum.BACK.getStatus().equals(flowParams.getHisStatus()))) { + String userIds = variable.get(flowTask.getNodeCode()).toString(); + flowTask.setPermissionList(List.of(userIds.split(StringUtils.SEPARATOR))); + variable.remove(flowTask.getNodeCode()); + } + // 如果是申请节点,则把启动人添加到办理人 + if (flowTask.getNodeCode().equals(applyNodeCode)) { + flowTask.setPermissionList(List.of(instance.getCreateBy())); + } + } } /** @@ -83,10 +104,10 @@ public class WorkflowGlobalListener implements GlobalListener { public void finish(ListenerVariable listenerVariable) { Instance instance = listenerVariable.getInstance(); Definition definition = listenerVariable.getDefinition(); - String businessId = instance.getBusinessId(); - String flowStatus = instance.getFlowStatus(); + Task task = listenerVariable.getTask(); Map params = new HashMap<>(); FlowParams flowParams = listenerVariable.getFlowParams(); + Map variable = new HashMap<>(); if (ObjectUtil.isNotNull(flowParams)) { // 历史任务扩展(通常为附件) params.put("hisTaskExt", flowParams.getHisTaskExt()); @@ -94,28 +115,70 @@ public class WorkflowGlobalListener implements GlobalListener { params.put("handler", flowParams.getHandler()); // 办理意见 params.put("message", flowParams.getMessage()); + variable = flowParams.getVariable(); } - // 判断流程状态(发布:撤销,退回,作废,终止,已完成事件) - String status = determineFlowStatus(instance, flowStatus); - if (StringUtils.isNotBlank(status)) { - flowProcessEventHandler.processHandler(definition.getFlowCode(), businessId, status, params, false); + //申请人提交事件 + Boolean submit = MapUtil.getBool(variable, FlowConstant.SUBMIT); + if (submit != null && submit) { + flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, instance.getFlowStatus(), variable, true); + } else { + // 判断流程状态(发布:撤销,退回,作废,终止,已完成事件) + String status = determineFlowStatus(instance); + if (StringUtils.isNotBlank(status)) { + flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, params, false); + } + } + //发布任务事件 + if (task != null) { + flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, task.getId()); + } + if (ObjectUtil.isNull(flowParams)) { + return; + } + // 只有办理或者退回的时候才执行消息通知和抄送 + if (TaskStatusEnum.PASS.getStatus().equals(flowParams.getHisStatus()) + || TaskStatusEnum.BACK.getStatus().equals(flowParams.getHisStatus())) { + if (variable != null) { + if (variable.containsKey(FlowConstant.FLOW_COPY_LIST)) { + List flowCopyList = (List) variable.get(FlowConstant.FLOW_COPY_LIST); + // 添加抄送人 + flwTaskService.setCopy(task, flowCopyList); + } + if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) { + List messageType = (List) variable.get(FlowConstant.MESSAGE_TYPE); + String notice = (String) variable.get(FlowConstant.MESSAGE_NOTICE); + // 消息通知 + if (CollUtil.isNotEmpty(messageType)) { + flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice); + } + } + FlowInstance ins = new FlowInstance(); + Map 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); + } } } /** - * 根据流程实例和当前流程状态确定最终状态 + * 根据流程实例确定最终状态 * - * @param instance 流程实例 - * @param flowStatus 流程实例当前状态 + * @param instance 流程实例 * @return 流程最终状态 */ - private String determineFlowStatus(Instance instance, String flowStatus) { + private String determineFlowStatus(Instance instance) { + String flowStatus = instance.getFlowStatus(); if (StringUtils.isNotBlank(flowStatus) && BusinessStatusEnum.initialState(flowStatus)) { log.info("流程实例当前状态: {}", flowStatus); return flowStatus; } else { Long instanceId = instance.getId(); - List flowTasks = taskService.selectByInstId(instanceId); + List flowTasks = flwTaskService.selectByInstId(instanceId); if (CollUtil.isEmpty(flowTasks)) { String status = BusinessStatusEnum.FINISH.getStatus(); // 更新流程状态为已完成 diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java index d2c0b3a4..4a59f258 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/mapper/FlwCategoryMapper.java @@ -29,7 +29,9 @@ public interface FlwCategoryMapper extends BaseMapperPlus().eq(FlowCategory::getCategoryId, categoryId)); + } /** * 根据父流程分类ID查询其所有子流程分类的列表 diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java new file mode 100644 index 00000000..662d599e --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java @@ -0,0 +1,37 @@ +package org.dromara.workflow.service; + +import java.util.List; + +/** + * 通用 工作流服务 + * + * @author LionLi + */ +public interface IFlwCommonService { + + /** + * 构建工作流用户 + * + * @param permissionList 办理用户 + * @return 用户 + */ + List buildUser(List permissionList); + + /** + * 发送消息 + * + * @param flowName 流程定义名称 + * @param instId 实例id + * @param messageType 消息类型 + * @param message 消息内容,为空则发送默认配置的消息内容 + */ + void sendMessage(String flowName, Long instId, List messageType, String message); + + /** + * 申请人节点编码 + * + * @param definitionId 流程定义id + * @return 申请人节点编码 + */ + String applyNodeCode(Long definitionId); +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java index 1a2d29f7..54743b72 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwDefinitionService.java @@ -35,7 +35,6 @@ public interface IFlwDefinitionService { */ TableDataInfo unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery); - /** * 发布流程定义 * diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java index 99729c2a..01e5124b 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java @@ -107,7 +107,7 @@ public interface IFlwInstanceService { * @param businessId 业务id * @return 结果 */ - Map flowImage(String businessId); + Map flowHisTaskList(String businessId); /** * 按照实例id更新状态 diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java new file mode 100644 index 00000000..95951653 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwNodeExtService.java @@ -0,0 +1,22 @@ +package org.dromara.workflow.service; + +import org.dromara.workflow.domain.vo.ButtonPermissionVo; + +import java.util.List; + +/** + * 流程节点扩展属性 服务层 + * + * @author AprilWind + */ +public interface IFlwNodeExtService { + + /** + * 从扩展属性构建按钮权限列表:根据 ext 中记录的权限值,标记每个按钮是否勾选 + * + * @param ext 扩展属性 JSON 字符串 + * @return 按钮权限 VO 列表 + */ + List buildButtonPermissionsFromExt(String ext); + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java index 116cb74f..830abaf5 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskAssigneeService.java @@ -12,11 +12,13 @@ import java.util.List; public interface IFlwTaskAssigneeService { /** - * 根据存储标识符(storageId)解析分配类型和ID,并获取对应的用户列表 + * 批量解析多个存储标识符(storageIds),按类型分类并合并查询用户列表 + * 输入格式支持多个以逗号分隔的标识(如 "user:123,role:456,789") + * 会自动去重返回结果,非法格式的标识将被忽略 * - * @param storageId 包含分配类型和ID的字符串(例如 "user:123" 或 "role:456") - * @return 与分配类型和ID匹配的用户列表,如果格式无效则返回空列表 + * @param storageIds 多个存储标识符字符串(逗号分隔) + * @return 合并后的用户列表,去重后返回,非法格式的标识将被跳过 */ - List fetchUsersByStorageId(String storageId); + List fetchUsersByStorageIds(String storageIds); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java index 11034e75..e172c001 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java @@ -5,7 +5,9 @@ import org.dromara.common.core.domain.dto.UserDTO; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Task; import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.entity.FlowNode; import org.dromara.warm.flow.orm.entity.FlowTask; import org.dromara.workflow.domain.bo.*; import org.dromara.workflow.domain.vo.FlowHisTaskVo; @@ -37,6 +39,14 @@ public interface IFlwTaskService { */ boolean completeTask(CompleteTaskBo completeTaskBo); + /** + * 添加抄送人 + * + * @param task 任务信息 + * @param flowCopyList 抄送人 + */ + void setCopy(Task task, List flowCopyList); + /** * 查询当前用户的待办任务 * @@ -132,6 +142,14 @@ public interface IFlwTaskService { */ FlowTaskVo selectById(Long taskId); + /** + * 获取下一节点信息 + * + * @param bo 参数 + * @return 结果 + */ + List getNextNodeList(FlowNextNodeBo bo); + /** * 按照任务id查询任务 * @@ -188,4 +206,13 @@ public interface IFlwTaskService { * @return 结果 */ List currentTaskAllUser(Long taskId); + + /** + * 按照节点编码查询节点 + * + * @param nodeCode 节点编码 + * @param definitionId 流程定义id + * @return 节点 + */ + FlowNode getByNodeCode(String nodeCode, Long definitionId); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java index 8c73b59e..883a967a 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/CategoryNameTranslationImpl.java @@ -26,12 +26,6 @@ public class CategoryNameTranslationImpl implements TranslationInterface @Override public String translation(Object key, String other) { - Long id = null; - if (key instanceof String categoryId) { - id = Convert.toLong(categoryId); - } else if (key instanceof Long categoryId) { - id = categoryId; - } - return flwCategoryService.selectCategoryNameById(id); + return flwCategoryService.selectCategoryNameById(Convert.toLong(key)); } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java index db1b7b7f..d42a48a2 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCategoryServiceImpl.java @@ -8,7 +8,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.exception.ServiceException; -import org.dromara.common.core.utils.*; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.ObjectUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.TreeBuildUtils; import org.dromara.common.mybatis.helper.DataBaseHelper; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.warm.flow.core.service.DefService; @@ -48,14 +51,7 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService { */ @Override public FlowCategoryVo queryById(Long categoryId) { - FlowCategoryVo category = baseMapper.selectVoById(categoryId); - if (ObjectUtil.isNull(category)) { - return null; - } - FlowCategoryVo parentCategory = baseMapper.selectVoOne(new LambdaQueryWrapper() - .select(FlowCategory::getCategoryName).eq(FlowCategory::getCategoryId, category.getParentId())); - category.setParentName(ObjectUtils.notNullGetter(parentCategory, FlowCategoryVo::getCategoryName)); - return category; + return baseMapper.selectVoById(categoryId); } /** @@ -95,27 +91,20 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService { */ @Override public List> selectCategoryTreeList(FlowCategoryBo category) { - LambdaQueryWrapper lqw = buildQueryWrapper(category); - List categorys = baseMapper.selectVoList(lqw); - if (CollUtil.isEmpty(categorys)) { + List categoryList = this.queryList(category); + if (CollUtil.isEmpty(categoryList)) { return CollUtil.newArrayList(); } - // 获取当前列表中每一个节点的parentId,然后在列表中查找是否有id与其parentId对应,若无对应,则表明此时节点列表中,该节点在当前列表中属于顶级节点 - List> treeList = CollUtil.newArrayList(); - for (FlowCategoryVo d : categorys) { - String parentId = d.getParentId().toString(); - FlowCategoryVo categoryVo = StreamUtils.findFirst(categorys, it -> it.getCategoryId().toString().equals(parentId)); - if (ObjectUtil.isNull(categoryVo)) { - List> trees = TreeBuildUtils.build(categorys, parentId, (dept, tree) -> - tree.setId(dept.getCategoryId().toString()) - .setParentId(dept.getParentId().toString()) - .setName(dept.getCategoryName()) - .setWeight(dept.getOrderNum())); - Tree tree = StreamUtils.findFirst(trees, it -> it.getId().equals(d.getCategoryId().toString())); - treeList.add(tree); - } - } - return treeList; + return TreeBuildUtils.buildMultiRoot( + categoryList, + node -> String.valueOf(node.getCategoryId()), + node -> String.valueOf(node.getParentId()), + (node, treeNode) -> treeNode + .setId(String.valueOf(node.getCategoryId())) + .setParentId(String.valueOf(node.getParentId())) + .setName(node.getCategoryName()) + .setWeight(node.getOrderNum()) + ); } /** diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java new file mode 100644 index 00000000..d7c6f77e --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java @@ -0,0 +1,247 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.dto.UserDTO; +import org.dromara.common.core.service.DeptService; +import org.dromara.common.core.service.DictService; +import org.dromara.common.core.service.UserService; +import org.dromara.common.core.utils.DateUtils; +import org.dromara.common.core.utils.ServletUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.NodeJson; +import org.dromara.warm.flow.core.dto.PromptContent; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.core.utils.MapUtil; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper; +import org.dromara.warm.flow.ui.service.ChartExtService; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.common.constant.FlowConstant; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * 流程图提示信息 + * + * @author AprilWind + */ +@ConditionalOnEnable +@Slf4j +@RequiredArgsConstructor +@Service +public class FlwChartExtServiceImpl implements ChartExtService { + + private final UserService userService; + private final DeptService deptService; + private final FlowHisTaskMapper flowHisTaskMapper; + private final DictService dictService; + + /** + * 设置流程图提示信息 + * + * @param defJson 流程定义json对象 + */ + @Override + public void execute(DefJson defJson) { + // 临时修复 后续版本将通过defjson获取流程实例ID + String[] parts = ServletUtils.getRequest().getRequestURI().split("/"); + Long instanceId = Convert.toLong(parts[parts.length - 1]); + + // 根据流程实例ID查询所有相关的历史任务列表 + List flowHisTasks = this.getHisTaskGroupedByNode(instanceId); + if (CollUtil.isEmpty(flowHisTasks)) { + return; + } + + // 按节点编号(nodeCode)对历史任务进行分组 + Map> groupedByNode = StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode); + + // 批量查询所有审批人的用户信息 + List userDTOList = userService.selectListByIds(StreamUtils.toList(flowHisTasks, e -> Convert.toLong(e.getApprover()))); + + // 将查询到的用户列表转换为以用户ID为key的映射 + Map userMap = StreamUtils.toIdentityMap(userDTOList, UserDTO::getUserId); + + Map dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS); + + // 遍历流程定义中的每个节点,调用处理方法,将对应节点的任务列表及用户信息传入,生成扩展提示内容 + for (NodeJson nodeJson : defJson.getNodeList()) { + // 获取当前节点对应的历史任务列表,如果没有则返回空列表避免空指针 + List taskList = groupedByNode.get(nodeJson.getNodeCode()); + if (CollUtil.isEmpty(taskList)) { + continue; + } + // 处理当前节点的扩展信息,包括构建审批人提示内容等 + this.processNodeExtInfo(nodeJson, taskList, userMap, dictType); + } + } + + /** + * 初始化流程图提示信息 + * + * @param defJson 流程定义json对象 + */ + @Override + public void initPromptContent(DefJson defJson) { + defJson.setTopText("流程名称: " + defJson.getFlowName()); + defJson.getNodeList().forEach(nodeJson -> { + nodeJson.setPromptContent( + new PromptContent() + // 提示信息 + .setInfo( + CollUtil.newArrayList( + new PromptContent.InfoItem() + .setPrefix("任务名称: ") + .setContent(nodeJson.getNodeName()) + .setContentStyle(Map.of( + "border", "1px solid #d1e9ff", + "backgroundColor", "#e8f4ff", + "padding", "4px 8px", + "borderRadius", "4px" + )) + .setRowStyle(Map.of( + "fontWeight", "bold", + "margin", "0 0 6px 0", + "padding", "0 0 8px 0", + "borderBottom", "1px solid #ccc" + )) + ) + ) + // 弹窗样式 + .setDialogStyle(MapUtil.mergeAll( + "position", "absolute", + "backgroundColor", "#fff", + "border", "1px solid #ccc", + "borderRadius", "4px", + "boxShadow", "0 2px 8px rgba(0, 0, 0, 0.15)", + "padding", "8px 12px", + "fontSize", "14px", + "zIndex", "1000", + "maxWidth", "500px", + "overflowY", "visible", + "overflowX", "hidden", + "color", "#333", + "pointerEvents", "auto", + "scrollbarWidth", "thin" + )) + ); + }); + } + + /** + * 处理节点的扩展信息,构建用于流程图悬浮提示的内容 + * + * @param nodeJson 当前节点对象 + * @param taskList 当前节点对应的历史审批任务列表 + */ + private void processNodeExtInfo(NodeJson nodeJson, List taskList, Map userMap, Map dictType) { + + // 获取节点提示内容对象中的 info 列表,用于追加提示项 + List info = nodeJson.getPromptContent().getInfo(); + + // 遍历所有任务记录,构建提示内容 + for (FlowHisTask task : taskList) { + UserDTO userDTO = userMap.get(Convert.toLong(task.getApprover())); + if (ObjectUtil.isEmpty(userDTO)) { + continue; + } + + // 查询用户所属部门名称 + String deptName = deptService.selectDeptNameByIds(Convert.toStr(userDTO.getDeptId())); + + // 添加标题项,如:👤 张三(市场部) + info.add(new PromptContent.InfoItem() + .setPrefix(StringUtils.format("👥 {}({})", userDTO.getNickName(), deptName)) + .setPrefixStyle(Map.of( + "fontWeight", "bold", + "fontSize", "15px", + "color", "#333" + )) + .setRowStyle(Map.of( + "margin", "8px 0", + "borderBottom", "1px dashed #ccc" + )) + ); + + // 添加具体信息项:账号、耗时、时间 + info.add(buildInfoItem("用户账号", userDTO.getUserName())); + info.add(buildInfoItem("审批状态", dictType.get(task.getFlowStatus()))); + info.add(buildInfoItem("审批耗时", DateUtils.getTimeDifference(task.getUpdateTime(), task.getCreateTime()))); + info.add(buildInfoItem("办理时间", DateUtils.formatDateTime(task.getUpdateTime()))); + } + } + + /** + * 构建单条提示内容对象 InfoItem,用于悬浮窗显示(key: value) + * + * @param key 字段名(作为前缀) + * @param value 字段值 + * @return 提示项对象 + */ + private PromptContent.InfoItem buildInfoItem(String key, String value) { + return new PromptContent.InfoItem() + // 前缀 + .setPrefix(key + ": ") + // 前缀样式 + .setPrefixStyle(Map.of( + "textAlign", "right", + "color", "#444", + "userSelect", "none", + "display", "inline-block", + "width", "100px", + "paddingRight", "8px", + "fontWeight", "500", + "fontSize", "14px", + "lineHeight", "24px", + "verticalAlign", "middle" + )) + // 内容 + .setContent(value) + // 内容样式 + .setContentStyle(Map.of( + "backgroundColor", "#f7faff", + "color", "#005cbf", + "padding", "4px 8px", + "fontSize", "14px", + "borderRadius", "4px", + "whiteSpace", "normal", + "border", "1px solid #d0e5ff", + "userSelect", "text", + "lineHeight", "20px" + )) + // 行样式 + .setRowStyle(Map.of( + "color", "#222", + "alignItems", "center", + "display", "flex", + "marginBottom", "6px", + "fontWeight", "400", + "fontSize", "14px" + )); + } + + /** + * 根据流程实例ID获取历史任务列表 + * + * @param instanceId 流程实例ID + * @return 历史任务列表 + */ + public List getHisTaskGroupedByNode(Long instanceId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(FlowHisTask::getInstanceId, instanceId) + .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()) + .orderByDesc(FlowHisTask::getCreateTime, FlowHisTask::getUpdateTime); + return flowHisTaskMapper.selectList(wrapper); + } + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java new file mode 100644 index 00000000..e6dc8155 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java @@ -0,0 +1,122 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.dto.UserDTO; +import org.dromara.common.core.utils.SpringUtils; +import org.dromara.common.core.utils.StreamUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mail.utils.MailUtils; +import org.dromara.common.sse.dto.SseMessageDto; +import org.dromara.common.sse.utils.SseMessageUtils; +import org.dromara.warm.flow.core.entity.Node; +import org.dromara.warm.flow.core.entity.Task; +import org.dromara.warm.flow.core.enums.SkipType; +import org.dromara.warm.flow.core.service.NodeService; +import org.dromara.warm.flow.orm.entity.FlowTask; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.common.enums.MessageTypeEnum; +import org.dromara.workflow.service.IFlwCommonService; +import org.dromara.workflow.service.IFlwTaskAssigneeService; +import org.dromara.workflow.service.IFlwTaskService; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * 工作流工具 + * + * @author LionLi + */ +@ConditionalOnEnable +@Slf4j +@RequiredArgsConstructor +@Service +public class FlwCommonServiceImpl implements IFlwCommonService { + private final NodeService nodeService; + + /** + * 构建工作流用户 + * + * @param permissionList 办理用户 + * @return 用户 + */ + @Override + public List buildUser(List permissionList) { + if (CollUtil.isEmpty(permissionList)) { + return List.of(); + } + IFlwTaskAssigneeService taskAssigneeService = SpringUtils.getBean(IFlwTaskAssigneeService.class); + String processedBys = CollUtil.join(permissionList, StringUtils.SEPARATOR); + // 根据 processedBy 前缀判断处理人类型,分别获取用户列表 + List users = taskAssigneeService.fetchUsersByStorageIds(processedBys); + + return StreamUtils.toList(users, userDTO -> String.valueOf(userDTO.getUserId())); + } + + + /** + * 发送消息 + * + * @param flowName 流程定义名称 + * @param messageType 消息类型 + * @param message 消息内容,为空则发送默认配置的消息内容 + */ + @Override + public void sendMessage(String flowName, Long instId, List messageType, String message) { + IFlwTaskService flwTaskService = SpringUtils.getBean(IFlwTaskService.class); + List userList = new ArrayList<>(); + List list = flwTaskService.selectByInstId(instId); + if (StringUtils.isBlank(message)) { + message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。"; + } + for (Task task : list) { + List users = flwTaskService.currentTaskAllUser(task.getId()); + if (CollUtil.isNotEmpty(users)) { + userList.addAll(users); + } + } + if (CollUtil.isNotEmpty(userList)) { + for (String code : messageType) { + MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code); + if (ObjectUtil.isNotEmpty(messageTypeEnum)) { + switch (messageTypeEnum) { + case SYSTEM_MESSAGE: + SseMessageDto dto = new SseMessageDto(); + dto.setUserIds(StreamUtils.toList(userList, UserDTO::getUserId).stream().distinct().collect(Collectors.toList())); + dto.setMessage(message); + SseMessageUtils.publishMessage(dto); + break; + case EMAIL_MESSAGE: + MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), "单据审批提醒", message); + break; + case SMS_MESSAGE: + //todo 短信发送 + break; + default: + throw new IllegalStateException("Unexpected value: " + messageTypeEnum); + } + } + } + } + } + + + /** + * 申请人节点编码 + * + * @param definitionId 流程定义id + * @return 申请人节点编码 + */ + @Override + public String applyNodeCode(Long definitionId) { + Node startNode = nodeService.getStartNode(definitionId); + Node nextNode = nodeService.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey()); + return nextNode.getNodeCode(); + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java index 591339b3..e595f46e 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java @@ -33,8 +33,8 @@ import org.dromara.workflow.common.constant.FlowConstant; import org.dromara.workflow.domain.FlowCategory; import org.dromara.workflow.domain.vo.FlowDefinitionVo; import org.dromara.workflow.mapper.FlwCategoryMapper; +import org.dromara.workflow.service.IFlwCommonService; import org.dromara.workflow.service.IFlwDefinitionService; -import org.dromara.workflow.utils.WorkflowUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -64,6 +64,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { private final FlowNodeMapper flowNodeMapper; private final FlowSkipMapper flowSkipMapper; private final FlwCategoryMapper flwCategoryMapper; + private final IFlwCommonService flwCommonService; /** * 查询流程定义列表 @@ -77,10 +78,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { LambdaQueryWrapper wrapper = buildQueryWrapper(flowDefinition); wrapper.eq(FlowDefinition::getIsPublish, PublishStatus.PUBLISHED.getKey()); Page page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper); - TableDataInfo build = TableDataInfo.build(); - build.setRows(BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class)); - build.setTotal(page.getTotal()); - return build; + List list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class); + return new TableDataInfo<>(list, page.getTotal()); } /** @@ -95,10 +94,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { LambdaQueryWrapper wrapper = buildQueryWrapper(flowDefinition); wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey())); Page page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper); - TableDataInfo build = TableDataInfo.build(); - build.setRows(BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class)); - build.setTotal(page.getTotal()); - return build; + List list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class); + return new TableDataInfo<>(list, page.getTotal()); } private LambdaQueryWrapper buildQueryWrapper(FlowDefinition flowDefinition) { @@ -125,7 +122,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { List errorMsg = new ArrayList<>(); if (CollUtil.isNotEmpty(flowNodes)) { for (FlowNode flowNode : flowNodes) { - String applyNodeCode = WorkflowUtils.applyNodeCode(id); + String applyNodeCode = flwCommonService.applyNodeCode(id); if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode()) && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) { errorMsg.add(flowNode.getNodeName()); } @@ -190,7 +187,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { List flowDefinitions = flowDefinitionMapper.selectByIds(StreamUtils.toList(flowHisTasks, FlowHisTask::getDefinitionId)); if (CollUtil.isNotEmpty(flowDefinitions)) { String join = StreamUtils.join(flowDefinitions, FlowDefinition::getFlowCode); - log.error("流程定义【{}】已被使用不可被删除!", join); + log.info("流程定义【{}】已被使用不可被删除!", join); throw new ServiceException("流程定义【" + join + "】已被使用不可被删除!"); } } @@ -219,6 +216,10 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService { .eq(FlowCategory::getTenantId, DEFAULT_TENANT_ID).eq(FlowCategory::getCategoryId, FlowConstant.FLOW_CATEGORY_ID)); flowCategory.setCategoryId(null); flowCategory.setTenantId(tenantId); + flowCategory.setCreateBy(null); + flowCategory.setCreateTime(null); + flowCategory.setUpdateBy(null); + flowCategory.setUpdateTime(null); flwCategoryMapper.insert(flowCategory); List defIds = StreamUtils.toList(flowDefinitions, FlowDefinition::getId); List flowNodes = flowNodeMapper.selectList(new LambdaQueryWrapper().in(FlowNode::getDefinitionId, defIds)); diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java index db8ab71b..a3e272f7 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java @@ -19,14 +19,12 @@ import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; -import org.dromara.warm.flow.core.FlowEngine; import org.dromara.warm.flow.core.constant.ExceptionCons; import org.dromara.warm.flow.core.dto.FlowParams; import org.dromara.warm.flow.core.entity.Definition; import org.dromara.warm.flow.core.entity.Instance; import org.dromara.warm.flow.core.entity.Task; import org.dromara.warm.flow.core.enums.NodeType; -import org.dromara.warm.flow.core.service.ChartService; import org.dromara.warm.flow.core.service.DefService; import org.dromara.warm.flow.core.service.InsService; import org.dromara.warm.flow.core.service.TaskService; @@ -42,13 +40,11 @@ import org.dromara.workflow.domain.bo.FlowInstanceBo; import org.dromara.workflow.domain.bo.FlowInvalidBo; import org.dromara.workflow.domain.vo.FlowHisTaskVo; import org.dromara.workflow.domain.vo.FlowInstanceVo; -import org.dromara.workflow.domain.vo.FlowVariableVo; import org.dromara.workflow.handler.FlowProcessEventHandler; import org.dromara.workflow.mapper.FlwCategoryMapper; import org.dromara.workflow.mapper.FlwInstanceMapper; import org.dromara.workflow.service.IFlwInstanceService; import org.dromara.workflow.service.IFlwTaskService; -import org.dromara.workflow.utils.WorkflowUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -68,7 +64,6 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { private final InsService insService; private final DefService defService; - private final ChartService chartService; private final TaskService taskService; private final FlowHisTaskMapper flowHisTaskMapper; private final FlowInstanceMapper flowInstanceMapper; @@ -185,7 +180,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { @Override @Transactional(rollbackFor = Exception.class) public boolean deleteByBusinessIds(List businessIds) { - List flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper().in(FlowInstance::getBusinessId, businessIds)); + List flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper().in(FlowInstance::getBusinessId, StreamUtils.toList(businessIds, Convert::toStr))); if (CollUtil.isEmpty(flowInstances)) { log.warn("未找到对应的流程实例信息,无法执行删除操作。"); return false; @@ -244,19 +239,15 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { throw new ServiceException(ExceptionCons.NOT_FOUNT_DEF); } String message = bo.getMessage(); + String userIdStr = LoginHelper.getUserIdStr(); BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus()); - String applyNodeCode = WorkflowUtils.applyNodeCode(definition.getId()); - //撤销 - WorkflowUtils.backTask(message, instance.getId(), applyNodeCode, BusinessStatusEnum.CANCEL.getStatus(), BusinessStatusEnum.CANCEL.getStatus()); - //判断或签节点是否有多个,只保留一个 - List currentTaskList = taskService.list(FlowEngine.newTask().setInstanceId(instance.getId())); - if (CollUtil.isNotEmpty(currentTaskList)) { - if (currentTaskList.size() > 1) { - currentTaskList.remove(0); - WorkflowUtils.deleteRunTask(StreamUtils.toList(currentTaskList, Task::getId)); - } - } - + FlowParams flowParams = FlowParams.build() + .message(message) + .flowStatus(BusinessStatusEnum.CANCEL.getStatus()) + .hisStatus(BusinessStatusEnum.CANCEL.getStatus()) + .handler(userIdStr) + .ignore(true); + taskService.revoke(instance.getId(), flowParams); } catch (Exception e) { log.error("撤销失败: {}", e.getMessage(), e); throw new ServiceException(e.getMessage()); @@ -284,7 +275,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { * @param businessId 业务id */ @Override - public Map flowImage(String businessId) { + public Map flowHisTaskList(String businessId) { FlowInstance flowInstance = this.selectInstByBusinessId(businessId); if (ObjectUtil.isNull(flowInstance)) { throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE); @@ -313,15 +304,14 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { } //历史任务 LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(FlowHisTask::getInstanceId, instanceId); - wrapper.eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()); - wrapper.orderByDesc(FlowHisTask::getCreateTime).orderByDesc(FlowHisTask::getUpdateTime); + wrapper.eq(FlowHisTask::getInstanceId, instanceId) + .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()) + .orderByDesc(FlowHisTask::getCreateTime, FlowHisTask::getUpdateTime); List flowHisTasks = flowHisTaskMapper.selectList(wrapper); if (CollUtil.isNotEmpty(flowHisTasks)) { list.addAll(BeanUtil.copyToList(flowHisTasks, FlowHisTaskVo.class)); } - String flowChart = chartService.chartIns(instanceId); - return Map.of("list", list, "image", flowChart); + return Map.of("list", list, "instanceId", instanceId); } /** @@ -345,21 +335,12 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { */ @Override public Map instanceVariable(Long instanceId) { - Map map = new HashMap<>(); FlowInstance flowInstance = flowInstanceMapper.selectById(instanceId); - Map variableMap = flowInstance.getVariableMap(); - List list = new ArrayList<>(); - if (CollUtil.isNotEmpty(variableMap)) { - for (Map.Entry entry : variableMap.entrySet()) { - FlowVariableVo flowVariableVo = new FlowVariableVo(); - flowVariableVo.setKey(entry.getKey()); - flowVariableVo.setValue(entry.getValue().toString()); - list.add(flowVariableVo); - } - } - map.put("variableList", list); - map.put("variable", flowInstance.getVariable()); - return map; + Map variableMap = Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap()); + List> variableList = variableMap.entrySet().stream() + .map(entry -> Map.of("key", entry.getKey(), "value", entry.getValue())) + .toList(); + return Map.of("variableList", variableList, "variable", flowInstance.getVariable()); } /** @@ -373,6 +354,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { Instance instance = insService.getById(instanceId); if (instance != null) { taskService.mergeVariable(instance, variable); + insService.updateById(instance); } } @@ -433,15 +415,12 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { if (instance != null) { BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus()); } - List flowTaskList = flwTaskService.selectByInstId(bo.getId()); - for (FlowTask flowTask : flowTaskList) { - FlowParams flowParams = new FlowParams(); - flowParams.message(bo.getComment()); - flowParams.flowStatus(BusinessStatusEnum.INVALID.getStatus()) - .hisStatus(TaskStatusEnum.INVALID.getStatus()); - flowParams.ignore(true); - taskService.termination(flowTask.getId(), flowParams); - } + FlowParams flowParams = FlowParams.build() + .message(bo.getComment()) + .flowStatus(BusinessStatusEnum.INVALID.getStatus()) + .hisStatus(TaskStatusEnum.INVALID.getStatus()) + .ignore(true); + taskService.terminationByInsId(bo.getId(), flowParams); return true; } catch (Exception e) { log.error(e.getMessage(), e); diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java new file mode 100644 index 00000000..fca21a36 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java @@ -0,0 +1,243 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.dto.DictTypeDTO; +import org.dromara.common.core.service.DictService; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.warm.flow.ui.service.NodeExtService; +import org.dromara.warm.flow.ui.vo.NodeExt; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.common.enums.ButtonPermissionEnum; +import org.dromara.workflow.common.enums.NodeExtEnum; +import org.dromara.workflow.domain.vo.ButtonPermissionVo; +import org.dromara.workflow.service.IFlwNodeExtService; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 流程设计器-节点扩展属性 + * + * @author AprilWind + */ +@ConditionalOnEnable +@Slf4j +@RequiredArgsConstructor +@Service +public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService { + + /** + * 存储不同 dictType 对应的配置信息 + */ + private static final Map CHILD_NODE_MAP = new HashMap<>(); + + record ButtonPermission(String label, Integer type, Boolean must, Boolean multiple) { + } + + static { + CHILD_NODE_MAP.put(ButtonPermissionEnum.class.getSimpleName(), + new ButtonPermission("权限按钮", 4, false, true)); + } + + private final DictService dictService; + + /** + * 获取节点扩展属性 + * + * @return 节点扩展属性列表 + */ + @Override + public List getNodeExt() { + List nodeExtList = new ArrayList<>(); + // 构建按钮权限页面 + nodeExtList.add(buildNodeExt("wf_button_tab", "权限", 2, + List.of(ButtonPermissionEnum.class))); + // 自定义构建 规则参考 NodeExt 与 warm-flow文档说明 + // nodeExtList.add(buildNodeExt("xxx_xxx", "xxx", 1, List); + return nodeExtList; + } + + /** + * 构建一个 `NodeExt` 对象 + * + * @param code 唯一编码 + * @param name 名称(新页签时,作为页签名称) + * @param type 节点类型(1: 基础设置,2: 新页签) + * @param sources 数据来源(枚举类或字典类型) + * @return 构建的 `NodeExt` 对象 + */ + @SuppressWarnings("unchecked cast") + private NodeExt buildNodeExt(String code, String name, int type, List sources) { + NodeExt nodeExt = new NodeExt(); + nodeExt.setCode(code); + nodeExt.setType(type); + nodeExt.setName(name); + nodeExt.setChilds(sources.stream() + .map(source -> { + if (source instanceof Class clazz && NodeExtEnum.class.isAssignableFrom(clazz)) { + return buildChildNode((Class) clazz); + } else if (source instanceof String dictType) { + return buildChildNode(dictType); + } + return null; + }) + .filter(ObjectUtil::isNotNull) + .toList() + ); + return nodeExt; + } + + /** + * 根据枚举类型构建一个 `ChildNode` 对象 + * + * @param enumClass 枚举类,必须实现 `NodeExtEnum` 接口 + * @return 构建的 `ChildNode` 对象 + */ + private NodeExt.ChildNode buildChildNode(Class enumClass) { + if (!enumClass.isEnum()) { + return null; + } + String simpleName = enumClass.getSimpleName(); + NodeExt.ChildNode childNode = buildChildNodeMap(simpleName); + // 编码,此json中唯 + childNode.setCode(simpleName); + // 字典,下拉框和复选框时用到 + childNode.setDict(Arrays.stream(enumClass.getEnumConstants()) + .map(NodeExtEnum.class::cast) + .map(x -> + new NodeExt.DictItem(x.getLabel(), x.getValue(), x.isSelected()) + ).toList()); + return childNode; + } + + /** + * 根据字典类型构建 `ChildNode` 对象 + * + * @param dictType 字典类型 + * @return 构建的 `ChildNode` 对象 + */ + private NodeExt.ChildNode buildChildNode(String dictType) { + DictTypeDTO dictTypeDTO = dictService.getDictType(dictType); + if (ObjectUtil.isNull(dictTypeDTO)) { + return null; + } + NodeExt.ChildNode childNode = buildChildNodeMap(dictType); + // 编码,此json中唯一 + childNode.setCode(dictType); + // label名称 + childNode.setLabel(dictTypeDTO.getDictName()); + // 描述 + childNode.setDesc(dictTypeDTO.getRemark()); + // 字典,下拉框和复选框时用到 + childNode.setDict(dictService.getDictData(dictType) + .stream().map(x -> + new NodeExt.DictItem(x.getDictLabel(), x.getDictValue(), Convert.toBool(x.getIsDefault(), false)) + ).toList()); + return childNode; + } + + /** + * 根据 CHILD_NODE_MAP 中的配置信息,构建一个基本的 ChildNode 对象 + * 该方法用于设置 ChildNode 的常规属性,例如 label、type、是否必填、是否多选等 + * + * @param key CHILD_NODE_MAP 的 key + * @return 返回构建好的 ChildNode 对象 + */ + private NodeExt.ChildNode buildChildNodeMap(String key) { + NodeExt.ChildNode childNode = new NodeExt.ChildNode(); + ButtonPermission bp = CHILD_NODE_MAP.get(key); + if (bp == null) { + childNode.setType(1); + childNode.setMust(false); + childNode.setMultiple(true); + return childNode; + } + // label名称 + childNode.setLabel(bp.label()); + // 1:输入框 2:输入框 3:下拉框 4:选择框 + childNode.setType(bp.type()); + // 是否必填 + childNode.setMust(bp.must()); + // 是否多选 + childNode.setMultiple(bp.multiple()); + return childNode; + } + + /** + * 从扩展属性构建按钮权限列表:根据 ext 中记录的权限值,标记每个按钮是否勾选 + * + * @param ext 扩展属性 JSON 字符串 + * @return 按钮权限 VO 列表 + */ + @Override + public List buildButtonPermissionsFromExt(String ext) { + // 解析 ext 为 Map>,用于标记权限 + Map> permissionMap = JsonUtils.parseArray(ext, ButtonPermissionVo.class) + .stream() + .collect(Collectors.toMap( + ButtonPermissionVo::getCode, + item -> StringUtils.splitList(item.getValue()).stream() + .map(String::trim) + .filter(StrUtil::isNotBlank) + .collect(Collectors.toSet()), + (a, b) -> b, + HashMap::new + )); + + // 构建按钮权限列表,标记哪些按钮在 permissionMap 中出现(表示已勾选) + return buildPermissionsFromSources(permissionMap, List.of(ButtonPermissionEnum.class)); + } + + /** + * 将权限映射与按钮权限来源(枚举类或字典类型)进行匹配,生成权限视图列表 + *

+ * 使用说明: + * - sources 支持传入多个来源类型,支持 NodeExtEnum 枚举类 或 字典类型字符串(dictType) + * - 若需要扩展更多按钮权限,只需在 sources 中新增对应的枚举类或字典类型 + *

+ * 示例: + * buildPermissionsFromSources(permissionMap, List.of(ButtonPermissionEnum.class, "custom_button_dict")); + * + * @param permissionMap 权限映射 + * @param sources 枚举类或字典类型列表 + * @return 按钮权限视图对象列表 + */ + @SuppressWarnings("unchecked cast") + private List buildPermissionsFromSources(Map> permissionMap, List sources) { + return sources.stream() + .flatMap(source -> { + if (source instanceof Class clazz && NodeExtEnum.class.isAssignableFrom(clazz)) { + Set selectedSet = permissionMap.getOrDefault(clazz.getSimpleName(), Collections.emptySet()); + return extractDictItems(this.buildChildNode((Class) clazz), selectedSet).stream(); + } else if (source instanceof String dictType) { + Set selectedSet = permissionMap.getOrDefault(dictType, Collections.emptySet()); + return extractDictItems(this.buildChildNode(dictType), selectedSet).stream(); + } + return Stream.empty(); + }).toList(); + } + + /** + * 从节点子项中提取字典项,并构建按钮权限视图对象列表 + * + * @param childNode 子节点 + * @param selectedSet 已选中的值集 + * @return 按钮权限视图对象列表 + */ + private List extractDictItems(NodeExt.ChildNode childNode, Set selectedSet) { + return Optional.ofNullable(childNode) + .map(NodeExt.ChildNode::getDict) + .orElse(List.of()) + .stream() + .map(dict -> new ButtonPermissionVo(dict.getValue(), selectedSet.contains(dict.getValue()))) + .toList(); + } + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java index 5877bb50..ceefa803 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java @@ -1,6 +1,9 @@ package org.dromara.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.Pair; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -9,7 +12,6 @@ import org.dromara.common.core.domain.dto.TaskAssigneeDTO; import org.dromara.common.core.domain.dto.UserDTO; import org.dromara.common.core.domain.model.TaskAssigneeBody; import org.dromara.common.core.enums.FormatsType; -import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.service.DeptService; import org.dromara.common.core.service.TaskAssigneeService; import org.dromara.common.core.service.UserService; @@ -19,15 +21,14 @@ import org.dromara.warm.flow.ui.dto.HandlerFunDto; import org.dromara.warm.flow.ui.dto.HandlerQuery; import org.dromara.warm.flow.ui.dto.TreeFunDto; import org.dromara.warm.flow.ui.service.HandlerSelectService; +import org.dromara.warm.flow.ui.vo.HandlerFeedBackVo; import org.dromara.warm.flow.ui.vo.HandlerSelectVo; import org.dromara.workflow.common.ConditionalOnEnable; import org.dromara.workflow.common.enums.TaskAssigneeEnum; import org.dromara.workflow.service.IFlwTaskAssigneeService; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; /** * 流程设计器-获取办理人权限设置列表 @@ -75,6 +76,44 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand return getHandlerSelectVo(buildHandlerData(dto, type), buildDeptTree(depts)); } + /** + * 办理人权限名称回显 + * + * @param storageIds 入库主键集合 + * @return 结果 + */ + @Override + public List handlerFeedback(List storageIds) { + if (CollUtil.isEmpty(storageIds)) { + return Collections.emptyList(); + } + // 解析并归类 ID,同时记录原始顺序和对应解析结果 + Map> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class); + Map> parsedMap = new LinkedHashMap<>(); + for (String storageId : storageIds) { + Pair parsed = this.parseStorageId(storageId); + parsedMap.put(storageId, parsed); + if (parsed != null) { + typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue()); + } + } + + // 查询所有类型对应的 ID 名称映射 + Map> nameMap = new EnumMap<>(TaskAssigneeEnum.class); + typeIdMap.forEach((type, ids) -> nameMap.put(type, this.getNamesByType(type, ids))); + + // 组装返回结果,保持原始顺序 + return parsedMap.entrySet().stream() + .map(entry -> { + String storageId = entry.getKey(); + Pair parsed = entry.getValue(); + String handlerName = (parsed == null) ? null + : nameMap.getOrDefault(parsed.getKey(), Collections.emptyMap()) + .get(parsed.getValue()); + return new HandlerFeedBackVo(storageId, handlerName); + }).toList(); + } + /** * 根据任务办理类型查询对应的数据 */ @@ -84,7 +123,6 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand case ROLE -> taskAssigneeService.selectRolesByTaskAssigneeList(taskQuery); case DEPT -> taskAssigneeService.selectDeptsByTaskAssigneeList(taskQuery); case POST -> taskAssigneeService.selectPostsByTaskAssigneeList(taskQuery); - default -> throw new ServiceException("Unsupported handler type"); }; } @@ -124,23 +162,29 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand } /** - * 根据存储标识符(storageId)解析分配类型和ID,并获取对应的用户列表 + * 批量解析多个存储标识符(storageIds),按类型分类并合并查询用户列表 + * 输入格式支持多个以逗号分隔的标识(如 "user:123,role:456,789") + * 会自动去重返回结果,非法格式的标识将被忽略 * - * @param storageId 包含分配类型和ID的字符串(例如 "user:123" 或 "role:456") - * @return 与分配类型和ID匹配的用户列表,如果格式无效则返回空列表 + * @param storageIds 多个存储标识符字符串(逗号分隔) + * @return 合并后的用户列表,去重后返回,非法格式的标识将被跳过 */ @Override - public List fetchUsersByStorageId(String storageId) { - List list = new ArrayList<>(); - for (String str : storageId.split(StrUtil.COMMA)) { - String[] parts = str.split(StrUtil.COLON, 2); - if (parts.length < 2) { - list.addAll(getUsersByType(TaskAssigneeEnum.USER, List.of(Long.valueOf(parts[0])))); - } else { - list.addAll(getUsersByType(TaskAssigneeEnum.fromCode(parts[0] + StrUtil.COLON), List.of(Long.valueOf(parts[1])))); + public List fetchUsersByStorageIds(String storageIds) { + if (StringUtils.isEmpty(storageIds)) { + return List.of(); + } + Map> typeIdMap = new EnumMap<>(TaskAssigneeEnum.class); + for (String storageId : storageIds.split(StringUtils.SEPARATOR)) { + Pair parsed = this.parseStorageId(storageId); + if (parsed != null) { + typeIdMap.computeIfAbsent(parsed.getKey(), k -> new ArrayList<>()).add(parsed.getValue()); } } - return list; + return typeIdMap.entrySet().stream() + .flatMap(entry -> this.getUsersByType(entry.getKey(), entry.getValue()).stream()) + .distinct() + .toList(); } /** @@ -162,4 +206,49 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand }; } + /** + * 根据任务分配类型和对应 ID 列表,批量查询名称映射关系 + * + * @param type 分配类型(用户、角色、部门、岗位) + * @param ids ID 列表(如用户ID、角色ID等) + * @return 返回 Map,其中 key 为 ID,value 为对应的名称 + */ + private Map getNamesByType(TaskAssigneeEnum type, List ids) { + return switch (type) { + case USER -> userService.selectUserNamesByIds(ids); + case ROLE -> userService.selectRoleNamesByIds(ids); + case DEPT -> userService.selectDeptNamesByIds(ids); + case POST -> userService.selectPostNamesByIds(ids); + }; + } + + /** + * 解析 storageId 字符串,返回类型和ID的组合 + * + * @param storageId 例如 "user:123" 或 "456" + * @return Pair(TaskAssigneeEnum, Long),如果格式非法返回 null + */ + private Pair parseStorageId(String storageId) { + if (StringUtils.isBlank(storageId)) { + return null; + } + // 跳过以 $ 或 # 开头的字符串 + if (StringUtils.startsWith(storageId, "$") || StringUtils.startsWith(storageId, "#")) { + log.debug("跳过 storageId 解析,检测到内置变量表达式:{}", storageId); + return null; + } + try { + String[] parts = storageId.split(StrUtil.COLON, 2); + if (parts.length < 2) { + return Pair.of(TaskAssigneeEnum.USER, Convert.toLong(parts[0])); + } else { + TaskAssigneeEnum type = TaskAssigneeEnum.fromCode(parts[0] + StrUtil.COLON); + return Pair.of(type, Convert.toLong(parts[1])); + } + } catch (Exception e) { + log.warn("解析 storageId 失败,格式非法:{},错误信息:{}", storageId, e.getMessage()); + return null; + } + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java index 21a54d74..f96a7573 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java @@ -16,7 +16,6 @@ import org.dromara.common.core.domain.dto.UserDTO; import org.dromara.common.core.enums.BusinessStatusEnum; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.service.UserService; -import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.ValidatorUtils; @@ -25,27 +24,32 @@ import org.dromara.common.core.validate.EditGroup; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.warm.flow.core.FlowEngine; 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.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.workflow.common.ConditionalOnEnable; +import org.dromara.workflow.common.constant.FlowConstant; import org.dromara.workflow.common.enums.TaskAssigneeType; import org.dromara.workflow.common.enums.TaskStatusEnum; import org.dromara.workflow.domain.bo.*; import org.dromara.workflow.domain.vo.FlowHisTaskVo; import org.dromara.workflow.domain.vo.FlowTaskVo; -import org.dromara.workflow.handler.FlowProcessEventHandler; -import org.dromara.workflow.handler.WorkflowPermissionHandler; import org.dromara.workflow.mapper.FlwCategoryMapper; import org.dromara.workflow.mapper.FlwTaskMapper; +import org.dromara.workflow.service.IFlwCommonService; +import org.dromara.workflow.service.IFlwNodeExtService; +import org.dromara.workflow.service.IFlwTaskAssigneeService; import org.dromara.workflow.service.IFlwTaskService; -import org.dromara.workflow.utils.WorkflowUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -75,10 +79,13 @@ public class FlwTaskServiceImpl implements IFlwTaskService { private final FlowTaskMapper flowTaskMapper; private final FlowHisTaskMapper flowHisTaskMapper; private final IdentifierGenerator identifierGenerator; - private final FlowProcessEventHandler flowProcessEventHandler; private final UserService userService; private final FlwTaskMapper flwTaskMapper; private final FlwCategoryMapper flwCategoryMapper; + private final FlowNodeMapper flowNodeMapper; + private final IFlwTaskAssigneeService flwTaskAssigneeService; + private final IFlwCommonService flwCommonService; + private final IFlwNodeExtService flwNodeExtService; /** * 启动任务 @@ -103,15 +110,17 @@ public class FlwTaskServiceImpl implements IFlwTaskService { if (ObjectUtil.isNotNull(flowInstance)) { BusinessStatusEnum.checkStartStatus(flowInstance.getFlowStatus()); List taskList = taskService.list(new FlowTask().setInstanceId(flowInstance.getId())); + taskService.mergeVariable(flowInstance, variables); + insService.updateById(flowInstance); StartProcessReturnDTO dto = new StartProcessReturnDTO(); dto.setProcessInstanceId(taskList.get(0).getInstanceId()); dto.setTaskId(taskList.get(0).getId()); return dto; } - FlowParams flowParams = new FlowParams(); - flowParams.flowCode(startProcessBo.getFlowCode()); - flowParams.variable(startProcessBo.getVariables()); - flowParams.flowStatus(BusinessStatusEnum.DRAFT.getStatus()); + FlowParams flowParams = FlowParams.build() + .flowCode(startProcessBo.getFlowCode()) + .variable(startProcessBo.getVariables()) + .flowStatus(BusinessStatusEnum.DRAFT.getStatus()); Instance instance; try { instance = insService.start(businessId, flowParams); @@ -144,30 +153,38 @@ public class FlwTaskServiceImpl implements IFlwTaskService { String notice = completeTaskBo.getNotice(); // 获取抄送人 List flowCopyList = completeTaskBo.getFlowCopyList(); + // 设置抄送人 + completeTaskBo.getVariables().put(FlowConstant.FLOW_COPY_LIST, flowCopyList); + // 消息类型 + completeTaskBo.getVariables().put(FlowConstant.MESSAGE_TYPE, messageType); + // 消息通知 + completeTaskBo.getVariables().put(FlowConstant.MESSAGE_NOTICE, notice); + + FlowTask flowTask = flowTaskMapper.selectById(taskId); if (ObjectUtil.isNull(flowTask)) { throw new ServiceException("流程任务不存在或任务已审批!"); } Instance ins = insService.getById(flowTask.getInstanceId()); - // 获取流程定义信息 - Definition definition = defService.getById(flowTask.getDefinitionId()); // 检查流程状态是否为草稿、已撤销或已退回状态,若是则执行流程提交监听 if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) { - flowProcessEventHandler.processHandler(definition.getFlowCode(), ins.getBusinessId(), ins.getFlowStatus(), null, true); + completeTaskBo.getVariables().put(FlowConstant.SUBMIT, true); + } + // 设置弹窗处理人 + Map assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap()); + if (CollUtil.isNotEmpty(assigneeMap)) { + completeTaskBo.getVariables().putAll(assigneeMap); } // 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息 - FlowParams flowParams = new FlowParams(); - flowParams.variable(completeTaskBo.getVariables()); - flowParams.skipType(SkipType.PASS.getKey()); - flowParams.message(completeTaskBo.getMessage()); - flowParams.flowStatus(BusinessStatusEnum.WAITING.getStatus()).hisStatus(TaskStatusEnum.PASS.getStatus()); - - flowParams.hisTaskExt(completeTaskBo.getFileId()); + FlowParams flowParams = FlowParams.build() + .variable(completeTaskBo.getVariables()) + .skipType(SkipType.PASS.getKey()) + .message(completeTaskBo.getMessage()) + .flowStatus(BusinessStatusEnum.WAITING.getStatus()) + .hisStatus(TaskStatusEnum.PASS.getStatus()) + .hisTaskExt(completeTaskBo.getFileId()); // 执行任务跳转,并根据返回的处理人设置下一步处理人 - Instance instance = taskService.skip(taskId, flowParams); - this.setHandler(instance, flowTask, flowCopyList); - // 消息通知 - WorkflowUtils.sendMessage(definition.getFlowName(), ins.getId(), messageType, notice); + taskService.skip(taskId, flowParams); return true; } catch (Exception e) { log.error(e.getMessage(), e); @@ -176,44 +193,34 @@ public class FlwTaskServiceImpl implements IFlwTaskService { } /** - * 设置办理人 + * 设置弹窗处理人 * - * @param instance 实例 - * @param task (当前任务)未办理的任务 - * @param flowCopyList 抄送人 + * @param assigneeMap 处理人 + * @param variablesMap 变量 */ - private void setHandler(Instance instance, FlowTask task, List flowCopyList) { - if (ObjectUtil.isNull(instance)) { - return; + private Map setPopAssigneeMap(Map assigneeMap, Map variablesMap) { + Map map = new HashMap<>(); + if (CollUtil.isEmpty(assigneeMap)) { + return map; } - // 添加抄送人 - this.setCopy(task, flowCopyList); - // 根据流程实例ID查询所有关联的任务 - List flowTasks = this.selectByInstId(instance.getId()); - if (CollUtil.isEmpty(flowTasks)) { - return; - } - List taskIdList = StreamUtils.toList(flowTasks, FlowTask::getId); - // 获取与当前任务关联的用户列表 - List associatedUsers = WorkflowUtils.getFlowUserService().getByAssociateds(taskIdList); - if (CollUtil.isEmpty(associatedUsers)) { - return; - } - List userList = new ArrayList<>(); - // 遍历任务列表,处理每个任务的办理人 - for (FlowTask flowTask : flowTasks) { - List users = StreamUtils.filter(associatedUsers, user -> Objects.equals(user.getAssociated(), flowTask.getId())); - if (CollUtil.isNotEmpty(users)) { - userList.addAll(WorkflowUtils.buildUser(users, flowTask.getId())); + for (Map.Entry entry : assigneeMap.entrySet()) { + if (variablesMap.containsKey(entry.getKey())) { + String userIds = variablesMap.get(entry.getKey()).toString(); + if (StringUtils.isNotBlank(userIds)) { + Set hashSet = new HashSet<>(); + //弹窗传入的选人 + List popUserIds = Arrays.asList(entry.getValue().toString().split(StringUtils.SEPARATOR)); + //已有的选人 + List variableUserIds = Arrays.asList(userIds.split(StringUtils.SEPARATOR)); + hashSet.addAll(popUserIds); + hashSet.addAll(variableUserIds); + map.put(entry.getKey(), String.join(StringUtils.SEPARATOR, hashSet)); + } + } else { + map.put(entry.getKey(), entry.getValue()); } } - // 批量删除现有任务的办理人记录 - WorkflowUtils.getFlowUserService().deleteByTaskIds(taskIdList); - // 确保要保存的 userList 不为空 - if (CollUtil.isEmpty(userList)) { - return; - } - WorkflowUtils.getFlowUserService().saveBatch(userList); + return map; } /** @@ -222,7 +229,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService { * @param task 任务信息 * @param flowCopyList 抄送人 */ - public void setCopy(FlowTask task, List flowCopyList) { + @Override + public void setCopy(Task task, List flowCopyList) { if (CollUtil.isEmpty(flowCopyList)) { return; } @@ -236,10 +244,10 @@ public class FlwTaskServiceImpl implements IFlwTaskService { task.setId(taskId); task.setNodeName("【抄送】" + task.getNodeName()); Date updateTime = new Date(flowHisTask.getUpdateTime().getTime() - 1000); - FlowParams flowParams = FlowParams.build(); - flowParams.skipType(SkipType.NONE.getKey()); - flowParams.hisStatus(TaskStatusEnum.COPY.getStatus()); - flowParams.message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getUserName)); + FlowParams flowParams = FlowParams.build() + .skipType(SkipType.NONE.getKey()) + .hisStatus(TaskStatusEnum.COPY.getStatus()) + .message("【抄送给】" + StreamUtils.join(flowCopyList, FlowCopyBo::getUserName)); HisTask hisTask = hisTaskService.setSkipHisTask(task, flowNode, flowParams); hisTask.setCreateTime(updateTime); hisTask.setUpdateTime(updateTime); @@ -253,7 +261,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { return flowUser; }).collect(Collectors.toList()); // 批量保存抄送人员 - WorkflowUtils.getFlowUserService().saveBatch(userList); + FlowEngine.userService().saveBatch(userList); } /** @@ -266,7 +274,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { public TableDataInfo pageByTaskWait(FlowTaskBo flowTaskBo, PageQuery pageQuery) { QueryWrapper queryWrapper = buildQueryWrapper(flowTaskBo); queryWrapper.eq("t.node_type", NodeType.BETWEEN.getKey()); - queryWrapper.in("t.processed_by", SpringUtils.getBean(WorkflowPermissionHandler.class).permissions()); + queryWrapper.in("t.processed_by", LoginHelper.getUserIdStr()); queryWrapper.in("t.flow_status", BusinessStatusEnum.WAITING.getStatus()); Page page = this.getFlowTaskVoPage(pageQuery, queryWrapper); return TableDataInfo.build(page); @@ -380,21 +388,23 @@ public class FlwTaskServiceImpl implements IFlwTaskService { Instance inst = insService.getById(task.getInstanceId()); BusinessStatusEnum.checkBackStatus(inst.getFlowStatus()); Long definitionId = task.getDefinitionId(); - Definition definition = defService.getById(definitionId); - String applyNodeCode = WorkflowUtils.applyNodeCode(definitionId); - FlowParams flowParams = FlowParams.build(); - flowParams.nodeCode(bo.getNodeCode()); - flowParams.message(message); - flowParams.skipType(SkipType.REJECT.getKey()); - flowParams.flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus() : TaskStatusEnum.WAITING.getStatus()) - .hisStatus(TaskStatusEnum.BACK.getStatus()); - flowParams.hisTaskExt(bo.getFileId()); - taskService.skip(task.getId(), flowParams); + String applyNodeCode = flwCommonService.applyNodeCode(definitionId); - Instance instance = insService.getById(inst.getId()); - this.setHandler(instance, task, null); + Map variable = new HashMap<>(); + // 消息类型 + variable.put("messageType", messageType); // 消息通知 - WorkflowUtils.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice); + variable.put("notice", notice); + + FlowParams flowParams = FlowParams.build() + .nodeCode(bo.getNodeCode()) + .variable(variable) + .message(message) + .skipType(SkipType.REJECT.getKey()) + .flowStatus(applyNodeCode.equals(bo.getNodeCode()) ? TaskStatusEnum.BACK.getStatus() : TaskStatusEnum.WAITING.getStatus()) + .hisStatus(TaskStatusEnum.BACK.getStatus()) + .hisTaskExt(bo.getFileId()); + taskService.skip(task.getId(), flowParams); return true; } catch (Exception e) { log.error(e.getMessage(), e); @@ -445,9 +455,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService { if (ObjectUtil.isNotNull(instance)) { BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus()); } - FlowParams flowParams = new FlowParams(); - flowParams.message(bo.getComment()); - flowParams.flowStatus(BusinessStatusEnum.TERMINATION.getStatus()) + FlowParams flowParams = FlowParams.build() + .message(bo.getComment()) + .flowStatus(BusinessStatusEnum.TERMINATION.getStatus()) .hisStatus(TaskStatusEnum.TERMINATION.getStatus()); taskService.termination(taskId, flowParams); return true; @@ -487,14 +497,57 @@ public class FlwTaskServiceImpl implements IFlwTaskService { flowTaskVo.setFlowCode(definition.getFlowCode()); flowTaskVo.setFlowName(definition.getFlowName()); flowTaskVo.setBusinessId(instance.getBusinessId()); - List nodeList = nodeService.getByNodeCodes(Collections.singletonList(flowTaskVo.getNodeCode()), instance.getDefinitionId()); - if (CollUtil.isNotEmpty(nodeList)) { - Node node = nodeList.get(0); - flowTaskVo.setNodeRatio(node.getNodeRatio()); + FlowNode flowNode = this.getByNodeCode(flowTaskVo.getNodeCode(), instance.getDefinitionId()); + if (ObjectUtil.isNull(flowNode)) { + throw new NullPointerException("当前【" + flowTaskVo.getNodeCode() + "】节点编码不存在"); } + //设置按钮权限 + flowTaskVo.setButtonList(flwNodeExtService.buildButtonPermissionsFromExt(flowNode.getExt())); + flowTaskVo.setNodeRatio(flowNode.getNodeRatio()); + flowTaskVo.setApplyNode(flowNode.getNodeCode().equals(flwCommonService.applyNodeCode(task.getDefinitionId()))); return flowTaskVo; } + /** + * 获取下一节点信息 + * + * @param bo 参数 + */ + @Override + public List getNextNodeList(FlowNextNodeBo bo) { + Long taskId = bo.getTaskId(); + Map variables = bo.getVariables(); + Task task = taskService.getById(taskId); + Instance instance = insService.getById(task.getInstanceId()); + Definition definition = defService.getById(task.getDefinitionId()); + Map mergeVariable = MapUtil.mergeAll(instance.getVariableMap(), variables); + // 获取下一节点列表 + List nextNodeList = nodeService.getNextNodeList(task.getDefinitionId(), task.getNodeCode(), null, SkipType.PASS.getKey(), mergeVariable); + List nextFlowNodes = BeanUtil.copyToList(nextNodeList, FlowNode.class); + // 只获取中间节点 + nextFlowNodes = StreamUtils.filter(nextFlowNodes, node -> NodeType.BETWEEN.getKey().equals(node.getNodeType())); + if (CollUtil.isNotEmpty(nextNodeList)) { + // 构建以下节点数据 + List buildNextTaskList = StreamUtils.toList(nextNodeList, node -> taskService.addTask(node, instance, definition, FlowParams.build())); + // 办理人变量替换 + ExpressionUtil.evalVariable(buildNextTaskList, + FlowParams.build() + .variable(mergeVariable) + ); + for (FlowNode flowNode : nextFlowNodes) { + buildNextTaskList.stream().filter(t -> t.getNodeCode().equals(flowNode.getNodeCode())).findFirst().ifPresent(t -> { + if (CollUtil.isNotEmpty(t.getPermissionList())) { + List users = flwTaskAssigneeService.fetchUsersByStorageIds(String.join(StringUtils.SEPARATOR, t.getPermissionList())); + if (CollUtil.isNotEmpty(users)) { + flowNode.setPermissionFlag(StreamUtils.join(users, e -> String.valueOf(e.getUserId()))); + } + } + }); + } + } + return nextFlowNodes; + } + /** * 按照任务id查询任务 * @@ -550,8 +603,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService { @Override @Transactional(rollbackFor = Exception.class) public boolean taskOperation(TaskOperationBo bo, String taskOperation) { - FlowParams flowParams = new FlowParams(); - flowParams.message(bo.getMessage()); + FlowParams flowParams = FlowParams.build() + .message(bo.getMessage()); if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) { flowParams.ignore(true); } @@ -577,10 +630,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService { } Long taskId = bo.getTaskId(); - FlowTaskVo flowTaskVo = selectById(taskId); + Task task = taskService.getById(taskId); + FlowNode flowNode = getByNodeCode(task.getNodeCode(), task.getDefinitionId()); if ("addSignature".equals(taskOperation) || "reductionSignature".equals(taskOperation)) { - if (flowTaskVo.getNodeRatio().compareTo(BigDecimal.ZERO) == 0) { - throw new ServiceException(flowTaskVo.getNodeName() + "不是会签节点!"); + if (flowNode.getNodeRatio().compareTo(BigDecimal.ZERO) == 0) { + throw new ServiceException(task.getNodeName() + "不是会签节点!"); } } // 设置任务状态并执行对应的任务操作 @@ -628,7 +682,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { List flowTasks = this.selectByIdList(taskIdList); // 批量删除现有任务的办理人记录 if (CollUtil.isNotEmpty(flowTasks)) { - WorkflowUtils.getFlowUserService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId)); + FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(flowTasks, FlowTask::getId)); List userList = flowTasks.stream() .map(flowTask -> { FlowUser flowUser = new FlowUser(); @@ -639,7 +693,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService { }) .collect(Collectors.toList()); if (CollUtil.isNotEmpty(userList)) { - WorkflowUtils.getFlowUserService().saveBatch(userList); + FlowEngine.userService().saveBatch(userList); } } } catch (Exception e) { @@ -658,13 +712,13 @@ public class FlwTaskServiceImpl implements IFlwTaskService { public Map> currentTaskAllUser(List taskIdList) { Map> map = new HashMap<>(); // 获取与当前任务关联的用户列表 - List associatedUsers = WorkflowUtils.getFlowUserService().getByAssociateds(taskIdList); + List associatedUsers = FlowEngine.userService().getByAssociateds(taskIdList); Map> listMap = StreamUtils.groupByKey(associatedUsers, User::getAssociated); for (Map.Entry> entry : listMap.entrySet()) { List value = entry.getValue(); if (CollUtil.isNotEmpty(value)) { - List userDTOS = userService.selectListByIds(StreamUtils.toList(value, e -> Long.valueOf(e.getProcessedBy()))); - map.put(entry.getKey(), userDTOS); + List userDtoList = userService.selectListByIds(StreamUtils.toList(value, e -> Convert.toLong(e.getProcessedBy()))); + map.put(entry.getKey(), userDtoList); } } return map; @@ -678,10 +732,24 @@ public class FlwTaskServiceImpl implements IFlwTaskService { @Override public List currentTaskAllUser(Long taskId) { // 获取与当前任务关联的用户列表 - List userList = WorkflowUtils.getFlowUserService().getByAssociateds(Collections.singletonList(taskId)); + List userList = FlowEngine.userService().getByAssociateds(Collections.singletonList(taskId)); if (CollUtil.isEmpty(userList)) { return Collections.emptyList(); } - return userService.selectListByIds(StreamUtils.toList(userList, e -> Long.valueOf(e.getProcessedBy()))); + return userService.selectListByIds(StreamUtils.toList(userList, e -> Convert.toLong(e.getProcessedBy()))); } + + /** + * 按照节点编码查询节点 + * + * @param nodeCode 节点编码 + * @param definitionId 流程定义id + */ + @Override + public FlowNode getByNodeCode(String nodeCode, Long definitionId) { + return flowNodeMapper.selectOne(new LambdaQueryWrapper() + .eq(FlowNode::getNodeCode, nodeCode) + .eq(FlowNode::getDefinitionId, definitionId)); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java index 2c431737..17d1c7ca 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java @@ -9,9 +9,9 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.event.ProcessTaskEvent; import org.dromara.common.core.domain.event.ProcessDeleteEvent; import org.dromara.common.core.domain.event.ProcessEvent; -import org.dromara.common.core.domain.event.ProcessTaskEvent; import org.dromara.common.core.enums.BusinessStatusEnum; import org.dromara.common.core.service.WorkflowService; import org.dromara.common.core.utils.MapstructUtils; @@ -47,6 +47,19 @@ public class TestLeaveServiceImpl implements ITestLeaveService { private final TestLeaveMapper baseMapper; private final WorkflowService workflowService; + /** + * spel条件表达:判断小于2 + * + * @param leaveDays 待判断的变量(可不传自行返回true或false) + * @return boolean + */ + public boolean eval(Integer leaveDays) { + if (leaveDays <= 2) { + return true; + } + return false; + } + /** * 查询请假 */ @@ -123,7 +136,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService { } /** - * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成等) + * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等) * 正常使用只需#processEvent.flowCode=='leave1' * 示例为了方便则使用startsWith匹配了全部示例key * @@ -132,7 +145,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService { @EventListener(condition = "#processEvent.flowCode.startsWith('leave')") public void processHandler(ProcessEvent processEvent) { log.info("当前任务执行了{}", processEvent.toString()); - TestLeave testLeave = baseMapper.selectById(Long.valueOf(processEvent.getBusinessId())); + TestLeave testLeave = baseMapper.selectById(Convert.toLong(processEvent.getBusinessId())); testLeave.setStatus(processEvent.getStatus()); // 用于例如审批附件 审批意见等 存储到业务表内 自行根据业务实现存储流程 Map params = processEvent.getParams(); @@ -144,14 +157,14 @@ public class TestLeaveServiceImpl implements ITestLeaveService { // 办理意见 String message = Convert.toStr(params.get("message")); } - if (processEvent.isSubmit()) { + if (processEvent.getSubmit()) { testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus()); } baseMapper.updateById(testLeave); } /** - * 执行办理任务监听 + * 执行任务创建监听 * 示例:也可通过 @EventListener(condition = "#processTaskEvent.flowCode=='leave1'")进行判断 * 在方法中判断流程节点key * if ("xxx".equals(processTaskEvent.getNodeCode())) { @@ -162,10 +175,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService { */ @EventListener(condition = "#processTaskEvent.flowCode.startsWith('leave')") public void processTaskHandler(ProcessTaskEvent processTaskEvent) { - log.info("当前任务执行了{}", processTaskEvent.toString()); - TestLeave testLeave = baseMapper.selectById(Long.valueOf(processTaskEvent.getBusinessId())); - testLeave.setStatus(BusinessStatusEnum.WAITING.getStatus()); - baseMapper.updateById(testLeave); + log.info("当前任务创建了{}", processTaskEvent.toString()); } /** @@ -178,7 +188,7 @@ public class TestLeaveServiceImpl implements ITestLeaveService { @EventListener(condition = "#processDeleteEvent.flowCode.startsWith('leave')") public void processDeleteHandler(ProcessDeleteEvent processDeleteEvent) { log.info("监听删除流程事件,当前任务执行了{}", processDeleteEvent.toString()); - TestLeave testLeave = baseMapper.selectById(Long.valueOf(processDeleteEvent.getBusinessId())); + TestLeave testLeave = baseMapper.selectById(Convert.toLong(processDeleteEvent.getBusinessId())); if (ObjectUtil.isNull(testLeave)) { return; } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java index f8a20b5b..0c022400 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java @@ -122,6 +122,8 @@ public class WorkflowServiceImpl implements WorkflowService { /** * 办理任务 + * 系统后台发起审批 无用户信息 需要忽略权限 + * completeTask.getVariables().put("ignore", true); * * @param completeTask 参数 */ @@ -129,4 +131,21 @@ public class WorkflowServiceImpl implements WorkflowService { public boolean completeTask(CompleteTaskDTO completeTask) { return flwTaskService.completeTask(BeanUtil.toBean(completeTask, CompleteTaskBo.class)); } + + /** + * 办理任务 + * + * @param taskId 任务ID + * @param message 办理意见 + */ + @Override + public boolean completeTask(Long taskId, String message) { + CompleteTaskBo completeTask = new CompleteTaskBo(); + completeTask.setTaskId(taskId); + completeTask.setMessage(message); + // 忽略权限(系统后台发起审批 无用户信息 需要忽略权限) + completeTask.getVariables().put("ignore", true); + return flwTaskService.completeTask(completeTask); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java deleted file mode 100644 index e48ffc82..00000000 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/utils/WorkflowUtils.java +++ /dev/null @@ -1,206 +0,0 @@ -package org.dromara.workflow.utils; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import org.dromara.common.core.domain.dto.UserDTO; -import org.dromara.common.core.utils.SpringUtils; -import org.dromara.common.core.utils.StreamUtils; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.mail.utils.MailUtils; -import org.dromara.common.sse.dto.SseMessageDto; -import org.dromara.common.sse.utils.SseMessageUtils; -import org.dromara.warm.flow.core.constant.ExceptionCons; -import org.dromara.warm.flow.core.dto.FlowParams; -import org.dromara.warm.flow.core.entity.Node; -import org.dromara.warm.flow.core.entity.Task; -import org.dromara.warm.flow.core.entity.User; -import org.dromara.warm.flow.core.enums.NodeType; -import org.dromara.warm.flow.core.enums.SkipType; -import org.dromara.warm.flow.core.service.NodeService; -import org.dromara.warm.flow.core.service.TaskService; -import org.dromara.warm.flow.core.service.UserService; -import org.dromara.warm.flow.core.utils.AssertUtil; -import org.dromara.warm.flow.orm.entity.FlowNode; -import org.dromara.warm.flow.orm.entity.FlowTask; -import org.dromara.warm.flow.orm.entity.FlowUser; -import org.dromara.warm.flow.orm.mapper.FlowNodeMapper; -import org.dromara.warm.flow.orm.mapper.FlowTaskMapper; -import org.dromara.workflow.common.enums.MessageTypeEnum; -import org.dromara.workflow.service.IFlwTaskAssigneeService; -import org.dromara.workflow.service.IFlwTaskService; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - - -/** - * 工作流工具 - * - * @author may - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class WorkflowUtils { - - private static final IFlwTaskAssigneeService TASK_ASSIGNEE_SERVICE = SpringUtils.getBean(IFlwTaskAssigneeService.class); - private static final IFlwTaskService FLW_TASK_SERVICE = SpringUtils.getBean(IFlwTaskService.class); - private static final FlowNodeMapper FLOW_NODE_MAPPER = SpringUtils.getBean(FlowNodeMapper.class); - private static final FlowTaskMapper FLOW_TASK_MAPPER = SpringUtils.getBean(FlowTaskMapper.class); - private static final UserService USER_SERVICE = SpringUtils.getBean(UserService.class); - private static final TaskService TASK_SERVICE = SpringUtils.getBean(TaskService.class); - private static final NodeService NODE_SERVICE = SpringUtils.getBean(NodeService.class); - - /** - * 获取工作流用户service - */ - public static UserService getFlowUserService() { - return USER_SERVICE; - } - - /** - * 构建工作流用户 - * - * @param userList 办理用户 - * @param taskId 任务ID - * @return 用户 - */ - public static Set buildUser(List userList, Long taskId) { - if (CollUtil.isEmpty(userList)) { - return Set.of(); - } - Set list = new HashSet<>(); - Set processedBySet = new HashSet<>(); - for (User user : userList) { - // 根据 processedBy 前缀判断处理人类型,分别获取用户列表 - List users = TASK_ASSIGNEE_SERVICE.fetchUsersByStorageId(user.getProcessedBy()); - // 转换为 FlowUser 并添加到结果集合 - if (CollUtil.isNotEmpty(users)) { - users.forEach(dto -> { - String processedBy = String.valueOf(dto.getUserId()); - if (!processedBySet.contains(processedBy)) { - FlowUser flowUser = new FlowUser(); - flowUser.setType(user.getType()); - flowUser.setProcessedBy(processedBy); - flowUser.setAssociated(taskId); - list.add(flowUser); - processedBySet.add(processedBy); - } - }); - } - } - return list; - } - - /** - * 发送消息 - * - * @param flowName 流程定义名称 - * @param messageType 消息类型 - * @param message 消息内容,为空则发送默认配置的消息内容 - */ - public static void sendMessage(String flowName, Long instId, List messageType, String message) { - List userList = new ArrayList<>(); - List list = FLW_TASK_SERVICE.selectByInstId(instId); - if (StringUtils.isBlank(message)) { - message = "有新的【" + flowName + "】单据已经提交至您,请您及时处理。"; - } - for (Task task : list) { - List users = FLW_TASK_SERVICE.currentTaskAllUser(task.getId()); - if (CollUtil.isNotEmpty(users)) { - userList.addAll(users); - } - } - if (CollUtil.isNotEmpty(userList)) { - for (String code : messageType) { - MessageTypeEnum messageTypeEnum = MessageTypeEnum.getByCode(code); - if (ObjectUtil.isNotEmpty(messageTypeEnum)) { - switch (messageTypeEnum) { - case SYSTEM_MESSAGE: - SseMessageDto dto = new SseMessageDto(); - dto.setUserIds(StreamUtils.toList(userList, UserDTO::getUserId).stream().distinct().collect(Collectors.toList())); - dto.setMessage(message); - SseMessageUtils.publishMessage(dto); - break; - case EMAIL_MESSAGE: - MailUtils.sendText(StreamUtils.join(userList, UserDTO::getEmail), "单据审批提醒", message); - break; - case SMS_MESSAGE: - //todo 短信发送 - break; - default: - throw new IllegalStateException("Unexpected value: " + messageTypeEnum); - } - } - } - } - } - - /** - * 驳回 - * - * @param message 审批意见 - * @param instanceId 流程实例id - * @param targetNodeCode 目标节点 - * @param flowStatus 流程状态 - * @param flowHisStatus 节点操作状态 - */ - public static void backTask(String message, Long instanceId, String targetNodeCode, String flowStatus, String flowHisStatus) { - List list = FLW_TASK_SERVICE.selectByInstId(instanceId); - if (CollUtil.isNotEmpty(list)) { - List tasks = StreamUtils.filter(list, e -> e.getNodeCode().equals(targetNodeCode)); - if (list.size() == tasks.size()) { - return; - } - } - for (FlowTask task : list) { - List userList = FLW_TASK_SERVICE.currentTaskAllUser(task.getId()); - FlowParams flowParams = FlowParams.build(); - flowParams.nodeCode(targetNodeCode); - flowParams.message(message); - flowParams.skipType(SkipType.PASS.getKey()); - flowParams.flowStatus(flowStatus).hisStatus(flowHisStatus); - flowParams.ignore(true); - //解决会签没权限问题 - if (CollUtil.isNotEmpty(userList)) { - flowParams.handler(userList.get(0).getUserId().toString()); - } - TASK_SERVICE.skip(task.getId(), flowParams); - } - //解决会签多人审批问题 - backTask(message, instanceId, targetNodeCode, flowStatus, flowHisStatus); - } - - /** - * 申请人节点编码 - * - * @param definitionId 流程定义id - * @return 申请人节点编码 - */ - public static String applyNodeCode(Long definitionId) { - //获取已发布的流程节点 - List flowNodes = FLOW_NODE_MAPPER.selectList(new LambdaQueryWrapper().eq(FlowNode::getDefinitionId, definitionId)); - AssertUtil.isTrue(CollUtil.isEmpty(flowNodes), ExceptionCons.NOT_PUBLISH_NODE); - Node startNode = flowNodes.stream().filter(t -> NodeType.isStart(t.getNodeType())).findFirst().orElse(null); - AssertUtil.isNull(startNode, ExceptionCons.LOST_START_NODE); - Node nextNode = NODE_SERVICE.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey()); - return nextNode.getNodeCode(); - } - - /** - * 删除运行中的任务 - * - * @param taskIds 任务id - */ - public static void deleteRunTask(List taskIds) { - if (CollUtil.isEmpty(taskIds)) { - return; - } - USER_SERVICE.deleteByTaskIds(taskIds); - FLOW_TASK_MAPPER.deleteByIds(taskIds); - } -} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwCategoryMapper.xml b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwCategoryMapper.xml index e9918f1f..10c948d8 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwCategoryMapper.xml +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwCategoryMapper.xml @@ -4,8 +4,4 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - diff --git a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml index 73e4ec78..f539030b 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml +++ b/xinnengyuan/ruoyi-modules/ruoyi-workflow/src/main/resources/mapper/workflow/FlwTaskMapper.xml @@ -31,7 +31,7 @@ d.version, uu.processed_by, uu.type - from flow_task as t + from flow_task t left join flow_user uu on uu.associated = t.id left join flow_definition d on t.definition_id = d.id left join flow_instance i on t.instance_id = i.id diff --git a/xinnengyuan/script/leave/leave1.json b/xinnengyuan/script/leave/leave1.json index 0cf67bc4..0ffdeeb8 100644 --- a/xinnengyuan/script/leave/leave1.json +++ b/xinnengyuan/script/leave/leave1.json @@ -1,7 +1,7 @@ { "flowCode" : "leave1", "flowName" : "请假申请-普通", - "category" : "1", + "category" : "100", "version" : "1", "formCustom" : "N", "formPath" : "/workflow/leaveEdit/index", @@ -11,8 +11,8 @@ "nodeName" : "开始", "nodeRatio" : 0.000, "coordinate" : "200,200|200,200", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "d5ee3ddf-3968-4379-a86f-9ceabde5faac", "nextNodeCode" : "dd515cdd-59f6-446f-94ca-25ca062afb42", @@ -25,8 +25,8 @@ "nodeName" : "申请人", "nodeRatio" : 0.000, "coordinate" : "360,200|360,200", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "dd515cdd-59f6-446f-94ca-25ca062afb42", "nextNodeCode" : "78fa8e5b-e809-44ed-978a-41092409ebcf", @@ -40,8 +40,8 @@ "permissionFlag" : "role:1", "nodeRatio" : 0.000, "coordinate" : "540,200|540,200", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", "skipList" : [ { "nowNodeCode" : "78fa8e5b-e809-44ed-978a-41092409ebcf", "nextNodeCode" : "a8abf15f-b83e-428a-86cc-033555ea9bbe", @@ -52,11 +52,11 @@ "nodeType" : 1, "nodeCode" : "a8abf15f-b83e-428a-86cc-033555ea9bbe", "nodeName" : "部门主管", - "permissionFlag" : "role:3,role:4", + "permissionFlag" : "role:3@@role:4", "nodeRatio" : 0.000, "coordinate" : "720,200|720,200", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", "skipList" : [ { "nowNodeCode" : "a8abf15f-b83e-428a-86cc-033555ea9bbe", "nextNodeCode" : "8b82b7d7-8660-455e-b880-d6d22ea3eb6d", @@ -69,7 +69,7 @@ "nodeName" : "结束", "nodeRatio" : 0.000, "coordinate" : "900,200|900,200", - "skipAnyNode" : "N", - "formCustom" : "N" + "formCustom" : "N", + "ext" : "[]" } ] -} \ No newline at end of file +} diff --git a/xinnengyuan/script/leave/leave2.json b/xinnengyuan/script/leave/leave2.json index 9fce8ffd..7bbdbaab 100644 --- a/xinnengyuan/script/leave/leave2.json +++ b/xinnengyuan/script/leave/leave2.json @@ -1,7 +1,7 @@ { "flowCode" : "leave2", "flowName" : "请假申请-排他网关", - "category" : "1", + "category" : "100", "version" : "1", "formCustom" : "N", "formPath" : "/workflow/leaveEdit/index", @@ -11,8 +11,8 @@ "nodeName" : "开始", "nodeRatio" : 0.000, "coordinate" : "300,240|300,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a", "nextNodeCode" : "fdcae93b-b69c-498a-b231-09255e74bcbd", @@ -25,8 +25,8 @@ "nodeName" : "申请人", "nodeRatio" : 0.000, "coordinate" : "440,240|440,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "fdcae93b-b69c-498a-b231-09255e74bcbd", "nextNodeCode" : "7b8c7ead-7dc8-4951-a7f3-f0c41995909e", @@ -38,8 +38,8 @@ "nodeCode" : "7b8c7ead-7dc8-4951-a7f3-f0c41995909e", "nodeRatio" : 0.000, "coordinate" : "560,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "7b8c7ead-7dc8-4951-a7f3-f0c41995909e", "nextNodeCode" : "b3528155-dcb7-4445-bbdf-3d00e3499e86", @@ -58,11 +58,11 @@ "nodeType" : 1, "nodeCode" : "b3528155-dcb7-4445-bbdf-3d00e3499e86", "nodeName" : "组长", - "permissionFlag" : "3,4", + "permissionFlag" : "3@@4", "nodeRatio" : 0.000, "coordinate" : "720,320|720,320", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", "skipList" : [ { "nowNodeCode" : "b3528155-dcb7-4445-bbdf-3d00e3499e86", "nextNodeCode" : "c9fa6d7d-2a74-4e78-b947-0cad8a6af869", @@ -76,8 +76,8 @@ "permissionFlag" : "role:1", "nodeRatio" : 0.000, "coordinate" : "860,240|860,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "c9fa6d7d-2a74-4e78-b947-0cad8a6af869", "nextNodeCode" : "40aa65fd-0712-4d23-b6f7-d0432b920fd1", @@ -90,8 +90,8 @@ "nodeName" : "结束", "nodeRatio" : 0.000, "coordinate" : "1000,240|1000,240", - "skipAnyNode" : "N", - "formCustom" : "N" + "formCustom" : "N", + "ext" : "[]" }, { "nodeType" : 1, "nodeCode" : "5ed2362b-fc0c-4d52-831f-95208b830605", @@ -99,8 +99,8 @@ "permissionFlag" : "role:1", "nodeRatio" : 0.000, "coordinate" : "720,160|720,160", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", "skipList" : [ { "nowNodeCode" : "5ed2362b-fc0c-4d52-831f-95208b830605", "nextNodeCode" : "c9fa6d7d-2a74-4e78-b947-0cad8a6af869", diff --git a/xinnengyuan/script/leave/leave3.json b/xinnengyuan/script/leave/leave3.json index 08daae4b..bb22d42c 100644 --- a/xinnengyuan/script/leave/leave3.json +++ b/xinnengyuan/script/leave/leave3.json @@ -1,7 +1,7 @@ { "flowCode" : "leave3", "flowName" : "请假申请-并行网关", - "category" : "1", + "category" : "100", "version" : "1", "formCustom" : "N", "formPath" : "/workflow/leaveEdit/index", @@ -11,8 +11,8 @@ "nodeName" : "开始", "nodeRatio" : 0.000, "coordinate" : "380,220|380,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "a80ecf9f-f465-4ae5-a429-e30ec5d0f957", "nextNodeCode" : "b7bbb571-06de-455c-8083-f83c07bf0b99", @@ -25,8 +25,8 @@ "nodeName" : "申请人", "nodeRatio" : 0.000, "coordinate" : "520,220|520,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "b7bbb571-06de-455c-8083-f83c07bf0b99", "nextNodeCode" : "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a", @@ -38,8 +38,8 @@ "nodeCode" : "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a", "nodeRatio" : 0.000, "coordinate" : "680,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "84d7ed24-bb44-4ba1-bf1f-e6f5092d3f0a", "nextNodeCode" : "4b7743cd-940c-431b-926f-e7b614fbf1fe", @@ -58,8 +58,8 @@ "permissionFlag" : "role:1", "nodeRatio" : 0.000, "coordinate" : "800,140|800,140", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "4b7743cd-940c-431b-926f-e7b614fbf1fe", "nextNodeCode" : "b66b6563-f9fe-41cc-a782-f7837bb6f3d2", @@ -71,8 +71,8 @@ "nodeCode" : "b66b6563-f9fe-41cc-a782-f7837bb6f3d2", "nodeRatio" : 0.000, "coordinate" : "920,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "b66b6563-f9fe-41cc-a782-f7837bb6f3d2", "nextNodeCode" : "23e7429e-2b47-4431-b93e-40db7c431ce6", @@ -86,8 +86,8 @@ "permissionFlag" : "1", "nodeRatio" : 0.000, "coordinate" : "1040,220|1040,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "23e7429e-2b47-4431-b93e-40db7c431ce6", "nextNodeCode" : "f5ace37f-5a5e-4e64-a6f6-913ab9a71cd1", @@ -100,17 +100,17 @@ "nodeName" : "结束", "nodeRatio" : 0.000, "coordinate" : "1160,220|1160,220", - "skipAnyNode" : "N", - "formCustom" : "N" + "formCustom" : "N", + "ext" : "[]" }, { "nodeType" : 1, "nodeCode" : "762cb975-37d8-4276-b6db-79a4c3606394", "nodeName" : "综合部", - "permissionFlag" : "role:3,role:4", + "permissionFlag" : "role:3@@role:4", "nodeRatio" : 0.000, "coordinate" : "800,300|800,300", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "762cb975-37d8-4276-b6db-79a4c3606394", "nextNodeCode" : "b66b6563-f9fe-41cc-a782-f7837bb6f3d2", @@ -118,4 +118,4 @@ "coordinate" : "850,300;920,300;920,245" } ] } ] -} \ No newline at end of file +} diff --git a/xinnengyuan/script/leave/leave4.json b/xinnengyuan/script/leave/leave4.json index f8f44081..50968f8d 100644 --- a/xinnengyuan/script/leave/leave4.json +++ b/xinnengyuan/script/leave/leave4.json @@ -1,7 +1,7 @@ { "flowCode" : "leave4", "flowName" : "请假申请-会签", - "category" : "1", + "category" : "100", "version" : "1", "formCustom" : "N", "formPath" : "/workflow/leaveEdit/index", @@ -11,8 +11,8 @@ "nodeName" : "开始", "nodeRatio" : 0.000, "coordinate" : "320,240|320,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "9ce8bf00-f25b-4fc6-91b8-827082fc4876", "nextNodeCode" : "e90b98ef-35b4-410c-a663-bae8b7624b9f", @@ -25,8 +25,8 @@ "nodeName" : "申请人", "nodeRatio" : 0.000, "coordinate" : "460,240|460,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "e90b98ef-35b4-410c-a663-bae8b7624b9f", "nextNodeCode" : "768b5b1a-6726-4d67-8853-4cc70d5b1045", @@ -40,8 +40,8 @@ "permissionFlag" : "${userList}", "nodeRatio" : 60.000, "coordinate" : "640,240|640,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "768b5b1a-6726-4d67-8853-4cc70d5b1045", "nextNodeCode" : "2f9f2e21-9bcf-42a3-a07c-13037aad22d1", @@ -52,11 +52,11 @@ "nodeType" : 1, "nodeCode" : "2f9f2e21-9bcf-42a3-a07c-13037aad22d1", "nodeName" : "全部审批通过", - "permissionFlag" : "role:1,role:3", + "permissionFlag" : "role:1@@role:3", "nodeRatio" : 100.000, "coordinate" : "820,240|820,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "2f9f2e21-9bcf-42a3-a07c-13037aad22d1", "nextNodeCode" : "27461e01-3d9f-4530-8fe3-bd5ec7f9571f", @@ -70,8 +70,8 @@ "permissionFlag" : "1", "nodeRatio" : 0.000, "coordinate" : "1000,240|1000,240", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "27461e01-3d9f-4530-8fe3-bd5ec7f9571f", "nextNodeCode" : "b62b88c3-8d8d-4969-911e-2aaea219e7fc", @@ -84,7 +84,7 @@ "nodeName" : "结束", "nodeRatio" : 0.000, "coordinate" : "1120,240|1120,240", - "skipAnyNode" : "N", - "formCustom" : "N" + "formCustom" : "N", + "ext" : "[]" } ] -} \ No newline at end of file +} diff --git a/xinnengyuan/script/leave/leave5.json b/xinnengyuan/script/leave/leave5.json index dc99494a..a27b1de4 100644 --- a/xinnengyuan/script/leave/leave5.json +++ b/xinnengyuan/script/leave/leave5.json @@ -1,7 +1,7 @@ { "flowCode" : "leave5", "flowName" : "请假申请-并行会签网关", - "category" : "1", + "category" : "100", "version" : "1", "formCustom" : "N", "formPath" : "/workflow/leaveEdit/index", @@ -11,8 +11,8 @@ "nodeName" : "开始", "nodeRatio" : 0.000, "coordinate" : "300,220|300,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "ebebaf26-9cb6-497e-8119-4c9fed4c597c", "nextNodeCode" : "e1b04e96-dc81-4858-a309-2fe945d2f374", @@ -25,8 +25,8 @@ "nodeName" : "申请人", "nodeRatio" : 0.000, "coordinate" : "420,220|420,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "e1b04e96-dc81-4858-a309-2fe945d2f374", "nextNodeCode" : "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9", @@ -38,8 +38,8 @@ "nodeCode" : "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9", "nodeRatio" : 0.000, "coordinate" : "560,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "3e743f4f-51ca-41d4-8e94-21f5dd9b59c9", "nextNodeCode" : "c80f273e-1f17-4bd8-9ad1-04a4a94ea862", @@ -55,11 +55,11 @@ "nodeType" : 1, "nodeCode" : "c80f273e-1f17-4bd8-9ad1-04a4a94ea862", "nodeName" : "会签", - "permissionFlag" : "role:1,role:3", + "permissionFlag" : "role:1@@role:3", "nodeRatio" : 100.000, "coordinate" : "700,320|700,320", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "c80f273e-1f17-4bd8-9ad1-04a4a94ea862", "nextNodeCode" : "1a20169e-3d82-4926-a151-e2daad28de1b", @@ -71,8 +71,8 @@ "nodeCode" : "1a20169e-3d82-4926-a151-e2daad28de1b", "nodeRatio" : 0.000, "coordinate" : "860,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "1a20169e-3d82-4926-a151-e2daad28de1b", "nextNodeCode" : "7a8f0473-e409-442e-a843-5c2b813d00e9", @@ -86,8 +86,8 @@ "permissionFlag" : "1", "nodeRatio" : 0.000, "coordinate" : "1000,220|1000,220", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "7a8f0473-e409-442e-a843-5c2b813d00e9", "nextNodeCode" : "03c4d2bc-58b5-4408-a2e4-65afb046f169", @@ -100,8 +100,8 @@ "nodeName" : "结束", "nodeRatio" : 0.000, "coordinate" : "1140,220|1140,220", - "skipAnyNode" : "N", - "formCustom" : "N" + "formCustom" : "N", + "ext" : "[]" }, { "nodeType" : 1, "nodeCode" : "1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4", @@ -109,8 +109,8 @@ "permissionFlag" : "${userList}", "nodeRatio" : 60.000, "coordinate" : "700,120|700,120", - "skipAnyNode" : "N", "formCustom" : "N", + "ext" : "[]", "skipList" : [ { "nowNodeCode" : "1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4", "nextNodeCode" : "1a20169e-3d82-4926-a151-e2daad28de1b", @@ -118,4 +118,4 @@ "coordinate" : "750,120;860,120;860,195" } ] } ] -} \ No newline at end of file +} diff --git a/xinnengyuan/script/leave/leave6.json b/xinnengyuan/script/leave/leave6.json new file mode 100644 index 00000000..d21d9d2c --- /dev/null +++ b/xinnengyuan/script/leave/leave6.json @@ -0,0 +1,215 @@ +{ + "flowCode" : "leave6", + "flowName" : "请假申请-排他并行会签", + "category" : "100", + "version" : "1", + "formCustom" : "N", + "formPath" : "/workflow/leaveEdit/index", + "nodeList" : [ { + "nodeType" : 0, + "nodeCode" : "122b89a5-7c6f-40a3-aa09-7a263f902054", + "nodeName" : "开始", + "nodeRatio" : 0.000, + "coordinate" : "240,300|240,300", + "formCustom" : "N", + "ext" : "[]", + "skipList" : [ { + "nowNodeCode" : "122b89a5-7c6f-40a3-aa09-7a263f902054", + "nextNodeCode" : "c25a0e86-fdd1-4f03-8e22-14db70389dbd", + "skipType" : "PASS", + "coordinate" : "260,300;350,300" + } ] + }, { + "nodeType" : 1, + "nodeCode" : "c25a0e86-fdd1-4f03-8e22-14db70389dbd", + "nodeName" : "申请人", + "nodeRatio" : 0.000, + "coordinate" : "400,300|400,300", + "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", + "skipList" : [ { + "nowNodeCode" : "c25a0e86-fdd1-4f03-8e22-14db70389dbd", + "nextNodeCode" : "07ecda1d-7a0a-47b5-8a91-6186c9473742", + "skipType" : "PASS", + "coordinate" : "450,300;510,300" + } ] + }, { + "nodeType" : 1, + "nodeCode" : "2bfa3919-78cf-4bc1-b59b-df463a4546f9", + "nodeName" : "副经理", + "permissionFlag" : "role:1@@role:3@@role:4", + "nodeRatio" : 0.000, + "coordinate" : "860,200|860,200", + "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", + "skipList" : [ { + "nowNodeCode" : "2bfa3919-78cf-4bc1-b59b-df463a4546f9", + "nextNodeCode" : "394e1cc8-b8b2-4189-9f81-44448e88ac32", + "skipType" : "PASS", + "coordinate" : "910,200;1000,200;1000,275" + } ] + }, { + "nodeType" : 1, + "nodeCode" : "ec17f60e-94e0-4d96-a3ce-3417e9d32d60", + "nodeName" : "组长", + "permissionFlag" : "1", + "nodeRatio" : 0.000, + "coordinate" : "860,400|860,400", + "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", + "skipList" : [ { + "nowNodeCode" : "ec17f60e-94e0-4d96-a3ce-3417e9d32d60", + "nextNodeCode" : "394e1cc8-b8b2-4189-9f81-44448e88ac32", + "skipType" : "PASS", + "coordinate" : "910,400;1000,400;1000,325" + } ] + }, { + "nodeType" : 1, + "nodeCode" : "07ecda1d-7a0a-47b5-8a91-6186c9473742", + "nodeName" : "副组长", + "permissionFlag" : "1", + "nodeRatio" : 0.000, + "coordinate" : "560,300|560,300", + "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,transfer,copy,pop\"}]", + "skipList" : [ { + "nowNodeCode" : "07ecda1d-7a0a-47b5-8a91-6186c9473742", + "nextNodeCode" : "48117e2c-6328-406b-b102-c4a9d115bb13", + "skipType" : "PASS", + "coordinate" : "610,300;675,300" + } ] + }, { + "nodeType" : 3, + "nodeCode" : "48117e2c-6328-406b-b102-c4a9d115bb13", + "nodeRatio" : 0.000, + "coordinate" : "700,300", + "formCustom" : "N", + "ext" : "[]", + "skipList" : [ { + "nowNodeCode" : "48117e2c-6328-406b-b102-c4a9d115bb13", + "nextNodeCode" : "2bfa3919-78cf-4bc1-b59b-df463a4546f9", + "skipName" : "大于两天", + "skipType" : "PASS", + "skipCondition" : "default@@${leaveDays > 2}", + "coordinate" : "700,275;700,200;810,200|700,237" + }, { + "nowNodeCode" : "48117e2c-6328-406b-b102-c4a9d115bb13", + "nextNodeCode" : "ec17f60e-94e0-4d96-a3ce-3417e9d32d60", + "skipType" : "PASS", + "skipCondition" : "spel@@#{@testLeaveServiceImpl.eval(#leaveDays)}", + "coordinate" : "700,325;700,400;810,400" + } ] + }, { + "nodeType" : 3, + "nodeCode" : "394e1cc8-b8b2-4189-9f81-44448e88ac32", + "nodeRatio" : 0.000, + "coordinate" : "1000,300", + "formCustom" : "N", + "ext" : "[]", + "skipList" : [ { + "nowNodeCode" : "394e1cc8-b8b2-4189-9f81-44448e88ac32", + "nextNodeCode" : "9c93a195-cff2-4e17-ab0a-a4f264191496", + "skipType" : "PASS", + "coordinate" : "1025,300;1130,300" + } ] + }, { + "nodeType" : 1, + "nodeCode" : "9c93a195-cff2-4e17-ab0a-a4f264191496", + "nodeName" : "经理会签", + "permissionFlag" : "1@@3", + "nodeRatio" : 100.000, + "coordinate" : "1180,300|1180,300", + "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,pop,addSign,subSign\"}]", + "skipList" : [ { + "nowNodeCode" : "9c93a195-cff2-4e17-ab0a-a4f264191496", + "nextNodeCode" : "a1a42056-afd1-4e90-88bc-36cbf5a66992", + "skipType" : "PASS", + "coordinate" : "1230,300;1315,300" + } ] + }, { + "nodeType" : 4, + "nodeCode" : "a1a42056-afd1-4e90-88bc-36cbf5a66992", + "nodeRatio" : 0.000, + "coordinate" : "1340,300", + "formCustom" : "N", + "ext" : "[]", + "skipList" : [ { + "nowNodeCode" : "a1a42056-afd1-4e90-88bc-36cbf5a66992", + "nextNodeCode" : "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5", + "skipType" : "PASS", + "coordinate" : "1340,325;1340,400;1430,400" + }, { + "nowNodeCode" : "a1a42056-afd1-4e90-88bc-36cbf5a66992", + "nextNodeCode" : "350dfa0c-a77c-4efa-8527-10efa02d8be4", + "skipType" : "PASS", + "coordinate" : "1340,275;1340,200;1430,200" + } ] + }, { + "nodeType" : 1, + "nodeCode" : "350dfa0c-a77c-4efa-8527-10efa02d8be4", + "nodeName" : "总经理", + "permissionFlag" : "3@@1", + "nodeRatio" : 0.000, + "coordinate" : "1480,200|1480,200", + "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", + "skipList" : [ { + "nowNodeCode" : "350dfa0c-a77c-4efa-8527-10efa02d8be4", + "nextNodeCode" : "c36a46ef-04f9-463f-bad7-4b395c818519", + "skipType" : "PASS", + "coordinate" : "1530,200;1640,200;1640,275" + } ] + }, { + "nodeType" : 1, + "nodeCode" : "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5", + "nodeName" : "副总经理", + "permissionFlag" : "1@@3", + "nodeRatio" : 0.000, + "coordinate" : "1480,400|1480,400", + "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", + "skipList" : [ { + "nowNodeCode" : "fcfdd9f6-f526-4c1a-b71d-88afa31aebc5", + "nextNodeCode" : "c36a46ef-04f9-463f-bad7-4b395c818519", + "skipType" : "PASS", + "coordinate" : "1530,400;1640,400;1640,325" + } ] + }, { + "nodeType" : 4, + "nodeCode" : "c36a46ef-04f9-463f-bad7-4b395c818519", + "nodeRatio" : 0.000, + "coordinate" : "1640,300", + "formCustom" : "N", + "ext" : "[]", + "skipList" : [ { + "nowNodeCode" : "c36a46ef-04f9-463f-bad7-4b395c818519", + "nextNodeCode" : "3fcea762-b53a-4ae1-8365-7bec90444828", + "skipType" : "PASS", + "coordinate" : "1665,300;1770,300" + } ] + }, { + "nodeType" : 1, + "nodeCode" : "3fcea762-b53a-4ae1-8365-7bec90444828", + "nodeName" : "董事", + "permissionFlag" : "1", + "nodeRatio" : 0.000, + "coordinate" : "1820,300|1820,300", + "formCustom" : "N", + "ext" : "[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]", + "skipList" : [ { + "nowNodeCode" : "3fcea762-b53a-4ae1-8365-7bec90444828", + "nextNodeCode" : "9cfbfd3e-6c04-41d6-9fc2-6787a7d2cd31", + "skipType" : "PASS", + "coordinate" : "1870,300;1960,300" + } ] + }, { + "nodeType" : 2, + "nodeCode" : "9cfbfd3e-6c04-41d6-9fc2-6787a7d2cd31", + "nodeName" : "结束", + "nodeRatio" : 0.000, + "coordinate" : "1980,300|1980,300", + "formCustom" : "N", + "ext" : "[]" + } ] +} diff --git a/xinnengyuan/script/sql/ry_workflow.sql b/xinnengyuan/script/sql/ry_workflow.sql index 9455636a..bb3d76e6 100644 --- a/xinnengyuan/script/sql/ry_workflow.sql +++ b/xinnengyuan/script/sql/ry_workflow.sql @@ -3,7 +3,7 @@ -- ---------------------------- CREATE TABLE `flow_definition` ( - `id` bigint unsigned NOT NULL COMMENT '主键id', + `id` bigint NOT NULL COMMENT '主键id', `flow_code` varchar(40) NOT NULL COMMENT '流程编码', `flow_name` varchar(100) NOT NULL COMMENT '流程名称', `category` varchar(100) DEFAULT NULL COMMENT '流程类别', @@ -24,15 +24,14 @@ CREATE TABLE `flow_definition` CREATE TABLE `flow_node` ( - `id` bigint unsigned NOT NULL COMMENT '主键id', + `id` bigint NOT NULL COMMENT '主键id', `node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', `definition_id` bigint NOT NULL COMMENT '流程定义id', `node_code` varchar(100) NOT NULL COMMENT '流程节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '流程节点名称', - `permission_flag` varchar(200) DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用逗号隔开)', + `permission_flag` varchar(200) DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用@@隔开)', `node_ratio` decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值', `coordinate` varchar(100) DEFAULT NULL COMMENT '坐标', - `skip_any_node` varchar(100) DEFAULT 'N' COMMENT '是否可以退回任意节点(Y是 N否)即将删除', `any_node_skip` varchar(100) DEFAULT NULL COMMENT '任意结点跳转', `listener_type` varchar(100) DEFAULT NULL COMMENT '监听器类型', `listener_path` varchar(400) DEFAULT NULL COMMENT '监听器路径', @@ -43,6 +42,7 @@ CREATE TABLE `flow_node` `version` varchar(20) NOT NULL COMMENT '版本', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `ext` text COMMENT '节点扩展属性', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', PRIMARY KEY (`id`) USING BTREE @@ -50,7 +50,7 @@ CREATE TABLE `flow_node` CREATE TABLE `flow_skip` ( - `id` bigint unsigned NOT NULL COMMENT '主键id', + `id` bigint NOT NULL COMMENT '主键id', `definition_id` bigint NOT NULL COMMENT '流程定义id', `now_node_code` varchar(100) NOT NULL COMMENT '当前流程节点的编码', `now_node_type` tinyint(1) DEFAULT NULL COMMENT '当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', @@ -76,7 +76,7 @@ CREATE TABLE `flow_instance` `node_code` varchar(40) NOT NULL COMMENT '流程节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '流程节点名称', `variable` text COMMENT '任务变量', - `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2 审批通过 3自动通过 4终止 5作废 6撤销 7取回 8已完成 9已退回 10失效)', + `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', `activity_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)', `def_json` text COMMENT '流程定义json', `create_by` varchar(64) DEFAULT '' COMMENT '创建者', @@ -96,6 +96,7 @@ CREATE TABLE `flow_task` `node_code` varchar(100) NOT NULL COMMENT '节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '节点名称', `node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', + `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', `form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径', `create_time` datetime DEFAULT NULL COMMENT '创建时间', @@ -107,25 +108,25 @@ CREATE TABLE `flow_task` CREATE TABLE `flow_his_task` ( - `id` bigint(20) unsigned NOT NULL COMMENT '主键id', - `definition_id` bigint(20) NOT NULL COMMENT '对应flow_definition表的id', - `instance_id` bigint(20) NOT NULL COMMENT '对应flow_instance表的id', - `task_id` bigint(20) NOT NULL COMMENT '对应flow_task表的id', + `id` bigint(20) NOT NULL COMMENT '主键id', + `definition_id` bigint(20) NOT NULL COMMENT '对应flow_definition表的id', + `instance_id` bigint(20) NOT NULL COMMENT '对应flow_instance表的id', + `task_id` bigint(20) NOT NULL COMMENT '对应flow_task表的id', `node_code` varchar(100) DEFAULT NULL COMMENT '开始节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '开始节点名称', `node_type` tinyint(1) DEFAULT NULL COMMENT '开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', `target_node_code` varchar(200) DEFAULT NULL COMMENT '目标节点编码', `target_node_name` varchar(200) DEFAULT NULL COMMENT '结束节点名称', `approver` varchar(40) DEFAULT NULL COMMENT '审批者', - `cooperate_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)', + `cooperate_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)', `collaborator` varchar(40) DEFAULT NULL COMMENT '协作人', - `skip_type` varchar(10) NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)', - `flow_status` varchar(20) NOT NULL COMMENT '流程状态(1审批中 2 审批通过 9已退回 10失效)', + `skip_type` varchar(10) NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)', + `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', `form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径', `message` varchar(500) DEFAULT NULL COMMENT '审批意见', `variable` TEXT DEFAULT NULL COMMENT '任务变量', - `ext` varchar(500) DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串', + `ext` TEXT DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串', `create_time` datetime DEFAULT NULL COMMENT '任务开始时间', `update_time` datetime DEFAULT NULL COMMENT '审批完成时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', @@ -136,7 +137,7 @@ CREATE TABLE `flow_his_task` CREATE TABLE `flow_user` ( - `id` bigint unsigned NOT NULL COMMENT '主键id', + `id` bigint NOT NULL COMMENT '主键id', `type` char(1) NOT NULL COMMENT '人员类型(1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限)', `processed_by` varchar(80) DEFAULT NULL COMMENT '权限人', `associated` bigint NOT NULL COMMENT '任务表id', @@ -146,7 +147,8 @@ CREATE TABLE `flow_user` `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', PRIMARY KEY (`id`) USING BTREE, - KEY `user_processed_type` (`processed_by`, `type`) + KEY `user_processed_type` (`processed_by`, `type`), + KEY `user_associated` (`associated`) USING BTREE ) ENGINE = InnoDB COMMENT ='流程用户表'; -- ---------------------------- @@ -212,6 +214,8 @@ insert into sys_menu values ('11622', '流程分类', '11616', '1', 'category', insert into sys_menu values ('11629', '我发起的', '11618', '1', 'myDocument', 'workflow/task/myDocument', '', '1', '1', 'C', '0', '0', '', 'guide', 103, 1, sysdate(), NULL, NULL, ''); insert into sys_menu values ('11630', '流程监控', '11616', '4', 'monitor', '', '', '1', '0', 'M', '0', '0', '', 'monitor', 103, 1, sysdate(), NULL, NULL, ''); insert into sys_menu values ('11631', '待办任务', '11630', '2', 'allTaskWaiting', 'workflow/task/allTaskWaiting', '', '1', '1', 'C', '0', '0', '', 'waiting', 103, 1, sysdate(), NULL, NULL, ''); +insert into sys_menu values ('11700', '流程设计', '11616', '5', 'design/index', 'workflow/processDefinition/design', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, ''); +insert into sys_menu values ('11701', '请假申请', '11616', '6', 'leaveEdit/index', 'workflow/leave/leaveEdit', '', 1, 1, 'C', '1', '0', 'workflow:leave:edit', '#', 103, 1, sysdate(), null, null, ''); -- 流程分类管理相关按钮 insert into sys_menu values ('11623', '流程分类查询', '11622', '1', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:query', '#', 103, 1,sysdate(), null, null, ''); insert into sys_menu values ('11624', '流程分类新增', '11622', '2', '#', '', '', 1, 0, 'F', '0', '0', 'workflow:category:add', '#', 103, 1,sysdate(), null, null, '');