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`;