diff --git a/config/nacos/ruoyi-workflow.yml b/config/nacos/ruoyi-workflow.yml
index 008b9b2e..f4135283 100644
--- a/config/nacos/ruoyi-workflow.yml
+++ b/config/nacos/ruoyi-workflow.yml
@@ -32,3 +32,11 @@ warm-flow:
ui: true
# 默认Authorization,如果有多个token,用逗号分隔
token-name: ${sa-token.token-name},clientid
+ # 流程状态对应的三元色
+ chart-status-color:
+ ## 未办理
+ - 157,255,0
+ ## 待办理
+ - 0,0,0
+ ## 已办理
+ - 255,200,0
diff --git a/leave/leave6.json b/leave/leave6.json
new file mode 100644
index 00000000..c0fb172e
--- /dev/null
+++ b/leave/leave6.json
@@ -0,0 +1,212 @@
+{
+ "flowCode" : "leave6",
+ "flowName" : "请假申请-排他并行会签",
+ "category" : "100",
+ "version" : "4",
+ "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",
+ "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.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",
+ "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.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\":\"enum:org.dromara.workflow.common.enums.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",
+ "ext" : "[{\"code\":\"enum:org.dromara.workflow.common.enums.ButtonPermissionEnum\",\"value\":\"back,termination,copy,trust,transfer\"}]",
+ "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\":\"enum:org.dromara.workflow.common.enums.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\":\"enum:org.dromara.workflow.common.enums.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\":\"enum:org.dromara.workflow.common.enums.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\":\"enum:org.dromara.workflow.common.enums.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" : "[]"
+ } ]
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 3b37a447..e63fd82e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,7 +56,7 @@
8.7.2-20250101
- 1.6.6
+ 1.6.7
2.3.0
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDictService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDictService.java
index 69fc30c8..d5b6727e 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDictService.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDictService.java
@@ -1,6 +1,7 @@
package org.dromara.system.api;
import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+import org.dromara.system.api.domain.vo.RemoteDictTypeVo;
import java.util.List;
@@ -11,6 +12,14 @@ import java.util.List;
*/
public interface RemoteDictService {
+ /**
+ * 根据字典类型查询信息
+ *
+ * @param dictType 字典类型
+ * @return 字典类型
+ */
+ RemoteDictTypeVo selectDictTypeByType(String dictType);
+
/**
* 根据字典类型查询字典数据
*
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictTypeVo.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictTypeVo.java
new file mode 100644
index 00000000..5b9b0996
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDictTypeVo.java
@@ -0,0 +1,46 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+ * 字典类型视图对象 sys_dict_type
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class RemoteDictTypeVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 字典主键
+ */
+ private Long dictId;
+
+ /**
+ * 字典名称
+ */
+ private String dictName;
+
+ /**
+ * 字典类型
+ */
+ private String dictType;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
index bf8efc55..519034cf 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
@@ -30,6 +30,11 @@ public interface CacheNames {
*/
String SYS_DICT = "sys_dict";
+ /**
+ * 数据字典类型
+ */
+ String SYS_DICT_TYPE = "sys_dict_type";
+
/**
* 租户
*/
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java
index 77eed8c0..f1e04f76 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/RegexConstants.java
@@ -17,9 +17,14 @@ public interface RegexConstants extends RegexPool {
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
/**
- * 权限标识必须符合 tool:build:list 格式,或者空字符串
+ * 权限标识必须符合以下格式:
+ * 1. 标准格式:xxx:yyy:zzz
+ * - 第一部分(xxx):只能包含字母、数字和下划线(_),不能使用 `*`
+ * - 第二部分(yyy):可以包含字母、数字、下划线(_)和 `*`
+ * - 第三部分(zzz):可以包含字母、数字、下划线(_)和 `*`
+ * 2. 允许空字符串(""),表示没有权限标识
*/
- String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$";
+ String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
/**
* 身份证号码(后6位)
diff --git a/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java
index 62a5d45b..2c0194d0 100644
--- a/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java
+++ b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java
@@ -80,6 +80,12 @@ public class DictServiceImpl implements DictService {
}
}
+ /**
+ * 获取字典下所有的字典值与标签
+ *
+ * @param dictType 字典类型
+ * @return dictValue为key,dictLabel为值组成的Map
+ */
@Override
public Map getAllDictByDictType(String dictType) {
List list = remoteDictService.selectDictDataByType(dictType);
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
index 2669a817..1472d242 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
@@ -103,6 +103,11 @@ public class SysUserBo extends BaseEntity {
*/
private Long roleId;
+ /**
+ * 用户ID
+ */
+ private String userIds;
+
/**
* 排除不查询的用户(工作流用)
*/
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteDictServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteDictServiceImpl.java
index 1caf755c..d20f9325 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteDictServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteDictServiceImpl.java
@@ -5,7 +5,9 @@ import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.system.api.RemoteDictService;
import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+import org.dromara.system.api.domain.vo.RemoteDictTypeVo;
import org.dromara.system.domain.vo.SysDictDataVo;
+import org.dromara.system.domain.vo.SysDictTypeVo;
import org.dromara.system.service.ISysDictTypeService;
import org.springframework.stereotype.Service;
@@ -23,6 +25,12 @@ public class RemoteDictServiceImpl implements RemoteDictService {
private final ISysDictTypeService sysDictTypeService;
+ @Override
+ public RemoteDictTypeVo selectDictTypeByType(String dictType) {
+ SysDictTypeVo vo = sysDictTypeService.selectDictTypeByType(dictType);
+ return MapstructUtils.convert(vo, RemoteDictTypeVo.class);
+ }
+
/**
* 根据字典类型查询字典数据
*
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java
index a6f18c79..13c74cab 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysUserService.java
@@ -15,11 +15,17 @@ import java.util.List;
*/
public interface ISysUserService {
-
+ /**
+ * 根据条件分页查询用户列表
+ *
+ * @param user 用户信息
+ * @param pageQuery 发呢也
+ * @return 用户信息
+ */
TableDataInfo selectPageUserList(SysUserBo user, PageQuery pageQuery);
/**
- * 根据条件分页查询用户列表
+ * 导出用户列表
*
* @param user 用户信息
* @return 用户信息集合信息
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java
index df30a162..fa505951 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDictTypeServiceImpl.java
@@ -117,6 +117,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService {
* @param dictType 字典类型
* @return 字典类型
*/
+ @Cacheable(cacheNames = CacheNames.SYS_DICT_TYPE, key = "#dictType")
@Override
public SysDictTypeVo selectDictTypeByType(String dictType) {
return baseMapper.selectVoOne(new LambdaQueryWrapper().eq(SysDictType::getDictType, dictType));
@@ -136,6 +137,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService {
throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName()));
}
CacheUtils.evict(CacheNames.SYS_DICT, dictType.getDictType());
+ CacheUtils.evict(CacheNames.SYS_DICT_TYPE, dictType.getDictType());
}
baseMapper.deleteByIds(Arrays.asList(dictIds));
}
@@ -146,6 +148,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService {
@Override
public void resetDictCache() {
CacheUtils.clear(CacheNames.SYS_DICT);
+ CacheUtils.clear(CacheNames.SYS_DICT_TYPE);
}
/**
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
index 6492d12d..12d18817 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
@@ -75,6 +75,7 @@ public class SysUserServiceImpl implements ISysUserService {
QueryWrapper wrapper = Wrappers.query();
wrapper.eq("u.del_flag", SystemConstants.NORMAL)
.eq(ObjectUtil.isNotNull(user.getUserId()), "u.user_id", user.getUserId())
+ .in(StringUtils.isNotBlank(user.getUserIds()), "u.user_id", StringUtils.splitTo(user.getUserIds(), Convert::toLong))
.like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
.eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
.like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java
new file mode 100644
index 00000000..7a224051
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/ButtonPermissionEnum.java
@@ -0,0 +1,60 @@
+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);
+
+ private final String label;
+ private final String value;
+ private final boolean selected;
+
+}
+
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/common/enums/NodeExtEnum.java
new file mode 100644
index 00000000..9926a8eb
--- /dev/null
+++ b/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/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java
index ffaff0bb..252cc0e8 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwTaskController.java
@@ -11,6 +11,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.api.domain.vo.RemoteUserVo;
import org.dromara.warm.flow.core.entity.Node;
+import org.dromara.warm.flow.orm.entity.FlowNode;
import org.dromara.workflow.api.domain.RemoteStartProcessReturn;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.domain.bo.*;
@@ -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/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
index 9fdf4847..5feb8dbc 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/CompleteTaskBo.java
@@ -58,6 +58,11 @@ public class CompleteTaskBo implements Serializable {
*/
private Map variables;
+ /**
+ * 弹窗选择的办理人
+ */
+ private Map assigneeMap;
+
/**
* 扩展变量(此处为逗号分隔的ossId)
* @return
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/bo/FlowNextNodeBo.java
new file mode 100644
index 00000000..385eb892
--- /dev/null
+++ b/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 String 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/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermission.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermission.java
new file mode 100644
index 00000000..51f320d2
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/ButtonPermission.java
@@ -0,0 +1,34 @@
+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 ButtonPermission implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 枚举路径
+ */
+ private String code;
+
+ /**
+ * 按钮编码
+ */
+ private String value;
+
+ /**
+ * 是否显示
+ */
+ private boolean show;
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java
index 3fb08d95..8ed7ada2 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/domain/vo/FlowTaskVo.java
@@ -1,16 +1,20 @@
package org.dromara.workflow.domain.vo;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.json.JSONUtil;
import lombok.Data;
+import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.warm.flow.core.entity.User;
import org.dromara.workflow.common.constant.FlowConstant;
+import org.dromara.workflow.common.enums.ButtonPermissionEnum;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
/**
* 任务视图
@@ -173,4 +177,41 @@ public class FlowTaskVo implements Serializable {
*/
@Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "createBy")
private String createByName;
+
+ /**
+ * 是否为申请人节点
+ */
+ private boolean applyNode;
+
+ /**
+ * 按钮权限
+ */
+ private List buttonList;
+
+ public List getButtonList(String ext) {
+ List buttonPermissions = Arrays.stream(ButtonPermissionEnum.values())
+ .map(value -> {
+ ButtonPermission buttonPermission = new ButtonPermission();
+ buttonPermission.setCode(value.getValue());
+ buttonPermission.setShow(false);
+ return buttonPermission;
+ })
+ .collect(Collectors.toList());
+ if (StringUtils.isNotBlank(ext)) {
+ List buttonCodeList = JSONUtil.toList(JSONUtil.parseArray(ext), ButtonPermission.class);
+ if (CollUtil.isNotEmpty(buttonCodeList)) {
+ Optional firstPermission = buttonCodeList.stream().findFirst();
+ firstPermission.ifPresent(permission -> {
+ Set codeSet = Arrays.stream(permission.getValue().split(","))
+ .map(String::trim)
+ .filter(code -> !code.isEmpty())
+ .collect(Collectors.toSet());
+ buttonPermissions.forEach(bp -> bp.setShow(codeSet.contains(bp.getCode())));
+ });
+ }
+ }
+ return buttonPermissions;
+ }
+
}
+
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java
index 76a50607..2830416d 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwCommonService.java
@@ -27,6 +27,15 @@ public interface IFlwCommonService {
*/
Set buildUser(List userList, Long taskId);
+ /**
+ * 构建工作流用户
+ *
+ * @param userIdList 办理用户
+ * @param taskId 任务ID
+ * @return 用户
+ */
+ Set buildFlowUser(List userIdList, Long taskId);
+
/**
* 发送消息
*
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
index 65f990e4..6c7fc6bc 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwTaskService.java
@@ -5,6 +5,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.api.domain.vo.RemoteUserVo;
import org.dromara.warm.flow.core.entity.Node;
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.api.domain.RemoteStartProcessReturn;
import org.dromara.workflow.domain.bo.*;
@@ -132,6 +133,14 @@ public interface IFlwTaskService {
*/
FlowTaskVo selectById(Long taskId);
+ /**
+ * 获取下一节点信息
+ *
+ * @param bo 参数
+ * @return 结果
+ */
+ List getNextNodeList(FlowNextNodeBo bo);
+
/**
* 按照任务id查询任务
*
@@ -188,4 +197,14 @@ public interface IFlwTaskService {
* @return 结果
*/
List currentTaskAllUser(Long taskId);
+
+ /**
+ * 按照节点编码查询节点
+ *
+ * @param nodeCode 节点编码
+ * @param definitionId 流程定义id
+ * @return 节点
+ */
+ FlowNode getByNodeCode(String nodeCode, Long definitionId);
+
}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
index bc83989e..80dcb956 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
@@ -30,6 +30,7 @@ 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.enums.MessageTypeEnum;
+import org.dromara.workflow.common.enums.TaskAssigneeType;
import org.dromara.workflow.service.IFlwCommonService;
import org.dromara.workflow.service.IFlwTaskAssigneeService;
import org.dromara.workflow.service.IFlwTaskService;
@@ -108,6 +109,33 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
return list;
}
+ /**
+ * 构建工作流用户
+ *
+ * @param userIdList 办理用户
+ * @param taskId 任务ID
+ * @return 用户
+ */
+ @Override
+ public Set buildFlowUser(List userIdList, Long taskId) {
+ if (CollUtil.isEmpty(userIdList)) {
+ return Set.of();
+ }
+ Set list = new HashSet<>();
+ Set processedBySet = new HashSet<>();
+ for (String userId : userIdList) {
+ if (!processedBySet.contains(userId)) {
+ FlowUser flowUser = new FlowUser();
+ flowUser.setType(TaskAssigneeType.APPROVER.getCode());
+ flowUser.setProcessedBy(String.valueOf(userId));
+ flowUser.setAssociated(taskId);
+ list.add(flowUser);
+ processedBySet.add(String.valueOf(userId));
+ }
+ }
+ return list;
+ }
+
/**
* 发送消息
*
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
index c565332d..2132ab49 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
@@ -374,6 +374,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
Instance instance = insService.getById(instanceId);
if (instance != null) {
taskService.mergeVariable(instance, variable);
+ insService.updateById(instance);
}
}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
new file mode 100644
index 00000000..1f990107
--- /dev/null
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
@@ -0,0 +1,192 @@
+package org.dromara.workflow.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.api.RemoteDictService;
+import org.dromara.system.api.domain.vo.RemoteDictTypeVo;
+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.springframework.stereotype.Service;
+
+import java.util.*;
+
+/**
+ * 流程设计器-节点扩展属性
+ *
+ * @author AprilWind
+ */
+@ConditionalOnEnable
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FlwNodeExtServiceImpl implements NodeExtService {
+
+ /**
+ * 权限页code
+ */
+ private static final String PERMISSION_TAB = "wf_button_tab";
+
+ /**
+ * 权限页名称
+ */
+ private static final String PERMISSION_TAB_NAME = "权限";
+
+ /**
+ * 枚举类型标识
+ */
+ private static final String ENUM_TYPE_PREFIX = "enum:";
+
+ /**
+ * 基础设置
+ */
+ private static final int TYPE_BASE_SETTING = 1;
+
+ /**
+ * 新页签
+ */
+ private static final int TYPE_NEW_TAB = 2;
+
+ /**
+ * 存储不同 dictType 对应的配置信息
+ */
+ private static final Map> CHILD_NODE_MAP = new HashMap<>();
+
+ static {
+ CHILD_NODE_MAP.put(ButtonPermissionEnum.class.getName(),
+ Map.of("label", "权限按钮", "type", 4, "must", false, "multiple", true));
+ }
+
+ @DubboReference
+ private RemoteDictService remoteDictService;
+
+ /**
+ * 获取节点扩展属性
+ *
+ * @return 结果
+ */
+ @Override
+ public List getNodeExt() {
+ List nodeExtList = new ArrayList<>();
+ // 构建按钮权限页面
+ nodeExtList.add(buildNodeExt(PERMISSION_TAB, PERMISSION_TAB_NAME, TYPE_NEW_TAB,
+ ENUM_TYPE_PREFIX + ButtonPermissionEnum.class.getName()));
+ return nodeExtList;
+ }
+
+ /**
+ * 构建一个 NodeExt 对象
+ *
+ * @param code 编码,此json中唯一
+ * @param name 名称,如果type为新页签时,作为页签名称
+ * @param type 节点类型,1:基础设置,2:新页签
+ * @param sourceTypes 字典/枚举类型来源(逗号分隔)
+ * @return 返回构建好的 NodeExt 对象
+ */
+ private NodeExt buildNodeExt(String code, String name, int type, String sourceTypes) {
+ NodeExt nodeExt = new NodeExt();
+ nodeExt.setCode(code);
+ nodeExt.setType(type);
+ nodeExt.setName(name);
+ nodeExt.setChilds(StringUtils.splitList(sourceTypes)
+ .stream().map(this::buildChildNode)
+ .filter(ObjectUtil::isNotNull)
+ .toList()
+ );
+ return nodeExt;
+ }
+
+ /**
+ * 构建一个 ChildNode 对象
+ *
+ * @param sourceType 字典类型
+ * @return 返回构建好的 ChildNode 对象
+ */
+ private NodeExt.ChildNode buildChildNode(String sourceType) {
+ return sourceType.startsWith(ENUM_TYPE_PREFIX) ?
+ buildChildNodeFromEnum(sourceType.substring(ENUM_TYPE_PREFIX.length())) : buildChildNodeFromDict(sourceType);
+ }
+
+ /**
+ * 根据枚举构建一个 ChildNode 对象
+ *
+ * @param enumClassName 枚举名称
+ * @return 返回构建好的 ChildNode 对象
+ */
+ private NodeExt.ChildNode buildChildNodeFromEnum(String enumClassName) {
+ try {
+ Class> enumClass = Class.forName(enumClassName);
+ if (!enumClass.isEnum()) {
+ return null;
+ }
+ NodeExt.ChildNode childNode = buildChildNodeMap(enumClassName);
+ // 编码,此json中唯
+ childNode.setCode(ENUM_TYPE_PREFIX + enumClassName);
+ // 字典,下拉框和复选框时用到
+ childNode.setDict(Arrays.stream(enumClass.getEnumConstants())
+ .filter(NodeExtEnum.class::isInstance)
+ .map(NodeExtEnum.class::cast)
+ .map(x ->
+ new NodeExt.DictItem(x.getLabel(), x.getValue(), x.isSelected())
+ ).toList());
+ return childNode;
+ } catch (ClassNotFoundException e) {
+ log.error("Enum class not found: {}", enumClassName, e);
+ }
+ return null;
+ }
+
+ /**
+ * 根据字典构建一个 ChildNode 对象
+ *
+ * @param dictType 字典类型
+ * @return 返回构建好的 ChildNode 对象
+ */
+ private NodeExt.ChildNode buildChildNodeFromDict(String dictType) {
+ RemoteDictTypeVo dictTypeDTO = remoteDictService.selectDictTypeByType(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(remoteDictService.selectDictDataByType(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();
+ Map map = CHILD_NODE_MAP.get(key);
+ // label名称
+ childNode.setLabel((String) map.get("label"));
+ // 1:输入框 2:输入框 3:下拉框 4:选择框
+ childNode.setType(Convert.toInt(map.get("type"), 1));
+ // 是否必填
+ childNode.setMust(Convert.toBool(map.get("must"), false));
+ // 是否多选
+ childNode.setMultiple(Convert.toBool(map.get("multiple"), true));
+ return childNode;
+ }
+
+}
diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
index 9a5dac44..d1de14e6 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
@@ -30,9 +30,12 @@ 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.api.domain.RemoteStartProcessReturn;
import org.dromara.workflow.common.ConditionalOnEnable;
@@ -46,6 +49,7 @@ 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.IFlwTaskAssigneeService;
import org.dromara.workflow.service.IFlwTaskService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -79,6 +83,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
private final FlowProcessEventHandler flowProcessEventHandler;
private final FlwTaskMapper flwTaskMapper;
private final FlwCategoryMapper flwCategoryMapper;
+ private final FlowNodeMapper flowNodeMapper;
+ private final IFlwTaskAssigneeService flwTaskAssigneeService;
private final IFlwCommonService flwCommonService;
@DubboReference
@@ -107,6 +113,8 @@ 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);
RemoteStartProcessReturn dto = new RemoteStartProcessReturn();
dto.setProcessInstanceId(taskList.get(0).getInstanceId());
dto.setTaskId(taskList.get(0).getId());
@@ -159,6 +167,11 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
flowProcessEventHandler.processHandler(definition.getFlowCode(), ins.getBusinessId(), ins.getFlowStatus(), null, true);
}
+ // 设置弹窗处理人
+ Map assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
+ if (CollUtil.isNotEmpty(assigneeMap)) {
+ completeTaskBo.getVariables().putAll(assigneeMap);
+ }
// 构建流程参数,包括变量、跳转类型、消息、处理人、权限等信息
FlowParams flowParams = new FlowParams();
flowParams.variable(completeTaskBo.getVariables());
@@ -172,6 +185,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
this.setHandler(instance, flowTask, flowCopyList);
// 消息通知
flwCommonService.sendMessage(definition.getFlowName(), ins.getId(), messageType, notice);
+ //设置下一环节处理人
+ setNextHandler(ins.getId());
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
@@ -179,6 +194,60 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
}
}
+ /**
+ * 设置下一环节处理人
+ *
+ * @param instanceId 实例ID
+ */
+ private void setNextHandler(Long instanceId) {
+ Instance inst = insService.getById(instanceId);
+ List flowTaskList = selectByInstId(instanceId);
+ Map variableMap = inst.getVariableMap();
+ for (FlowTask task : flowTaskList) {
+ if (variableMap != null && variableMap.containsKey(task.getNodeCode())) {
+ String userIds = variableMap.get(task.getNodeCode()).toString();
+ // 批量删除现有任务的办理人记录
+ flwCommonService.getFlowUserService().deleteByTaskIds(List.of(task.getId()));
+ // 批量新增任务办理人记录
+ Set users = flwCommonService.buildFlowUser(List.of(userIds.split(StringUtils.SEPARATOR)), task.getId());
+ flwCommonService.getFlowUserService().saveBatch(new ArrayList<>(users));
+ variableMap.remove(task.getNodeCode());
+ }
+ }
+ taskService.mergeVariable(inst, variableMap);
+ }
+
+ /**
+ * 设置弹窗处理人
+ *
+ * @param assigneeMap 处理人
+ * @param variablesMap 变量
+ */
+ private Map setPopAssigneeMap(Map assigneeMap, Map variablesMap) {
+ Map map = new HashMap<>();
+ if (CollUtil.isEmpty(assigneeMap)) {
+ return map;
+ }
+ 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());
+ }
+ }
+ return map;
+ }
+
/**
* 设置办理人
*
@@ -491,14 +560,52 @@ 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 = getByNodeCode(flowTaskVo.getNodeCode(), instance.getDefinitionId());
+ if (ObjectUtil.isNull(flowNode)) {
+ throw new NullPointerException("当前【" + flowTaskVo.getNodeCode() + "】节点编码不存在");
}
+ flowTaskVo.setButtonList(flowTaskVo.getButtonList(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) {
+ String 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);
+ if (CollUtil.isNotEmpty(nextNodeList)) {
+ //构建以下节点数据
+ List buildNextTaskList = StreamUtils.toList(nextNodeList, node -> taskService.addTask(node, instance, definition, null));
+ //办理人变量替换
+ ExpressionUtil.evalVariable(buildNextTaskList, 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.fetchUsersByStorageId(String.join(StringUtils.SEPARATOR, t.getPermissionList()));
+ if (CollUtil.isNotEmpty(users)) {
+ flowNode.setPermissionFlag(StreamUtils.join(users, e -> String.valueOf(e.getUserId())));
+ }
+ }
+ });
+ }
+ }
+ return nextFlowNodes;
+ }
+
/**
* 按照任务id查询任务
*
@@ -581,10 +688,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() + "不是会签节点!");
}
}
// 设置任务状态并执行对应的任务操作
@@ -689,4 +797,17 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
return remoteUserService.selectListByIds(StreamUtils.toList(userList, e -> Long.valueOf(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/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
index 858f7c2c..c11c9368 100644
--- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
+++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/TestLeaveServiceImpl.java
@@ -48,6 +48,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;
+ }
+
/**
* 查询请假
*/
diff --git a/sql/oracle/oracle_ry_workflow.sql b/sql/oracle/oracle_ry_workflow.sql
index 527a8f8b..08a4adea 100644
--- a/sql/oracle/oracle_ry_workflow.sql
+++ b/sql/oracle/oracle_ry_workflow.sql
@@ -47,7 +47,6 @@ create table FLOW_NODE
NODE_NAME VARCHAR2(100),
NODE_RATIO NUMBER(6, 3),
COORDINATE VARCHAR2(100),
- SKIP_ANY_NODE VARCHAR2(100) default 'N',
ANY_NODE_SKIP VARCHAR2(100),
LISTENER_TYPE VARCHAR2(100),
LISTENER_PATH VARCHAR2(500),
@@ -58,6 +57,7 @@ create table FLOW_NODE
VERSION VARCHAR2(20),
CREATE_TIME DATE,
UPDATE_TIME DATE,
+ EXT VARCHAR2(500),
DEL_FLAG VARCHAR2(1) default '0',
TENANT_ID VARCHAR2(40),
PERMISSION_FLAG VARCHAR2(200)
@@ -73,7 +73,6 @@ comment on column FLOW_NODE.NODE_CODE is '流程节点编码';
comment on column FLOW_NODE.NODE_NAME is '流程节点名称';
comment on column FLOW_NODE.NODE_RATIO is '流程签署比例值';
comment on column FLOW_NODE.COORDINATE is '坐标';
-comment on column FLOW_NODE.SKIP_ANY_NODE is '是否可以退回任意节点(Y是 N否)即将删除';
comment on column FLOW_NODE.ANY_NODE_SKIP is '任意结点跳转';
comment on column FLOW_NODE.LISTENER_TYPE is '监听器类型';
comment on column FLOW_NODE.LISTENER_PATH is '监听器路径';
@@ -84,6 +83,7 @@ comment on column FLOW_NODE.FORM_PATH is '审批表单路径';
comment on column FLOW_NODE.VERSION is '版本';
comment on column FLOW_NODE.CREATE_TIME is '创建时间';
comment on column FLOW_NODE.UPDATE_TIME is '更新时间';
+comment on column FLOW_NODE.EXT is '扩展属性';
comment on column FLOW_NODE.DEL_FLAG is '删除标志';
comment on column FLOW_NODE.TENANT_ID is '租户id';
comment on column FLOW_NODE.PERMISSION_FLAG is '权限标识(权限类型:权限标识,可以多个,用逗号隔开)';
diff --git a/sql/postgres/postgres_ry_workflow.sql b/sql/postgres/postgres_ry_workflow.sql
index 437e3cbe..a18bb09a 100644
--- a/sql/postgres/postgres_ry_workflow.sql
+++ b/sql/postgres/postgres_ry_workflow.sql
@@ -50,7 +50,6 @@ CREATE TABLE flow_node
permission_flag varchar(200) NULL, -- 权限标识(权限类型:权限标识,可以多个,用逗号隔开)
node_ratio numeric(6, 3) NULL, -- 流程签署比例值
coordinate varchar(100) NULL, -- 坐标
- skip_any_node varchar(100) NULL DEFAULT 'N':: character varying, -- 是否可以退回任意节点(Y是 N否)即将删除
any_node_skip varchar(100) NULL, -- 任意结点跳转
listener_type varchar(100) NULL, -- 监听器类型
listener_path varchar(400) NULL, -- 监听器路径
@@ -61,6 +60,7 @@ CREATE TABLE flow_node
"version" varchar(20) NOT NULL, -- 版本
create_time timestamp NULL, -- 创建时间
update_time timestamp NULL, -- 更新时间
+ ext varchar(500) NULL, -- 扩展属性
del_flag bpchar(1) NULL DEFAULT '0':: character varying, -- 删除标志
tenant_id varchar(40) NULL, -- 租户id
CONSTRAINT flow_node_pkey PRIMARY KEY (id)
@@ -75,7 +75,6 @@ COMMENT ON COLUMN flow_node.node_name IS '流程节点名称';
COMMENT ON COLUMN flow_node.permission_flag IS '权限标识(权限类型:权限标识,可以多个,用逗号隔开)';
COMMENT ON COLUMN flow_node.node_ratio IS '流程签署比例值';
COMMENT ON COLUMN flow_node.coordinate IS '坐标';
-COMMENT ON COLUMN flow_node.skip_any_node IS '是否可以退回任意节点(Y是 N否)即将删除';
COMMENT ON COLUMN flow_node.any_node_skip IS '任意结点跳转';
COMMENT ON COLUMN flow_node.listener_type IS '监听器类型';
COMMENT ON COLUMN flow_node.listener_path IS '监听器路径';
@@ -86,6 +85,7 @@ COMMENT ON COLUMN flow_node.form_path IS '审批表单路径';
COMMENT ON COLUMN flow_node."version" IS '版本';
COMMENT ON COLUMN flow_node.create_time IS '创建时间';
COMMENT ON COLUMN flow_node.update_time IS '更新时间';
+COMMENT ON COLUMN flow_node.ext IS '扩展属性';
COMMENT ON COLUMN flow_node.del_flag IS '删除标志';
COMMENT ON COLUMN flow_node.tenant_id IS '租户id';
diff --git a/sql/ry-workflow.sql b/sql/ry-workflow.sql
index a09f3c21..2db3f069 100644
--- a/sql/ry-workflow.sql
+++ b/sql/ry-workflow.sql
@@ -24,7 +24,7 @@ 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 '流程节点编码',
@@ -32,7 +32,6 @@ CREATE TABLE `flow_node`
`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
diff --git a/sql/update/oracle/update_2.2.0-2.3.0.sql b/sql/update/oracle/update_2.2.0-2.3.0.sql
index 2c4835b8..143c909d 100644
--- a/sql/update/oracle/update_2.2.0-2.3.0.sql
+++ b/sql/update/oracle/update_2.2.0-2.3.0.sql
@@ -1,2 +1,6 @@
+ALTER TABLE flow_node DROP COLUMN skip_any_node;
+ALTER TABLE flow_node ADD (ext VARCHAR2(500));
+COMMENT ON COLUMN flow_node.ext IS '扩展属性';
+
ALTER TABLE sys_oss ADD (ext1 VARCHAR2(500));
COMMENT ON COLUMN sys_oss.ext1 IS '扩展属性';
diff --git a/sql/update/postgres/update_2.2.0-2.3.0.sql b/sql/update/postgres/update_2.2.0-2.3.0.sql
index e73b55ff..31bf7465 100644
--- a/sql/update/postgres/update_2.2.0-2.3.0.sql
+++ b/sql/update/postgres/update_2.2.0-2.3.0.sql
@@ -1,2 +1,6 @@
+ALTER TABLE flow_node DROP COLUMN skip_any_node;
+ALTER TABLE flow_node ADD COLUMN ext varchar(500);
+COMMENT ON COLUMN flow_node.ext IS '扩展属性';
+
ALTER TABLE sys_oss ADD COLUMN ext1 varchar(500));
COMMENT ON COLUMN sys_oss.ext1 IS '扩展属性';
diff --git a/sql/update/update_2.2.0-2.3.0.sql b/sql/update/update_2.2.0-2.3.0.sql
index a00ca67b..f1a0efd7 100644
--- a/sql/update/update_2.2.0-2.3.0.sql
+++ b/sql/update/update_2.2.0-2.3.0.sql
@@ -1,2 +1,6 @@
+ALTER TABLE `flow_node` DROP COLUMN `skip_any_node`;
+ALTER TABLE `flow_node`
+ ADD COLUMN `ext` text NULL COMMENT '扩展属性' AFTER `update_time`;
+
ALTER TABLE `sys_oss`
ADD COLUMN `ext1` text NULL COMMENT '扩展属性' AFTER `url`;