From 9d5be382e52f9ac57199d5ef75cc23f925a4cdb0 Mon Sep 17 00:00:00 2001 From: 潘志宝 <979469083@qq.com> Date: 星期五, 13 十二月 2024 17:07:50 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java | 70 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskService.java | 182 + iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java | 107 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java | 13 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModelNodeType.java | 62 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmCategoryController.java | 11 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java | 20 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmModelArithSettingsService.java | 2 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageService.java | 8 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java | 686 ++++++ iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java | 14 iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java | 1 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java | 47 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java | 8 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelService.java | 65 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmFormDO.java | 6 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java | 13 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmFieldPermissionEnum.java | 33 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java | 2 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java | 17 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java | 102 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java | 9 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/ErrorCodeConstants.java | 8 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/package-info.java | 4 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java | 26 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageServiceImpl.java | 11 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java | 35 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmProcessDefinitionConvert.java | 13 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java | 37 iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java | 11 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java | 212 ++ iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java | 54 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java | 3 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormSDeptLeaderStrategy.java | 56 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java | 118 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java | 4 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.java | 28 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmTaskStatusEnum.java | 11 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java | 783 +++++- iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java | 37 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/dao/StScheduleModelSettingDao.java | 2 iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/MmModelArithSettings.xml | 3 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java | 31 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java | 32 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java | 107 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java | 26 iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/object/BeanUtils.java | 7 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelFormTypeEnum.java | 2 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java | 17 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java | 62 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java | 72 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java | 8 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionService.java | 24 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java | 47 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryServiceImpl.java | 23 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java | 113 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerValueType.java | 2 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java | 22 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleModelSettingService.java | 2 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java | 24 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java | 508 ++++ iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java | 65 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java | 10 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java | 16 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java | 94 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java | 23 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java | 73 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java | 23 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.http | 16 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java | 42 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java | 31 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java | 12 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java | 31 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmModelArithSettingsServiceImpl.java | 5 iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm | 1 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmReasonEnum.java | 49 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmFormMapper.java | 2 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java | 16 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java | 97 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java | 7 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyService.java | 23 iailab-cloud/iailab-gateway/src/main/resources/application.yaml | 14 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java | 47 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java | 23 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java | 24 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java | 87 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/message/BpmMessageEnum.java | 3 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java | 45 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java | 57 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmProcessInstanceConvert.java | 202 + iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmBoundaryEventType.java | 25 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmCategoryDO.java | 2 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/DictTypeConstants.java | 2 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerType.java | 2 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java | 6 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java | 19 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java | 21 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java | 45 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java | 43 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java | 6 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java | 33 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java | 71 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java | 200 + iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java | 3 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryService.java | 7 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java | 32 iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/collection/CollectionUtils.java | 20 /dev/null | 40 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessExpressionController.java | 1 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java | 10 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModeConditionType.java | 36 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java | 21 iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelTypeEnum.java | 31 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java | 7 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceService.java | 54 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java | 106 + iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleModelSettingServiceImpl.java | 5 iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/dao/MmModelArithSettingsDao.java | 3 iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/StScheduleModelSettingDao.xml | 7 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmTaskController.java | 22 iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java | 57 121 files changed, 5,274 insertions(+), 864 deletions(-) diff --git a/iailab-cloud/iailab-gateway/src/main/resources/application.yaml b/iailab-cloud/iailab-gateway/src/main/resources/application.yaml index 0923fc7..f0c4867 100644 --- a/iailab-cloud/iailab-gateway/src/main/resources/application.yaml +++ b/iailab-cloud/iailab-gateway/src/main/resources/application.yaml @@ -122,6 +122,20 @@ - Path=/admin-api/shasteel/** filters: - RewritePath=/admin-api/shasteel/v3/api-docs, /v3/api-docs + ## xmcpms 服务 + - id: xmcpms-admin-api # 路由的编号 + uri: grayLb://xmcpms-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/admin-api/xmcpms/** + filters: + - RewritePath=/admin-api/xmcpms/v3/api-docs, /v3/api-docs + ## xmcsms 服务 + - id: xmcsms-admin-api # 路由的编号 + uri: grayLb://xmcsms-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/admin-api/xmcsms/** + filters: + - RewritePath=/admin-api/xmcsms/v3/api-docs, /v3/api-docs x-forwarded: prefix-enabled: true # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 diff --git a/iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java b/iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java index a69106c..409d01d 100644 --- a/iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java +++ b/iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java @@ -21,6 +21,7 @@ import net.sf.jsqlparser.expression.operators.relational.EqualsTo; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import java.util.HashMap; import java.util.HashSet; @@ -119,7 +120,7 @@ // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限 if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) - && Boolean.FALSE.equals(deptDataPermission.getSelf())) { + && Boolean.FALSE.equals(deptDataPermission.getSelf())) { return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空 } @@ -141,7 +142,7 @@ return deptExpression; } // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?) - return new Parenthesis(new OrExpression(deptExpression, userExpression)); + return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression)); } private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) { @@ -156,8 +157,8 @@ } // 拼接条件 return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), -// new Parenthesis(new ExpressionList<>(CollectionUtils.convertList(deptIds, LongValue::new)))); - new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new))); + // Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号 + new ParenthesedExpressionList(new ExpressionList<LongValue>(CollectionUtils.convertList(deptIds, LongValue::new)))); } private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) { @@ -181,7 +182,7 @@ public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) { String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName(); - addDeptColumn(tableName, columnName); + addDeptColumn(tableName, columnName); } public void addDeptColumn(String tableName, String columnName) { diff --git a/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/collection/CollectionUtils.java b/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/collection/CollectionUtils.java index 7eb3952..9b972c9 100644 --- a/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/collection/CollectionUtils.java +++ b/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/collection/CollectionUtils.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import com.google.common.collect.ImmutableMap; +import com.iailab.framework.common.pojo.PageResult; import java.util.*; import java.util.function.*; @@ -71,6 +72,13 @@ return new ArrayList<>(); } return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) { + if (ArrayUtil.isEmpty(from)) { + return new PageResult<>(from.getTotal()); + } + return new PageResult<>(convertList(from.getList(), func), from.getTotal()); } public static <T, U> List<U> convertListByFlatMap(Collection<T> from, @@ -290,7 +298,15 @@ return valueFunc.apply(t); } - public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc, + public static <T, V extends Comparable<? super V>> T getMinObject(List<T> from, Function<T, V> valueFunc) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + return from.stream().min(Comparator.comparing(valueFunc)).get(); + } + + public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc, BinaryOperator<V> accumulator) { return getSumValue(from, valueFunc, accumulator, null); } @@ -316,7 +332,7 @@ } public static <T> List<T> newArrayList(List<List<T>> list) { - return list.stream().flatMap(Collection::stream).collect(Collectors.toList()); + return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); } } \ No newline at end of file diff --git a/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/object/BeanUtils.java b/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/object/BeanUtils.java index 3c4f20f..80c1beb 100644 --- a/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/object/BeanUtils.java +++ b/iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/object/BeanUtils.java @@ -59,4 +59,11 @@ return new PageResult<>(list, source.getTotal()); } + public static void copyProperties(Object source, Object target) { + if (source == null || target == null) { + return; + } + BeanUtil.copyProperties(source, target, false); + } + } \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/DictTypeConstants.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/DictTypeConstants.java index 132ff9b..f11fc87 100644 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/DictTypeConstants.java +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/DictTypeConstants.java @@ -7,7 +7,5 @@ */ public interface DictTypeConstants { -// String TASK_ASSIGN_RULE_TYPE = "bpm_task_assign_rule_type"; // 任务分配规则类型 -// String TASK_ASSIGN_SCRIPT = "bpm_task_assign_script"; // 任务分配自定义脚本 } diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/ErrorCodeConstants.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/ErrorCodeConstants.java index 3586238..db74e93 100644 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/ErrorCodeConstants.java +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/ErrorCodeConstants.java @@ -23,6 +23,7 @@ "原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置"); ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败,原因:BPMN 流程图中,没有开始事件"); ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败,原因:BPMN 流程图中,用户任务({})的名字不存在"); + ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员"); // ========== 流程定义 1-009-003-000 ========== ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图"); @@ -34,15 +35,16 @@ ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在"); ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中"); ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的"); - ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置"); - ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在"); + ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "任务({})的候选人未配置"); + ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在"); + ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); // ========== 流程任务 1-009-005-000 ========== ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你"); ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在"); ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作"); ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在"); - ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "回退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转"); + ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退回任务失败,目标节点是在并行网关上或非同一路线上,不可跳转"); ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人"); ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在"); ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在"); diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmBoundaryEventType.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmBoundaryEventType.java new file mode 100644 index 0000000..77e0346 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmBoundaryEventType.java @@ -0,0 +1,25 @@ +package com.iailab.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 边界事件 (boundary event) 自定义类型枚举 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmBoundaryEventType { + + USER_TASK_TIMEOUT(1,"用户任务超时"); + + private final Integer type; + private final String name; + + public static BpmBoundaryEventType typeOf(Integer type) { + return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values()); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmFieldPermissionEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmFieldPermissionEnum.java new file mode 100644 index 0000000..6bdc79d --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmFieldPermissionEnum.java @@ -0,0 +1,33 @@ +package com.iailab.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BPM 表单权限的枚举 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmFieldPermissionEnum { + + READ(1, "只读"), + WRITE(2, "可编辑"), + NONE(3, "隐藏"); + + /** + * 权限 + */ + private final Integer permission; + /** + * 名字 + */ + private final String name; + + public static BpmFieldPermissionEnum valueOf(Integer permission) { + return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values()); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelFormTypeEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelFormTypeEnum.java index db286e8..76ab30e 100644 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelFormTypeEnum.java +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelFormTypeEnum.java @@ -9,7 +9,7 @@ /** * BPM 模型的表单类型的枚举 * - * @author iailab + * @author hou */ @Getter @AllArgsConstructor diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelTypeEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelTypeEnum.java new file mode 100644 index 0000000..a726020 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmModelTypeEnum.java @@ -0,0 +1,31 @@ +package com.iailab.module.bpm.enums.definition; + +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 模型的类型的枚举 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmModelTypeEnum implements IntArrayValuable { + + BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/ + SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉、飞书工作流的设计器 + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmModelTypeEnum::getType).toArray(); + + private final Integer type; + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerType.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerType.java index 843d19f..afae0e4 100644 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerType.java +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerType.java @@ -6,7 +6,7 @@ /** * BPM 流程监听器的类型 * - * @author iailab + * @author hou */ @Getter @AllArgsConstructor diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerValueType.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerValueType.java index cba021c..fb281ca 100644 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerValueType.java +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmProcessListenerValueType.java @@ -6,7 +6,7 @@ /** * BPM 流程监听器的值类型 * - * @author iailab + * @author hou */ @Getter @AllArgsConstructor diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModeConditionType.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModeConditionType.java new file mode 100644 index 0000000..ee883ed --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModeConditionType.java @@ -0,0 +1,36 @@ +package com.iailab.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 仿钉钉的流程器设计器条件节点的条件类型 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmSimpleModeConditionType implements IntArrayValuable { + + EXPRESSION(1, "条件表达式"), + RULE(2, "条件规则"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModeConditionType::getType).toArray(); + + private final Integer type; + + private final String name; + + public static BpmSimpleModeConditionType valueOf(Integer type) { + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); + } + + @Override + public int[] array() { + return ARRAYS; + } +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModelNodeType.java new file mode 100644 index 0000000..e74cb02 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -0,0 +1,62 @@ +package com.iailab.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 仿钉钉的流程器设计器的模型节点类型 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmSimpleModelNodeType implements IntArrayValuable { + + // 0 ~ 1 开始和结束 + START_NODE(0, "开始", "startEvent"), + END_NODE(1, "结束", "endEvent"), + + // 10 ~ 49 各种节点 + START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点,Id 固定 + APPROVE_NODE(11, "审批人", "userTask"), + COPY_NODE(12, "抄送人", "serviceTask"), + + // 50 ~ 条件分支 + CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 + CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), + PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"), + INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"), + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); + + private final Integer type; + private final String name; + private final String bpmnType; + + /** + * 判断是否为分支节点 + * + * @param type 节点类型 + */ + public static boolean isBranchNode(Integer type) { + return Objects.equals(CONDITION_BRANCH_NODE.getType(), type) + || Objects.equals(PARALLEL_BRANCH_NODE.getType(), type) + || Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type); + } + + public static BpmSimpleModelNodeType valueOf(Integer type) { + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); + } + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java new file mode 100644 index 0000000..ec5069f --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveMethodEnum.java @@ -0,0 +1,47 @@ +package com.iailab.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 多人审批方式的枚举 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable { + + RANDOM(1, "随机挑选一人审批", null), + RATIO(2, "多人会签(按通过比例)", "${ nrOfCompletedInstances/nrOfInstances >= %s}"), // 会签(按通过比例) + ANY(3, "多人或签(一人通过或拒绝)", "${ nrOfCompletedInstances > 0 }"), // 或签(通过只需一人,拒绝只需一人) + SEQUENTIAL(4, "依次审批", "${ nrOfCompletedInstances >= nrOfInstances }"); // 依次审批 + + /** + * 审批方式 + */ + private final Integer method; + /** + * 名字 + */ + private final String name; + /** + * 完成表达式 + */ + private final String completionCondition; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray(); + + public static BpmUserTaskApproveMethodEnum valueOf(Integer method) { + return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values()); + } + + @Override + public int[] array() { + return ARRAYS; + } +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java new file mode 100644 index 0000000..212c8a7 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskApproveTypeEnum.java @@ -0,0 +1,31 @@ +package com.iailab.module.bpm.enums.definition; + +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 用户任务的审批类型枚举 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskApproveTypeEnum implements IntArrayValuable { + + USER(1), // 人工审批 + AUTO_APPROVE(2), // 自动通过 + AUTO_REJECT(3); // 自动拒绝 + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveTypeEnum::getType).toArray(); + + private final Integer type; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java new file mode 100644 index 0000000..4654b19 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignEmptyHandlerTypeEnum.java @@ -0,0 +1,33 @@ +package com.iailab.module.bpm.enums.definition; + +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * BPM 用户任务的审批人为空时,处理类型枚举 + * + * @author hou + */ +@RequiredArgsConstructor +@Getter +public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements IntArrayValuable { + + APPROVE(1), // 自动通过 + REJECT(2), // 自动拒绝 + ASSIGN_USER(3), // 指定人员审批 + ASSIGN_ADMIN(4), // 转交给流程管理员 + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray(); + + private final Integer type; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java new file mode 100644 index 0000000..db2430f --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskAssignStartUserHandlerTypeEnum.java @@ -0,0 +1,31 @@ +package com.iailab.module.bpm.enums.definition; + +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * BPM 用户任务的审批人与发起人相同时,处理类型枚举 + * + * @author hou + */ +@RequiredArgsConstructor +@Getter +public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements IntArrayValuable { + + START_USER_AUDIT(1), // 由发起人对自己审批 + SKIP(2), // 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 + TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过 + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray(); + + private final Integer type; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java new file mode 100644 index 0000000..2870441 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskRejectHandlerType.java @@ -0,0 +1,35 @@ +package com.iailab.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * BPM 用户任务拒绝处理类型枚举 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskRejectHandlerType implements IntArrayValuable { + + FINISH_PROCESS_INSTANCE(1, "终止流程"), + RETURN_USER_TASK(2, "驳回到指定任务节点"); + + private final Integer type; + private final String name; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskRejectHandlerType::getType).toArray(); + + public static BpmUserTaskRejectHandlerType typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } + + @Override + public int[] array() { + return ARRAYS; + } +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java new file mode 100644 index 0000000..8d470ae --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/definition/BpmUserTaskTimeoutHandlerTypeEnum.java @@ -0,0 +1,32 @@ +package com.iailab.module.bpm.enums.definition; + +import com.iailab.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 用户任务超时处理类型枚举 + * + * @author hou + */ +@Getter +@AllArgsConstructor +public enum BpmUserTaskTimeoutHandlerTypeEnum implements IntArrayValuable { + + REMINDER(1,"自动提醒"), + APPROVE(2, "自动同意"), + REJECT(3, "自动拒绝"); + + private final Integer type; + private final String name; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray(); + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/message/BpmMessageEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/message/BpmMessageEnum.java index 957b295..3a610b3 100644 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/message/BpmMessageEnum.java +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/message/BpmMessageEnum.java @@ -14,7 +14,8 @@ PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人 PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人 - TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人 + TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人 + TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人 /** * 短信模板的标识 diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmDeleteReasonEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmDeleteReasonEnum.java deleted file mode 100644 index 3085f7f..0000000 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmDeleteReasonEnum.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.iailab.module.bpm.enums.task; - -import cn.hutool.core.util.StrUtil; -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 流程实例/任务的删除原因枚举 - * - * @author iailab - */ -@Getter -@AllArgsConstructor -public enum BpmDeleteReasonEnum { - - // ========== 流程实例的独有原因 ========== - - REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法 - CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程 - CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程 - - // ========== 流程任务的独有原因 ========== - - CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 - ; - - private final String reason; - - /** - * 格式化理由 - * - * @param args 参数 - * @return 理由 - */ - public String format(Object... args) { - return StrUtil.format(reason, args); - } - - // ========== 逻辑 ========== - - public static boolean isRejectReason(String reason) { - return StrUtil.startWith(reason, "审批不通过任务,原因:"); - } - -} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java index a63df43..0166bca 100644 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java @@ -1,6 +1,7 @@ package com.iailab.module.bpm.enums.task; import com.iailab.framework.common.core.IntArrayValuable; +import com.iailab.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,6 +16,7 @@ @AllArgsConstructor public enum BpmProcessInstanceStatusEnum implements IntArrayValuable { + NOT_START(-1, "未开始"), RUNNING(1, "审批中"), APPROVE(2, "审批通过"), REJECT(3, "审批不通过"), @@ -36,4 +38,14 @@ return ARRAYS; } + public static boolean isRejectStatus(Integer status) { + return REJECT.getStatus().equals(status); + } + + public static boolean isProcessEndStatus(Integer status) { + return ObjectUtils.equalsAny(status, + APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus()); + } + + } diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmReasonEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmReasonEnum.java new file mode 100644 index 0000000..a74d87d --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmReasonEnum.java @@ -0,0 +1,49 @@ +package com.iailab.module.bpm.enums.task; + +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 流程实例/任务的的处理原因枚举 + * + * @author iailab + */ +@Getter +@AllArgsConstructor +public enum BpmReasonEnum { + + // ========== 流程实例的独有原因 ========== + + REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法 + CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程 + CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程 + + // ========== 流程任务的独有原因 ========== + + CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 + TIMEOUT_APPROVE("审批超时,系统自动通过"), + TIMEOUT_REJECT("审批超时,系统自动不通过"), + ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"), + ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"), + ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"), + ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"), + ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"), + ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"), + APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"), + APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"), + ; + + private final String reason; + + /** + * 格式化理由 + * + * @param args 参数 + * @return 理由 + */ + public String format(Object... args) { + return StrUtil.format(reason, args); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmTaskStatusEnum.java b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmTaskStatusEnum.java index fa5b21d..cd3e646 100644 --- a/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmTaskStatusEnum.java +++ b/iailab-module-bpm/iailab-module-bpm-api/src/main/java/com/iailab/module/bpm/enums/task/BpmTaskStatusEnum.java @@ -1,5 +1,6 @@ package com.iailab.module.bpm.enums.task; +import cn.hutool.core.util.ObjUtil; import com.iailab.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; import lombok.Getter; @@ -13,13 +14,13 @@ @AllArgsConstructor public enum BpmTaskStatusEnum { + NOT_START(-1, "未开始"), RUNNING(1, "审批中"), APPROVE(2, "审批通过"), REJECT(3, "审批不通过"), CANCEL(4, "已取消"), RETURN(5, "已退回"), - DELEGATE(6, "委派中"), /** * 使用场景: @@ -44,6 +45,10 @@ */ private final String name; + public static boolean isRejectStatus(Integer status) { + return REJECT.getStatus().equals(status); + } + /** * 判断该状态是否已经处于 End 最终状态 * <p> @@ -58,4 +63,8 @@ RETURN.getStatus(), APPROVING.getStatus()); } + public static boolean isCancelStatus(Integer status) { + return ObjUtil.equal(status, CANCEL.getStatus()); + } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/package-info.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/package-info.java new file mode 100644 index 0000000..7d705d8 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/package-info.java @@ -0,0 +1,4 @@ +/** + * 基础包,放一些通用的 VO 类 + */ +package com.iailab.module.bpm.controller.admin.base; diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java new file mode 100644 index 0000000..41726c3 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java @@ -0,0 +1,22 @@ +package com.iailab.module.bpm.controller.admin.base.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户精简信息 VO") +@Data +public class UserSimpleBaseVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + private String nickname; + @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png") + private String avatar; + + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long deptId; + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") + private String deptName; + +} \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmCategoryController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmCategoryController.java index d19c4ad..7500c9e 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmCategoryController.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmCategoryController.java @@ -48,6 +48,15 @@ return success(true); } + @PutMapping("/update-sort-batch") + @Operation(summary = "批量更新流程分类的排序") + @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3") + @PreAuthorize("@ss.hasPermission('bpm:category:update')") + public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) { + categoryService.updateCategorySortBatch(ids); + return success(true); + } + @DeleteMapping("/delete") @Operation(summary = "删除流程分类") @Parameter(name = "id", description = "编号", required = true) @@ -78,8 +87,6 @@ @Operation(summary = "获取流程分类的精简信息列表", description = "只包含被开启的分类,主要用于前端的下拉选项") public CommonResult<List<BpmCategoryRespVO>> getCategorySimpleList() { List<BpmCategoryDO> list = categoryService.getCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus()); - BpmCategoryRespVO bpmCategoryRespVO = new BpmCategoryRespVO(); - BpmCategoryRespVO bpmCategoryRespVO1 = bpmCategoryRespVO.setName("123"); list.sort(Comparator.comparingInt(BpmCategoryDO::getSort)); return success(convertList(list, category -> new BpmCategoryRespVO().setId(category.getId()) .setName(category.getName()).setCode(category.getCode()))); diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java index 0be0d1b..43d6863 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmModelController.java @@ -2,12 +2,9 @@ import cn.hutool.core.collection.CollUtil; import com.iailab.framework.common.pojo.CommonResult; -import com.iailab.framework.common.pojo.PageResult; -import com.iailab.framework.common.util.collection.CollectionUtils; -import com.iailab.framework.common.util.io.IoUtils; -import com.iailab.framework.common.util.json.JsonUtils; -import com.iailab.framework.common.util.object.BeanUtils; import com.iailab.module.bpm.controller.admin.definition.vo.model.*; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import com.iailab.module.bpm.convert.definition.BpmModelConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; @@ -16,6 +13,8 @@ import com.iailab.module.bpm.service.definition.BpmModelService; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; +import com.iailab.module.system.api.user.AdminUserApi; +import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -28,15 +27,12 @@ import javax.annotation.Resource; import javax.validation.Valid; -import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Stream; import static com.iailab.framework.common.pojo.CommonResult.success; -import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap; -import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; +import static com.iailab.framework.common.util.collection.CollectionUtils.*; +import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 流程模型") @RestController @@ -53,32 +49,42 @@ @Resource private BpmProcessDefinitionService processDefinitionService; - @GetMapping("/page") + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/list") @Operation(summary = "获得模型分页") - public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) { - PageResult<Model> pageResult = modelService.getModelPage(pageVO); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty(pageResult.getTotal())); + @Parameter(name = "name", description = "模型名称", example = "芋艿") + public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) { + List<Model> list = modelService.getModelList(name); + if (CollUtil.isEmpty(list)) { + return success(Collections.emptyList()); } - // 拼接数据 // 获得 Form 表单 - Set<Long> formIds = convertSet(pageResult.getList(), model -> { - BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); + Set<Long> formIds = convertSet(list, model -> { + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); return metaInfo != null ? metaInfo.getFormId() : null; }); Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds); // 获得 Category Map Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap( - convertSet(pageResult.getList(), Model::getCategory)); + convertSet(list, Model::getCategory)); // 获得 Deployment Map - Set<String> deploymentIds = new HashSet<>(); - pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId())); - Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds); + Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap( + convertSet(list, Model::getDeploymentId)); // 获得 ProcessDefinition Map - List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds); + List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds( + deploymentMap.keySet()); Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId); - return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap)); + // 获得 User Map + Set<Long> userIds = convertSetByFlatMap(list, model -> { + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); + return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty(); + }); + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); + return success(BpmModelConvert.INSTANCE.buildModelList(list, + formMap, categoryMap, deploymentMap, processDefinitionMap, userMap)); } @GetMapping("/get") @@ -97,26 +103,25 @@ @PostMapping("/create") @Operation(summary = "新建模型") @PreAuthorize("@ss.hasPermission('bpm:model:create')") - public CommonResult<String> createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) { - return success(modelService.createModel(createRetVO, null)); + public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) { + return success(modelService.createModel(createRetVO)); } + @PutMapping("/update") @Operation(summary = "修改模型") @PreAuthorize("@ss.hasPermission('bpm:model:update')") - public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) { - modelService.updateModel(modelVO); + public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) { + modelService.updateModel(getLoginUserId(), modelVO); return success(true); } - @PostMapping("/import") - @Operation(summary = "导入模型") - @PreAuthorize("@ss.hasPermission('bpm:model:import')") - public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException { - BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class); - // 读取文件 - String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false); - return success(modelService.createModel(createReqVO, bpmnXml)); + @PutMapping("/update-sort-batch") + @Operation(summary = "批量修改模型排序") + @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3") + public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) { + modelService.updateModelSortBatch(getLoginUserId(), ids); + return success(true); } @PostMapping("/deploy") @@ -124,7 +129,7 @@ @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('bpm:model:deploy')") public CommonResult<Boolean> deployModel(@RequestParam("id") String id) { - modelService.deployModel(id); + modelService.deployModel(getLoginUserId(), id); return success(true); } @@ -132,7 +137,15 @@ @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态") @PreAuthorize("@ss.hasPermission('bpm:model:update')") public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) { - modelService.updateModelState(reqVO.getId(), reqVO.getState()); + modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState()); + return success(true); + } + + @PutMapping("/update-bpmn") + @Operation(summary = "修改模型的 BPMN") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult<Boolean> updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) { + modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml()); return success(true); } @@ -141,8 +154,26 @@ @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('bpm:model:delete')") public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) { - modelService.deleteModel(id); + modelService.deleteModel(getLoginUserId(), id); return success(true); } + // ========== 仿钉钉/飞书的精简模型 ========= + + @GetMapping("/simple/get") + @Operation(summary = "获得仿钉钉流程设计模型") + @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") + public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){ + return success(modelService.getSimpleModel(modelId)); + } + + @PostMapping("/simple/update") + @Operation(summary = "保存仿钉钉流程设计模型") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult<Boolean> updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) { + modelService.updateSimpleModel(getLoginUserId(), reqVO); + return success(Boolean.TRUE); + } + + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java index 9e83900..3f55b48 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java @@ -9,7 +9,6 @@ import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; -import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import com.iailab.module.bpm.service.definition.BpmCategoryService; import com.iailab.module.bpm.service.definition.BpmFormService; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; @@ -34,6 +33,7 @@ import static com.iailab.framework.common.pojo.CommonResult.success; import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; +import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 流程定义") @RestController @@ -69,7 +69,7 @@ convertSet(pageResult.getList(), ProcessDefinition::getId)); // 获得 Form Map Map<Long, BpmFormDO> formMap = formService.getFormMap( - convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfoDO::getFormId)); + convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfoDO::getFormId)); return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionPage( pageResult, deploymentMap, processDefinitionMap, formMap, categoryMap)); } @@ -78,15 +78,24 @@ @Operation(summary = "获得流程定义列表") @Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举 public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList( - @RequestParam("suspensionState") Integer suspensionState) { - List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState); + @RequestParam("suspensionState") Integer suspensionState, @RequestParam(value = "categoryId", required = false) String categoryId) { + // 1.1 获得开启的流程定义 + List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState, categoryId); if (CollUtil.isEmpty(list)) { return success(Collections.emptyList()); } - - // 获得 BpmProcessDefinitionInfoDO Map + // 1.2 移除不可见的流程定义 Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap( convertSet(list, ProcessDefinition::getId)); + Long userId = getLoginUserId(); + list.removeIf(processDefinition -> { + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId()); + return processDefinitionInfo == null // 不存在 + || Boolean.FALSE.equals(processDefinitionInfo.getVisible()) // visible 不可见 + || !processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId); // 无权限发起 + }); + + // 2. 拼接 VO 返回 return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList( list, null, processDefinitionMap, null, null)); } @@ -105,9 +114,8 @@ } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); - List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( - processDefinition, null, processDefinitionInfo, null, null, bpmnModel, userTaskList)); + processDefinition, null, processDefinitionInfo, null, null, bpmnModel)); } } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessExpressionController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessExpressionController.java index 77e70b6..a74c29d 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessExpressionController.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/BpmProcessExpressionController.java @@ -71,4 +71,5 @@ return success(BeanUtils.toBean(pageResult, BpmProcessExpressionRespVO.class)); } + } \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java deleted file mode 100644 index 35dba8b..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.iailab.module.bpm.controller.admin.definition.vo.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.web.multipart.MultipartFile; - -import javax.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 流程模型的导入 Request VO 相比流程模型的新建来说,只是多了一个 bpmnFile 文件") -@Data -public class BpmModeImportReqVO extends BpmModelCreateReqVO { - - @Schema(description = "BPMN 文件", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "BPMN 文件不能为空") - private MultipartFile bpmnFile; - -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java new file mode 100644 index 0000000..318dc92 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java @@ -0,0 +1,20 @@ +package com.iailab.module.bpm.controller.admin.definition.vo.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +@Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO") +@Data +public class BpmModeUpdateBpmnReqVO { + + @Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "流程编号不能为空") + private String id; + + @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "BPMN XML 不能为空") + private String bpmnXml; + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java deleted file mode 100644 index 230b340..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.iailab.module.bpm.controller.admin.definition.vo.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import javax.validation.constraints.NotEmpty; - -@Schema(description = "管理后台 - 流程模型的创建 Request VO") -@Data -public class BpmModelCreateReqVO { - - @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_iailab") - @NotEmpty(message = "流程标识不能为空") - private String key; - - @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") - @NotEmpty(message = "流程名称不能为空") - private String name; - - @Schema(description = "流程描述", example = "我是描述") - private String description; - -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java new file mode 100644 index 0000000..872a81a --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java @@ -0,0 +1,65 @@ +package com.iailab.module.bpm.controller.admin.definition.vo.model; + +import com.iailab.framework.common.validation.InEnum; +import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; +import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * BPM 流程 MetaInfo Response DTO + * 主要用于 { Model#setMetaInfo(String)} 的存储 + * + * 最终,它的字段和 + * {@link com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} + * 是一致的 + * + * @author iailab + */ +@Data +public class BpmModelMetaInfoVO { + + @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg") + @NotEmpty(message = "流程图标不能为空") + @URL(message = "流程图标格式不正确") + private String icon; + + @Schema(description = "流程描述", example = "我是描述") + private String description; + + @Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @InEnum(BpmModelTypeEnum.class) + @NotNull(message = "流程类型不能为空") + private Integer type; + + @Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @InEnum(BpmModelFormTypeEnum.class) + @NotNull(message = "表单类型不能为空") + private Integer formType; + @Schema(description = "表单编号", example = "1024") + private Long formId; // formType 为 NORMAL 使用,必须非空 + @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create") + private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空 + @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view") + private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空 + + @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否可见不能为空") + private Boolean visible; + + @Schema(description = "可发起用户编号数组", example = "[1,2,3]") + private List<Long> startUserIds; + + @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]") + @NotEmpty(message = "可管理用户编号数组不能为空") + private List<Long> managerUserIds; + + @Schema(description = "排序", example = "1") + private Long sort; // 创建时,后端自动生成 + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java deleted file mode 100644 index f734c2c..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.iailab.module.bpm.controller.admin.definition.vo.model; - -import com.iailab.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - - -@Schema(description = "管理后台 - 流程模型分页 Request VO") -@Data -public class BpmModelPageReqVO extends PageParam { - - @Schema(description = "标识,精准匹配", example = "process1641042089407") - private String key; - - @Schema(description = "名字,模糊匹配", example = "平台") - private String name; - - @Schema(description = "流程分类", example = "1") - private String category; - -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java index 7f25fe2..4ea235d 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java @@ -1,47 +1,36 @@ package com.iailab.module.bpm.controller.admin.definition.vo.model; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDateTime; +import java.util.List; @Schema(description = "管理后台 - 流程模型 Response VO") @Data -public class BpmModelRespVO { +public class BpmModelRespVO extends BpmModelMetaInfoVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String id; - @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_iailab") + @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao") private String key; - @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") private String name; - @Schema(description = "流程图标", example = "https://www.baidu.com/iailab.jpg") + @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") private String icon; - - @Schema(description = "流程描述", example = "我是描述") - private String description; @Schema(description = "流程分类编码", example = "1") private String category; @Schema(description = "流程分类名字", example = "请假") private String categoryName; - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") - private Integer formType; - - @Schema(description = "表单编号", example = "1024") - private Long formId; // 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 @Schema(description = "表单名字", example = "请假表单") private String formName; - - @Schema(description = "自定义表单的提交路径", example = "/bpm/oa/leave/create") - private String formCustomCreatePath; // 使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 - @Schema(description = "自定义表单的查看路径", example = "/bpm/oa/leave/view") - private String formCustomViewPath; // ,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; @@ -49,6 +38,9 @@ @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) private String bpmnXml; + @Schema(description = "可发起的用户数组") + private List<UserSimpleBaseVO> startUsers; + /** * 最新部署的流程定义 */ diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java new file mode 100644 index 0000000..3e8fb9f --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java @@ -0,0 +1,26 @@ +package com.iailab.module.bpm.controller.admin.definition.vo.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +@Schema(description = "管理后台 - 流程模型的保存 Request VO") +@Data +public class BpmModelSaveReqVO extends BpmModelMetaInfoVO { + + @Schema(description = "编号", example = "1024") + private String id; + + @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao") + @NotEmpty(message = "流程标识不能为空") + private String key; + + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + @NotEmpty(message = "流程名称不能为空") + private String name; + + @Schema(description = "流程分类", example = "1") + private String category; + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java deleted file mode 100644 index 4dad576..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.iailab.module.bpm.controller.admin.definition.vo.model; - -import com.iailab.framework.common.validation.InEnum; -import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.hibernate.validator.constraints.URL; - -import javax.validation.constraints.NotEmpty; - -@Schema(description = "管理后台 - 流程模型的更新 Request VO") -@Data -public class BpmModelUpdateReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotEmpty(message = "编号不能为空") - private String id; - - @Schema(description = "流程名称", example = "平台") - private String name; - - @Schema(description = "流程图标", example = "https://www.baidu.com/iailab.jpg") - @URL(message = "流程图标格式不正确") - private String icon; - - @Schema(description = "流程描述", example = "我是描述") - private String description; - - @Schema(description = "流程分类", example = "1") - private String category; - - @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) - private String bpmnXml; - - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") - @InEnum(BpmModelFormTypeEnum.class) - private Integer formType; - @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024") - private Long formId; - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", - example = "/bpm/oa/leave/create") - private String formCustomCreatePath; - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", - example = "/bpm/oa/leave/view") - private String formCustomViewPath; - -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java new file mode 100644 index 0000000..6a5c4ff --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java @@ -0,0 +1,212 @@ +package com.iailab.module.bpm.controller.admin.definition.vo.model.simple; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.iailab.framework.common.validation.InEnum; +import com.iailab.module.bpm.enums.definition.*; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO") +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BpmSimpleModelNodeVO { + + @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1") + @NotEmpty(message = "模型节点编号不能为空") + private String id; + + @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "模型节点类型不能为空") + @InEnum(BpmSimpleModelNodeType.class) + private Integer type; + + @Schema(description = "模型节点名称", example = "领导审批") + private String name; + + @Schema(description = "节点展示内容", example = "指定成员: 芋道源码") + private String showText; + + @Schema(description = "子节点") + private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个 + + @Schema(description = "条件节点") + private List<BpmSimpleModelNodeVO> conditionNodes; // 补充说明:有且仅有条件、并行、包容等分支会使用 + + @Schema(description = "条件类型", example = "1") + @InEnum(BpmSimpleModeConditionType.class) + private Integer conditionType; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE + + @Schema(description = "条件表达式", example = "${day>3}") + private String conditionExpression; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE + + @Schema(description = "是否默认条件", example = "true") + private Boolean defaultFlow; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE + /** + * 条件组 + */ + private ConditionGroups conditionGroups; // 仅用于条件节点 BpmSimpleModelNodeType.CONDITION_NODE + + @Schema(description = "候选人策略", example = "30") + @InEnum(BpmTaskCandidateStrategyEnum.class) + private Integer candidateStrategy; // 用于审批,抄送节点 + + @Schema(description = "候选人参数") + private String candidateParam; // 用于审批,抄送节点 + + @Schema(description = "审批节点类型", example = "1") + @InEnum(BpmUserTaskApproveTypeEnum.class) + private Integer approveType; // 用于审批节点 + + @Schema(description = "多人审批方式", example = "1") + @InEnum(BpmUserTaskApproveMethodEnum.class) + private Integer approveMethod; // 用于审批节点 + + @Schema(description = "通过比例", example = "100") + private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置 + + @Schema(description = "表单权限", example = "[]") + private List<Map<String, String>> fieldsPermission; + + @Schema(description = "操作按钮设置", example = "[]") + private List<OperationButtonSetting> buttonsSetting; // 用于审批节点 + + /** + * 审批节点拒绝处理 + */ + private RejectHandler rejectHandler; + + /** + * 审批节点超时处理 + */ + private TimeoutHandler timeoutHandler; + + @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1") + @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class) + private Integer assignStartUserHandlerType; + + /** + * 空处理策略 + */ + private AssignEmptyHandler assignEmptyHandler; + + @Schema(description = "审批节点拒绝处理策略") + @Data + public static class RejectHandler { + + @Schema(description = "拒绝处理类型", example = "1") + @InEnum(BpmUserTaskRejectHandlerType.class) + private Integer type; + + @Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1") + private String returnNodeId; + } + + @Schema(description = "审批节点超时处理策略") + @Valid + @Data + public static class TimeoutHandler { + + @Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "是否开启超时处理不能为空") + private Boolean enable; + + @Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "任务超时未处理的行为不能为空") + @InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class) + private Integer type; + + @Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H") + @NotEmpty(message = "超时时间不能为空") + private String timeDuration; + + @Schema(description = "最大提醒次数", example = "1") + private Integer maxRemindCount; + + } + + @Schema(description = "空处理策略") + @Data + @Valid + public static class AssignEmptyHandler { + + @Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "空处理类型不能为空") + @InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class) + private Integer type; + + @Schema(description = "指定人员审批的用户编号数组", example = "1") + private List<Long> userIds; + + } + + @Schema(description = "操作按钮设置") + @Data + @Valid + public static class OperationButtonSetting { + + // TODO @jason:是不是按钮的标识?id 会和数据库的 id 自增有点模糊,key 标识会更合理一点点哈。 + @Schema(description = "按钮 Id", example = "1") + private Integer id; + + @Schema(description = "显示名称", example = "审批") + private String displayName; + + @Schema(description = "是否启用", example = "true") + private Boolean enable; + } + + @Schema(description = "条件组") + @Data + @Valid + public static class ConditionGroups { + + @Schema(description = "条件组下的条件关系是否为与关系", example = "true") + @NotNull(message = "条件关系不能为空") + private Boolean and; + + @Schema(description = "条件组下的条件", example = "[]") + @NotEmpty(message = "条件不能为空") + private List<Condition> conditions; + } + + @Schema(description = "条件") + @Data + @Valid + public static class Condition { + + @Schema(description = "条件下的规则关系是否为与关系", example = "true") + @NotNull(message = "规则关系不能为空") + private Boolean and; + + @Schema(description = "条件下的规则", example = "[]") + @NotEmpty(message = "规则不能为空") + private List<ConditionRule> rules; + } + + @Schema(description = "条件规则") + @Data + @Valid + public static class ConditionRule { + + @Schema(description = "运行符号", example = "==") + @NotEmpty(message = "运行符号不能为空") + private String opCode; + + @Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId") + @NotEmpty(message = "运算符左边的值不能为空") + private String leftSide; + + @Schema(description = "运算符右边的值", example = "1") + @NotEmpty(message = "运算符右边的值不能为空") + private String rightSide; + } + + // TODO @芋艿:条件;建议可以固化的一些选项;然后有个表达式兜底;要支持 +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java new file mode 100644 index 0000000..c002d16 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java @@ -0,0 +1,24 @@ +package com.iailab.module.bpm.controller.admin.definition.vo.model.simple; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +// TODO @jason:需要考虑,如果某个节点的配置不正确,需要有提示;具体怎么实现,可以讨论下; +@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO") +@Data +public class BpmSimpleModelUpdateReqVO { + + @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "流程模型编号不能为空") + private String id; // 对应 Flowable act_re_model 表 ID_ 字段 + + @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "仿钉钉流程设计模型对象不能为空") + @Valid + private BpmSimpleModelNodeVO simpleModel; + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java index 53d83fa..ab23f9d 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java @@ -16,13 +16,13 @@ @Schema(description = "版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer version; - @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") private String name; - @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iailab") + @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") private String key; - @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.baidu.com/iailab.jpg") + @Schema(description = "流程图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg") private String icon; @Schema(description = "流程描述", example = "我是描述") @@ -32,6 +32,9 @@ private String category; @Schema(description = "流程分类名字", example = "请假") private String categoryName; + + @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer modelType; // 参见 BpmModelTypeEnum 枚举类 @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") private Integer formType; @@ -59,9 +62,12 @@ @Schema(description = "BPMN XML") private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回 - @Schema(description = "发起用户需要选择审批人的任务数组") - private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回 + @Schema(description = "SIMPLE 设计器模型数据 json 格式") + private String simpleModel; // 非必须返回 + @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long sort; + @Schema(description = "BPMN UserTask 用户任务") @Data public static class UserTask { diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmActivityController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmActivityController.java deleted file mode 100644 index d9dc427..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmActivityController.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.iailab.module.bpm.controller.admin.task; - -import com.iailab.framework.common.pojo.CommonResult; -import com.iailab.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import com.iailab.module.bpm.service.task.BpmActivityService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.util.List; - -import static com.iailab.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 流程活动实例") -@RestController -@RequestMapping("/bpm/activity") -@Validated -public class BpmActivityController { - - @Resource - private BpmActivityService activityService; - - @GetMapping("/list") - @Operation(summary = "生成指定流程实例的高亮流程图", - description = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成") - @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true) - @PreAuthorize("@ss.hasPermission('bpm:task:query')") - public CommonResult<List<BpmActivityRespVO>> getActivityList( - @RequestParam("processInstanceId") String processInstanceId) { - return success(activityService.getActivityListByProcessInstanceId(processInstanceId)); - } -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.http b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.http new file mode 100644 index 0000000..c690827 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.http @@ -0,0 +1,16 @@ +### 请求 /bpm/process-instance/get-bpmn 接口 => 成功 +GET {{baseUrl}}/bpm/process-instance/get-bpmn-model-view?id=1d5fb5a6-85f8-11ef-b717-7e93075f94e3 +Content-Type: application/json +tenant-id: 1 +Authorization: Bearer {{token}} + +### 请求 /bpm/process-instance/get-bpmn 接口 => 失败 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932 +#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b +GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b +Content-Type: application/json +tenant-id: 1 +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.java index bf58339..a9cf98a 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -4,14 +4,10 @@ import com.iailab.framework.common.pojo.CommonResult; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.number.NumberUtils; -import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; -import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; -import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; -import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; +import com.iailab.module.bpm.controller.admin.task.vo.instance.*; import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; -import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.service.definition.BpmCategoryService; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.task.BpmProcessInstanceService; @@ -39,6 +35,7 @@ import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + @Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请” @RestController @@ -131,15 +128,13 @@ processInstance.getProcessDefinitionId()); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); - String bpmnXml = BpmnModelUtils.getBpmnXml( - processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId())); AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData(); DeptRespDTO dept = null; - if (startUser != null) { + if (startUser != null && startUser.getDeptId() != null) { dept = deptApi.getDept(startUser.getDeptId()).getCheckedData(); } return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance, - processDefinition, processDefinitionInfo, bpmnXml, startUser, dept)); + processDefinition, processDefinitionInfo, startUser, dept)); } @DeleteMapping("/cancel-by-start-user") @@ -160,4 +155,19 @@ return success(true); } + @GetMapping("/get-approval-detail") + @Operation(summary = "获得审批详情") + @Parameter(name = "id", description = "流程实例的编号", required = true) + @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')") + public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) { + return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO)); + } + + @GetMapping("/get-bpmn-model-view") + @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用") + @Parameter(name = "id", description = "流程实例的编号", required = true) + public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) { + return success(processInstanceService.getProcessInstanceBpmnModelView(id)); + } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java index a45ac46..f2f1efe 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java @@ -6,12 +6,12 @@ import com.iailab.framework.common.util.collection.MapUtils; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.object.BeanUtils; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import com.iailab.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; import com.iailab.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; import com.iailab.module.bpm.service.task.BpmProcessInstanceCopyService; import com.iailab.module.bpm.service.task.BpmProcessInstanceService; -import com.iailab.module.bpm.service.task.BpmTaskService; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; @@ -29,9 +29,9 @@ import java.util.stream.Stream; import static com.iailab.framework.common.pojo.CommonResult.success; -import static com.iailab.framework.common.util.collection.CollectionUtils.convertListByFlatMap; -import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; +import static com.iailab.framework.common.util.collection.CollectionUtils.*; import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + @Tag(name = "管理后台 - 流程实例抄送") @RestController @@ -43,8 +43,6 @@ private BpmProcessInstanceCopyService processInstanceCopyService; @Resource private BpmProcessInstanceService processInstanceService; - @Resource - private BpmTaskService taskService; @Resource private AdminUserApi adminUserApi; @@ -61,18 +59,19 @@ } // 拼接返回 - Map<String, String> taskNameMap = taskService.getTaskNameByTaskIds( - convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getTaskId)); Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap( convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId)); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator())))); - return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> { - MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname())); - MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname())); - MapUtils.findAndThen(taskNameMap, copyVO.getTaskId(), copyVO::setTaskName); + return success(convertPage(pageResult, copy -> { + BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class); + MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()), + user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class))); + MapUtils.findAndThen(userMap, copy.getStartUserId(), + user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class))); MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(), processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime()))); + return copyVO; })); } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmTaskController.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmTaskController.java index a5db91e..cdd7713 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmTaskController.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/BpmTaskController.java @@ -36,7 +36,8 @@ import static com.iailab.framework.common.pojo.CommonResult.success; import static com.iailab.framework.common.util.collection.CollectionUtils.*; -import static com.iailab.framework.web.core.util.WebFrameworkUtils.getLoginUserId; +import static com.iailab.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + @Tag(name = "管理后台 - 流程任务实例") @RestController @@ -117,24 +118,21 @@ @PreAuthorize("@ss.hasPermission('bpm:task:query')") public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId( @RequestParam("processInstanceId") String processInstanceId) { - List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId); + List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true); if (CollUtil.isEmpty(taskList)) { return success(Collections.emptyList()); } // 拼接数据 - HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId); - // 获得 User 和 Dept Map Set<Long> userIds = convertSetByFlatMap(taskList, task -> Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); - userIds.add(NumberUtils.parseLong(processInstance.getStartUserId())); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); // 获得 Form Map Map<Long, BpmFormDO> formMap = formService.getFormMap( convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey()))); - return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance, + return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, formMap, userMap, deptMap)); } @@ -155,7 +153,7 @@ } @GetMapping("/list-by-return") - @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮") + @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮") @Parameter(name = "taskId", description = "当前任务ID", required = true) @PreAuthorize("@ss.hasPermission('bpm:task:update')") public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) { @@ -165,7 +163,7 @@ } @PutMapping("/return") - @Operation(summary = "回退任务", description = "用于【流程详情】的【回退】按钮") + @Operation(summary = "退回任务", description = "用于【流程详情】的【退回】按钮") @PreAuthorize("@ss.hasPermission('bpm:task:update')") public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) { taskService.returnTask(getLoginUserId(), reqVO); @@ -204,6 +202,14 @@ return success(true); } + @PutMapping("/copy") + @Operation(summary = "抄送任务") + @PreAuthorize("@ss.hasPermission('bpm:task:update')") + public CommonResult<Boolean> copyTask(@Valid @RequestBody BpmTaskCopyReqVO reqVO) { + taskService.copyTask(getLoginUserId(), reqVO); + return success(true); + } + @GetMapping("/list-by-parent-task-id") @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表 @Parameter(name = "parentTaskId", description = "父级任务编号", required = true) diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java index e38364e..efd7526 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java @@ -1,5 +1,6 @@ package com.iailab.module.bpm.controller.admin.task.vo.cc; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -12,29 +13,31 @@ @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private Long id; - @Schema(description = "发起人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") - private Long startUserId; - @Schema(description = "发起人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") - private String startUserName; + @Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED) + private UserSimpleBaseVO startUser; @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233") private String processInstanceId; - @Schema(description = "流程实例的名称") + @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试") private String processInstanceName; - @Schema(description = "流程实例的发起时间") + @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime processInstanceStartTime; - @Schema(description = "发起抄送的任务编号") + @Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED) + private String activityId; + @Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED) + private String activityName; + + @Schema(description = "流程活动的编号") private String taskId; - @Schema(description = "发起抄送的任务名称") - private String taskName; - @Schema(description = "抄送人") - private String creator; - @Schema(description = "抄送人昵称") - private String creatorName; + @Schema(description = "抄送人意见") + private String reason; - @Schema(description = "抄送时间") + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED) + private UserSimpleBaseVO createUser; + + @Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java new file mode 100644 index 0000000..0f58984 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java @@ -0,0 +1,37 @@ +package com.iailab.module.bpm.controller.admin.task.vo.instance; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.AssertTrue; +import java.util.Map; + +@Schema(description = "管理后台 - 审批详情 Request VO") +@Data +public class BpmApprovalDetailReqVO { + + @Schema(description = "流程定义的编号", example = "1024") + private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID + + @Schema(description = "流程变量") + private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测 + + @Schema(description = "流程实例的编号", example = "1024") + private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID + + // TODO @芋艿:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。 + @Schema(description = "流程活动编号", example = "StartUserNode") + private String activityId; // 用于获取表单权限。1)发起流程时,传“发起人节点” activityId 可获取发起人的表单权限;2)从抄送列表界面进来时,传抄送的 activityId 可获取抄送人的表单权限; + + @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b") + private String taskId; // 用于获取表单权限。1)从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限 + + @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空") + @JsonIgnore + public boolean isValidProcessParam() { + return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java new file mode 100644 index 0000000..d887c6f --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java @@ -0,0 +1,106 @@ +package com.iailab.module.bpm.controller.admin.task.vo.instance; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + + +@Schema(description = "管理后台 - 审批详情 Response VO") +@Data +public class BpmApprovalDetailRespVO { + + @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举 + + @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List<ActivityNode> activityNodes; + + @Schema(description = "表单字段权限") + private Map<String, String> formFieldsPermission; + + @Schema(description = "待办任务") + private BpmTaskRespVO todoTask; + + /** + * 所属流程定义信息 + */ + private BpmProcessDefinitionRespVO processDefinition; + + /** + * 所属流程实例信息 + */ + private BpmProcessInstanceRespVO processInstance; + + @Schema(description = "活动节点信息") + @Data + public static class ActivityNode { + + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode") + private String id; + + @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人") + private String name; + + @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举 + + @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 BpmTaskStatusEnum 枚举 + + @Schema(description = "节点的开始时间") + private LocalDateTime startTime; + @Schema(description = "节点的结束时间") + private LocalDateTime endTime; + + @Schema(description = "审批节点的任务信息") + private List<ActivityNodeTask> tasks; + + @Schema(description = "候选人策略", example = "35") + private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选 + + @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers + private List<Long> candidateUserIds; + + @Schema(description = "候选人用户列表") + private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表 + + } + + @Schema(description = "活动节点的任务信息") + @Data + public static class ActivityNodeTask { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private String id; + + @Schema(description = "任务所属人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser + private Long owner; + + @Schema(description = "任务所属人", example = "1024") + private UserSimpleBaseVO ownerUser; + + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser + private Long assignee; + + @Schema(description = "任务分配人", example = "2048") + private UserSimpleBaseVO assigneeUser; + + @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; // 参见 BpmTaskStatusEnum 枚举 + + @Schema(description = "审批意见", example = "同意") + private String reason; + + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java new file mode 100644 index 0000000..838981d --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java @@ -0,0 +1,43 @@ +package com.iailab.module.bpm.controller.admin.task.vo.instance; + +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; +import java.util.Set; + +@Schema(description = "管理后台 - 流程示例的 BPMN 视图 Response VO") +@Data +public class BpmProcessInstanceBpmnModelViewRespVO { + + // ========== 基本信息 ========== + + @Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED) + private BpmProcessInstanceRespVO processInstance; + + @Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List<BpmTaskRespVO> tasks; + + @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) + private String bpmnXml; + + @Schema(description = "SIMPLE 模型") + private BpmSimpleModelNodeVO simpleModel; + + // ========== 进度信息 ========== + + @Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set<String> unfinishedTaskActivityIds; // 只包括 UserTask + + @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set<String> finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow + + @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set<String> finishedSequenceFlowActivityIds; // 只包括 SequenceFlow + + @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED) + private Set<String> rejectedTaskActivityIds; // 只包括 UserTask + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java index 3e5f878..f7a4d7f 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java @@ -9,11 +9,12 @@ import static com.iailab.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + @Schema(description = "管理后台 - 流程实例抄送的分页 Request VO") @Data public class BpmProcessInstanceCopyPageReqVO extends PageParam { - @Schema(description = "流程名称", example = "平台") + @Schema(description = "流程名称", example = "芋道") private String processInstanceName; @Schema(description = "创建时间") diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java index abfc279..ce9b06f 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java @@ -11,15 +11,16 @@ import static com.iailab.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + @Schema(description = "管理后台 - 流程实例分页 Request VO") @Data public class BpmProcessInstancePageReqVO extends PageParam { - @Schema(description = "流程名称", example = "平台") + @Schema(description = "流程名称", example = "芋道") private String name; - @Schema(description = "流程定义的编号", example = "2048") - private String processDefinitionId; + @Schema(description = "流程定义的标识", example = "2048") + private String processDefinitionKey; // 精准匹配 @Schema(description = "流程实例的状态", example = "1") @InEnum(BpmProcessInstanceStatusEnum.class) diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java index 4885ed6..5647f31 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java @@ -1,5 +1,6 @@ package com.iailab.module.bpm.controller.admin.task.vo.instance; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -15,7 +16,7 @@ @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String id; - @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") + @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") private String name; @Schema(description = "流程分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @@ -44,7 +45,7 @@ /** * 发起流程的用户 */ - private User startUser; + private UserSimpleBaseVO startUser; @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") private String processDefinitionId; @@ -58,22 +59,6 @@ */ private List<Task> tasks; // 仅在流程实例分页才返回 - @Schema(description = "用户信息") - @Data - public static class User { - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long id; - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "iailab") - private String nickname; - - @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long deptId; - @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") - private String deptName; - - } - @Schema(description = "流程任务") @Data public static class Task { @@ -81,7 +66,7 @@ @Schema(description = "流程任务的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String id; - @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") private String name; } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java new file mode 100644 index 0000000..b63d714 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java @@ -0,0 +1,23 @@ +package com.iailab.module.bpm.controller.admin.task.vo.task; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.util.Collection; + +@Schema(description = "管理后台 - 抄送流程任务的 Request VO") +@Data +public class BpmTaskCopyReqVO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]") + @NotEmpty(message = "抄送用户不能为空") + private Collection<Long> copyUserIds; + + @Schema(description = "抄送意见", example = "帮忙看看!") + private String reason; +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java index 90e4f0f..d429b89 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java @@ -1,5 +1,7 @@ package com.iailab.module.bpm.controller.admin.task.vo.task; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -15,7 +17,7 @@ @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String id; - @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") + @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") private String name; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) @@ -33,14 +35,21 @@ @Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") private String reason; + @Schema(description = "任务负责人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser + private Long owner; /** * 负责人的用户信息 */ - private BpmProcessInstanceRespVO.User ownerUser; + private UserSimpleBaseVO ownerUser; + + @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048") + @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser + private Long assignee; /** * 审核的用户信息 */ - private BpmProcessInstanceRespVO.User assigneeUser; + private UserSimpleBaseVO assigneeUser; @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one") private String taskDefinitionKey; @@ -55,18 +64,20 @@ @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String parentTaskId; @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask") - private List<BpmTaskRespVO> children; + private List<BpmTaskRespVO> children; // 由加签生成,包含多层子任务 @Schema(description = "表单编号", example = "1024") private Long formId; @Schema(description = "表单名字", example = "请假表单") private String formName; - @Schema(description = "表单的配置-JSON 字符串") + @Schema(description = "表单的配置,JSON 字符串") private String formConf; @Schema(description = "表单项的数组") private List<String> formFields; @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED) private Map<String, Object> formVariables; + @Schema(description = "操作按钮设置值") + private Map<Integer, OperationButtonSetting> buttonsSetting; @Data @Schema(description = "流程实例") @@ -75,7 +86,7 @@ @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private String id; - @Schema(description = "流程实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "平台") + @Schema(description = "流程实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") private String name; @Schema(description = "提交时间", requiredMode = Schema.RequiredMode.REQUIRED) @@ -87,8 +98,20 @@ /** * 发起人的用户信息 */ - private BpmProcessInstanceRespVO.User startUser; + private UserSimpleBaseVO startUser; } + @Data + @Schema(description = "操作按钮设置") + public static class OperationButtonSetting { + + @Schema(description = "显示名称", example = "审批") + private String displayName; + + @Schema(description = "是否启用", example = "true") + private Boolean enable; + } + + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java index 6ad20c0..1f9e2ea 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmModelConvert.java @@ -1,19 +1,18 @@ package com.iailab.module.bpm.convert.definition; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; -import com.iailab.framework.common.pojo.PageResult; -import com.iailab.framework.common.util.collection.CollectionUtils; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.json.JsonUtils; import com.iailab.framework.common.util.object.BeanUtils; -import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO; -import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; -import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; +import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.Model; @@ -21,60 +20,64 @@ import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Objects; + +import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; /** * 流程模型 Convert * - * @author yunlongn + * @author hou */ @Mapper public interface BpmModelConvert { BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class); - default PageResult<BpmModelRespVO> buildModelPage(PageResult<Model> pageResult, - Map<Long, BpmFormDO> formMap, - Map<String, BpmCategoryDO> categoryMap, Map<String, Deployment> deploymentMap, - Map<String, ProcessDefinition> processDefinitionMap) { - List<BpmModelRespVO> list = CollectionUtils.convertList(pageResult.getList(), model -> { - BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model); + default List<BpmModelRespVO> buildModelList(List<Model> list, + Map<Long, BpmFormDO> formMap, + Map<String, BpmCategoryDO> categoryMap, + Map<String, Deployment> deploymentMap, + Map<String, ProcessDefinition> processDefinitionMap, + Map<Long, AdminUserRespDTO> userMap) { + List<BpmModelRespVO> result = convertList(list, model -> { + BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null; BpmCategoryDO category = categoryMap.get(model.getCategory()); Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null; - ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null; - return buildModel0(model, metaInfo, form, category, deployment, processDefinition); + ProcessDefinition processDefinition = model.getDeploymentId() != null ? + processDefinitionMap.get(model.getDeploymentId()) : null; + List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null; + return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers); }); - return new PageResult<>(list, pageResult.getTotal()); + // 排序 + result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort)); + return result; } - default BpmModelRespVO buildModel(Model model, - byte[] bpmnBytes) { - BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model); - BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null); + default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) { + BpmModelMetaInfoVO metaInfo = parseMetaInfo(model); + BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null); if (ArrayUtil.isNotEmpty(bpmnBytes)) { - modelVO.setBpmnXml(new String(bpmnBytes)); + modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes)); } return modelVO; } default BpmModelRespVO buildModel0(Model model, - BpmModelMetaInfoRespDTO metaInfo, BpmFormDO form, BpmCategoryDO category, - Deployment deployment, ProcessDefinition processDefinition) { + BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category, + Deployment deployment, ProcessDefinition processDefinition, + List<AdminUserRespDTO> startUsers) { BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName()) .setKey(model.getKey()).setCategory(model.getCategory()) .setCreateTime(DateUtils.of(model.getCreateTime())); // Form - if (metaInfo != null) { - modelRespVO.setFormType(metaInfo.getFormType()).setFormId(metaInfo.getFormId()) - .setFormCustomCreatePath(metaInfo.getFormCustomCreatePath()) - .setFormCustomViewPath(metaInfo.getFormCustomViewPath()); - modelRespVO.setIcon(metaInfo.getIcon()).setDescription(metaInfo.getDescription()); - } + BeanUtils.copyProperties(metaInfo, modelRespVO); if (form != null) { - modelRespVO.setFormId(form.getId()).setFormName(form.getName()); + modelRespVO.setFormName(form.getName()); } // Category if (category != null) { @@ -89,49 +92,34 @@ modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime())); } } + // User + modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class)); return modelRespVO; } - default void copyToCreateModel(Model model, BpmModelCreateReqVO bean) { - model.setName(bean.getName()); - model.setKey(bean.getKey()); - model.setMetaInfo(buildMetaInfoStr(null, - null, bean.getDescription(), - null, null, null, null)); + default void copyToModel(Model model, BpmModelSaveReqVO reqVO) { + model.setName(reqVO.getName()); + model.setKey(reqVO.getKey()); + model.setCategory(reqVO.getCategory()); + model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class))); } - default void copyToUpdateModel(Model model, BpmModelUpdateReqVO bean) { - model.setName(bean.getName()); - model.setCategory(bean.getCategory()); - model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model), - bean.getIcon(), bean.getDescription(), - bean.getFormType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath())); - } - - default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, - String icon, String description, - Integer formType, Long formId, String formCustomCreatePath, String formCustomViewPath) { - if (metaInfo == null) { - metaInfo = new BpmModelMetaInfoRespDTO(); + default BpmModelMetaInfoVO parseMetaInfo(Model model) { + BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class); + if (vo == null) { + return null; } - // 只有非空,才进行设置,避免更新时的覆盖 - if (StrUtil.isNotEmpty(icon)) { - metaInfo.setIcon(icon); + if (vo.getManagerUserIds() == null) { + vo.setManagerUserIds(Collections.emptyList()); } - if (StrUtil.isNotEmpty(description)) { - metaInfo.setDescription(description); + if (vo.getStartUserIds() == null) { + vo.setStartUserIds(Collections.emptyList()); } - if (Objects.nonNull(formType)) { - metaInfo.setFormType(formType); - metaInfo.setFormId(formId); - metaInfo.setFormCustomCreatePath(formCustomCreatePath); - metaInfo.setFormCustomViewPath(formCustomViewPath); + // 如果为空,兜底处理,使用 createTime 创建时间 + if (vo.getSort() == null) { + vo.setSort(model.getCreateTime().getTime()); } - return JsonUtils.toJsonString(metaInfo); - } - - default BpmModelMetaInfoRespDTO buildMetaInfo(Model model) { - return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); + return vo; } } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmProcessDefinitionConvert.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmProcessDefinitionConvert.java index e51ccd7..c197220 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmProcessDefinitionConvert.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/definition/BpmProcessDefinitionConvert.java @@ -11,7 +11,6 @@ import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.UserTask; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.ProcessDefinition; @@ -20,6 +19,7 @@ import org.mapstruct.MappingTarget; import org.mapstruct.factory.Mappers; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -47,7 +47,7 @@ Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap, Map<Long, BpmFormDO> formMap, Map<String, BpmCategoryDO> categoryMap) { - return CollectionUtils.convertList(list, definition -> { + List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> { Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class); BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class); BpmFormDO form = null; @@ -55,8 +55,11 @@ form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class); } BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class); - return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null); + return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null); }); + // 排序 + result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort)); + return result; } default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition, @@ -64,8 +67,7 @@ BpmProcessDefinitionInfoDO processDefinitionInfo, BpmFormDO form, BpmCategoryDO category, - BpmnModel bpmnModel, - List<UserTask> startUserSelectUserTaskList) { + BpmnModel bpmnModel) { BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class); respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode()); // Deployment @@ -87,7 +89,6 @@ // BpmnModel if (bpmnModel != null) { respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel)); - respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionRespVO.UserTask.class)); } return respVO; } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmActivityConvert.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmActivityConvert.java deleted file mode 100644 index 89a2e4f..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmActivityConvert.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.iailab.module.bpm.convert.task; - -import com.iailab.framework.common.util.date.DateUtils; -import com.iailab.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import org.flowable.engine.history.HistoricActivityInstance; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -/** - * BPM 活动 Convert - * - * @author iailab - */ -@Mapper(uses = DateUtils.class) -public interface BpmActivityConvert { - - BpmActivityConvert INSTANCE = Mappers.getMapper(BpmActivityConvert.class); - - List<BpmActivityRespVO> convertList(List<HistoricActivityInstance> list); - - @Mappings({ - @Mapping(source = "activityId", target = "key"), - @Mapping(source = "activityType", target = "type") - }) - BpmActivityRespVO convert(HistoricActivityInstance bean); -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmProcessInstanceConvert.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmProcessInstanceConvert.java index 84dfc43..2f3c24d 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmProcessInstanceConvert.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmProcessInstanceConvert.java @@ -1,35 +1,54 @@ package com.iailab.module.bpm.convert.task; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; + import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.collection.MapUtils; +import com.iailab.framework.common.util.collection.SetUtils; import com.iailab.framework.common.util.number.NumberUtils; import com.iailab.framework.common.util.object.BeanUtils; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO; +import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO; +import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; +import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import com.iailab.module.bpm.convert.definition.BpmProcessDefinitionConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import com.iailab.module.bpm.event.BpmProcessInstanceStatusEvent; +import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; import com.iailab.module.system.api.dept.dto.DeptRespDTO; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; +import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.factory.Mappers; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; + +import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; +import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; + /** * 流程实例 Convert * - * @author iailab + * @author 芋道源码 */ @Mapper public interface BpmProcessInstanceConvert { @@ -55,7 +74,7 @@ if (userMap != null) { AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId())); if (startUser != null) { - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName())); } } @@ -65,20 +84,18 @@ default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance, ProcessDefinition processDefinition, - BpmProcessDefinitionInfoDO processDefinitionExt, - String bpmnXml, + BpmProcessDefinitionInfoDO processDefinitionInfo, AdminUserRespDTO startUser, DeptRespDTO dept) { BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class); - respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)); - respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); + respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)) + .setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance)); // definition respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)); - copyTo(processDefinitionExt, respVO.getProcessDefinition()); - respVO.getProcessDefinition().setBpmnXml(bpmnXml); + copyTo(processDefinitionInfo, respVO.getProcessDefinition()); // user if (startUser != null) { - respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); + respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); if (dept != null) { respVO.getStartUser().setDeptName(dept.getName()); } @@ -89,12 +106,7 @@ @Mapping(source = "from.id", target = "to.id", ignore = true) void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); - default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, HistoricProcessInstance instance, Integer status) { - return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) - .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); - } - - default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {; + default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) { return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); } @@ -108,10 +120,162 @@ default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) { return new BpmMessageSendWhenProcessInstanceRejectReqDTO() - .setProcessInstanceName(instance.getName()) - .setProcessInstanceId(instance.getId()) - .setReason(reason) - .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); + .setProcessInstanceName(instance.getName()) + .setProcessInstanceId(instance.getId()) + .setReason(reason) + .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); + } + + default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, + List<HistoricTaskInstance> taskInstances, + BpmnModel bpmnModel, + BpmSimpleModelNodeVO simpleModel, + Set<String> unfinishedTaskActivityIds, + Set<String> finishedTaskActivityIds, + Set<String> finishedSequenceFlowActivityIds, + Set<String> rejectTaskActivityIds, + Map<Long, AdminUserRespDTO> userMap, + Map<Long, DeptRespDTO> deptMap) { + BpmProcessInstanceBpmnModelViewRespVO respVO = new BpmProcessInstanceBpmnModelViewRespVO(); + // 基本信息 + respVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class, o -> o + .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) + .setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap))); + respVO.setTasks(convertList(taskInstances, task -> BeanUtils.toBean(task, BpmTaskRespVO.class) + .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)) + .setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap)) + .setOwnerUser(buildUser(task.getOwner(), userMap, deptMap)))); + respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel)); + respVO.setSimpleModel(simpleModel); + // 进度信息 + respVO.setUnfinishedTaskActivityIds(unfinishedTaskActivityIds) + .setFinishedTaskActivityIds(finishedTaskActivityIds) + .setFinishedSequenceFlowActivityIds(finishedSequenceFlowActivityIds) + .setRejectedTaskActivityIds(rejectTaskActivityIds); + return respVO; + } + + default UserSimpleBaseVO buildUser(String userIdStr, + Map<Long, AdminUserRespDTO> userMap, + Map<Long, DeptRespDTO> deptMap) { + if (StrUtil.isEmpty(userIdStr)) { + return null; + } + Long userId = NumberUtils.parseLong(userIdStr); + return buildUser(userId, userMap, deptMap); + } + + default UserSimpleBaseVO buildUser(Long userId, + Map<Long, AdminUserRespDTO> userMap, + Map<Long, DeptRespDTO> deptMap) { + if (userId == null) { + return null; + } + AdminUserRespDTO user = userMap.get(userId); + if (user == null) { + return null; + } + UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class); + DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null; + if (dept != null) { + userVO.setDeptName(dept.getName()); + } + return userVO; + } + + default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) { + if (task == null) { + return null; + } + return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class) + .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); + } + + default Set<Long> parseUserIds(HistoricProcessInstance processInstance, + List<BpmApprovalDetailRespVO.ActivityNode> activityNodes, + BpmTaskRespVO todoTask) { + Set<Long> userIds = new HashSet<>(); + if (processInstance != null) { + userIds.add(NumberUtils.parseLong(processInstance.getStartUserId())); + } + for (BpmApprovalDetailRespVO.ActivityNode activityNode : activityNodes) { + CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getAssignee)); + CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getOwner)); + CollUtil.addAll(userIds, activityNode.getCandidateUserIds()); + } + if (todoTask != null) { + CollUtil.addIfAbsent(userIds, todoTask.getAssignee()); + CollUtil.addIfAbsent(userIds, todoTask.getOwner()); + if (CollUtil.isNotEmpty(todoTask.getChildren())) { + CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getAssignee)); + CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getOwner)); + } + } + return userIds; + } + + default Set<Long> parseUserIds02(HistoricProcessInstance processInstance, + List<HistoricTaskInstance> tasks) { + Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); + tasks.forEach(task -> { + CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee()))); + CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner()))); + }); + return userIds; + } + + default BpmApprovalDetailRespVO buildApprovalDetail(BpmnModel bpmnModel, + ProcessDefinition processDefinition, + BpmProcessDefinitionInfoDO processDefinitionInfo, + HistoricProcessInstance processInstance, + Integer processInstanceStatus, + List<BpmApprovalDetailRespVO.ActivityNode> activityNodes, + BpmTaskRespVO todoTask, + Map<String, String> formFieldsPermission, + Map<Long, AdminUserRespDTO> userMap, + Map<Long, DeptRespDTO> deptMap) { + // 1.1 流程实例 + BpmProcessInstanceRespVO processInstanceResp = null; + if (processInstance != null) { + AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); + DeptRespDTO dept = startUser != null ? deptMap.get(startUser.getDeptId()) : null; + processInstanceResp = buildProcessInstance(processInstance, null, null, startUser, dept); + } + + // 1.2 流程定义 + BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition( + processDefinition, null, processDefinitionInfo, null, null, bpmnModel); + + // 1.3 流程节点 + activityNodes.forEach(approveNode -> { + if (approveNode.getTasks() != null) { + approveNode.getTasks().forEach(task -> { + task.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap)); + task.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap)); + }); + } + approveNode.setCandidateUsers(convertList(approveNode.getCandidateUserIds(), userId -> buildUser(userId, userMap, deptMap))); + }); + + // 1.4 待办任务 + if (todoTask != null) { + todoTask.setAssigneeUser(buildUser(todoTask.getAssignee(), userMap, deptMap)); + todoTask.setOwnerUser(buildUser(todoTask.getOwner(), userMap, deptMap)); + if (CollUtil.isNotEmpty(todoTask.getChildren())) { + todoTask.getChildren().forEach(childTask -> { + childTask.setAssigneeUser(buildUser(childTask.getAssignee(), userMap, deptMap)); + childTask.setOwnerUser(buildUser(childTask.getOwner(), userMap, deptMap)); + }); + } + } + + // 2. 拼接起来 + return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus) + .setProcessDefinition(definitionResp) + .setProcessInstance(processInstanceResp) + .setFormFieldsPermission(formFieldsPermission) + .setTodoTask(todoTask) + .setActivityNodes(activityNodes); } } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java index 93025be..5b1e179 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/convert/task/BpmTaskConvert.java @@ -1,14 +1,15 @@ package com.iailab.module.bpm.convert.task; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.collection.CollectionUtils; import com.iailab.framework.common.util.number.NumberUtils; import com.iailab.framework.common.util.object.BeanUtils; -import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; +import com.iailab.module.bpm.controller.admin.base.user.UserSimpleBaseVO; import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; +import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; import com.iailab.module.system.api.dept.dto.DeptRespDTO; @@ -25,13 +26,14 @@ import java.util.List; import java.util.Map; -import static com.iailab.framework.common.util.collection.CollectionUtils.*; +import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; import static com.iailab.framework.common.util.collection.MapUtils.findAndThen; + /** * Bpm 任务 Convert * - * @author iailab + * @author 芋道源码 */ @Mapper public interface BpmTaskConvert { @@ -48,7 +50,7 @@ } taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); }); } @@ -56,13 +58,13 @@ Map<String, HistoricProcessInstance> processInstanceMap, Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { - List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(pageResult.getList(), task -> { + List<BpmTaskRespVO> taskVOList = convertList(pageResult.getList(), task -> { BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); // 用户信息 AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); if (assignUser != null) { - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); } // 流程实例 @@ -70,7 +72,7 @@ if (processInstance != null) { AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); + taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class)); } return taskVO; }); @@ -78,17 +80,17 @@ } default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList, - HistoricProcessInstance processInstance, Map<Long, BpmFormDO> formMap, Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { - List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(taskList, task -> { + return convertList(taskList, task -> { + // 特殊:已取消的任务,不返回 BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class); - taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)); - // 流程实例 - AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId())); - taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class)); - taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class)); + Integer taskStatus = FlowableUtils.getTaskStatus(task); + if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) { + return null; + } + taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task)); // 表单信息 BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class); if (form != null) { @@ -96,27 +98,10 @@ .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task)); } // 用户信息 - AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); - if (assignUser != null) { - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); - findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName())); - } - AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); - if (ownerUser != null) { - taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); - findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); - } + buildTaskAssignee(taskVO, task.getAssignee(), userMap, deptMap); + buildTaskOwner(taskVO, task.getOwner(), userMap, deptMap); return taskVO; }); - - // 拼接父子关系 - Map<String, List<BpmTaskRespVO>> childrenTaskMap = convertMultiMap( - filterList(taskVOList, r -> StrUtil.isNotEmpty(r.getParentTaskId())), - BpmTaskRespVO::getParentTaskId); - for (BpmTaskRespVO taskVO : taskVOList) { - taskVO.setChildren(childrenTaskMap.get(taskVO.getId())); - } - return filterList(taskVOList, r -> StrUtil.isEmpty(r.getParentTaskId())); } default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList, @@ -125,7 +110,7 @@ return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> { AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee())); if (assignUser != null) { - taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class)); + taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); DeptRespDTO dept = deptMap.get(assignUser.getDeptId()); if (dept != null) { taskVO.getAssigneeUser().setDeptName(dept.getName()); @@ -133,10 +118,19 @@ } AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner())); if (ownerUser != null) { - taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class)); + taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName())); } })); + } + + default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks, + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) { + return BeanUtils.toBean(todoTask, BpmTaskRespVO.class) + .setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask)) + .setButtonsSetting(buttonsSetting) + .setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class) + .setStatus(FlowableUtils.getTaskStatus(childTask)))); } default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, @@ -149,14 +143,50 @@ return reqDTO; } + default void buildTaskOwner(BpmTaskRespVO task, String taskOwner, + Map<Long, AdminUserRespDTO> userMap, + Map<Long, DeptRespDTO> deptMap) { + AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner)); + if (ownerUser != null) { + task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class)); + findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName())); + } + } + + default void buildTaskChildren(BpmTaskRespVO task, Map<String, List<Task>> childrenTaskMap, + Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) { + List<Task> childTasks = childrenTaskMap.get(task.getId()); + if (CollUtil.isNotEmpty(childTasks)) { + task.setChildren( + convertList(childTasks, childTask -> { + BpmTaskRespVO childTaskVO = BeanUtils.toBean(childTask, BpmTaskRespVO.class); + childTaskVO.setStatus(FlowableUtils.getTaskStatus(childTask)); + buildTaskOwner(childTaskVO, childTask.getOwner(), userMap, deptMap); + buildTaskAssignee(childTaskVO, childTask.getAssignee(), userMap, deptMap); + return childTaskVO; + }) + ); + } + } + + default void buildTaskAssignee(BpmTaskRespVO task, String taskAssignee, + Map<Long, AdminUserRespDTO> userMap, + Map<Long, DeptRespDTO> deptMap) { + AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee)); + if (assignUser != null) { + task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class)); + findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); + } + } + /** * 将父任务的属性,拷贝到子任务(加签任务) - * + * <p> * 为什么不使用 mapstruct 映射?因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的。 * 使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题。 * * @param parentTask 父任务 - * @param childTask 加签任务 + * @param childTask 加签任务 */ default void copyTo(TaskEntityImpl parentTask, TaskEntityImpl childTask) { childTask.setName(parentTask.getName()); @@ -165,7 +195,6 @@ childTask.setParentTaskId(parentTask.getId()); childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId()); childTask.setProcessInstanceId(parentTask.getProcessInstanceId()); -// childTask.setExecutionId(parentTask.getExecutionId()); // TODO iailab:新加的,不太确定;尴尬,不加时,子任务不通过会失败(报错);加了,子任务审批通过会失败(报错) childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey()); childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId()); childTask.setPriority(parentTask.getPriority()); diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmCategoryDO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmCategoryDO.java index e9b06bb..670a239 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmCategoryDO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmCategoryDO.java @@ -1,9 +1,9 @@ package com.iailab.module.bpm.dal.dataobject.definition; -import com.iailab.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.iailab.framework.mybatis.core.dataobject.BaseDO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmFormDO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmFormDO.java index 2e9be6d..8553101 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmFormDO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmFormDO.java @@ -1,10 +1,11 @@ package com.iailab.module.bpm.dal.dataobject.definition; -import com.iailab.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.iailab.framework.mybatis.core.dataobject.BaseDO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -19,6 +20,7 @@ * @author iailab */ @TableName(value = "bpm_form", autoResultMap = true) +@KeySequence("bpm_form_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @Builder @NoArgsConstructor @@ -45,8 +47,6 @@ /** * 表单项的数组 * - * 目前直接将 https://github.com/JakHuang/form-generator 生成的 JSON 串,直接保存 - * 定义:https://github.com/JakHuang/form-generator/issues/46 */ @TableField(typeHandler = JacksonTypeHandler.class) private List<String> fields; diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java index 8da16ae..4fb7626 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java @@ -1,25 +1,33 @@ package com.iailab.module.bpm.dal.dataobject.definition; -import com.iailab.framework.mybatis.core.dataobject.BaseDO; -import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.iailab.framework.mybatis.core.dataobject.BaseDO; +import com.iailab.framework.mybatis.core.type.LongListTypeHandler; +import com.iailab.framework.mybatis.core.type.StringListTypeHandler; +import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; +import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum; +import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.flowable.engine.repository.Model; +import org.flowable.engine.repository.ProcessDefinition; import java.util.List; /** * BPM 流程定义的拓信息 - * 主要解决 Flowable {@link org.flowable.engine.repository.ProcessDefinition} 不支持拓展字段,所以新建该表 + * 主要解决 Flowable {@link ProcessDefinition} 不支持拓展字段,所以新建该表 * * @author iailab */ @TableName(value = "bpm_process_definition_info", autoResultMap = true) +@KeySequence("bpm_process_definition_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @Builder @NoArgsConstructor @@ -34,15 +42,21 @@ /** * 流程定义的编号 * - * 关联 ProcessDefinition 的 id 属性 + * 关联 {@link ProcessDefinition#getId()} 属性 */ private String processDefinitionId; /** * 流程模型的编号 * - * 关联 Model 的 id 属性 + * 关联 {@link Model#getId()} 属性 */ private String modelId; + /** + * 流程模型的类型 + * + * 枚举 {@link BpmModelTypeEnum} + */ + private Integer modelType; /** * 图标 @@ -56,11 +70,12 @@ /** * 表单类型 * - * 关联 {@link BpmModelFormTypeEnum} + * 枚举 {@link BpmModelFormTypeEnum} */ private Integer formType; /** * 动态表单编号 + * * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 * * 关联 {@link BpmFormDO#getId()} @@ -68,6 +83,7 @@ private Long formId; /** * 表单的配置 + * * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 * * 冗余 {@link BpmFormDO#getConf()} @@ -75,21 +91,63 @@ private String formConf; /** * 表单项的数组 + * * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 * - * 冗余 {@link BpmFormDO#getFields()} ()} + * 冗余 {@link BpmFormDO#getFields()} */ @TableField(typeHandler = JacksonTypeHandler.class) private List<String> formFields; /** * 自定义表单的提交路径,使用 Vue 的路由地址 + * * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 */ private String formCustomCreatePath; /** * 自定义表单的查看路径,使用 Vue 的路由地址 + * * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 */ private String formCustomViewPath; + /** + * SIMPLE 设计器模型数据 json 格式 + * + * 目的:当使用仿钉钉设计器时。流程模型发布的时候,需要保存流程模型设计器的快照数据。 + */ + private String simpleModel; + /** + * 是否可见 + * + * 目的:如果 false 不可见,则不展示在“发起流程”的列表里 + */ + private Boolean visible; + /** + * 排序值 + */ + private Long sort; + + /** + * 可发起用户编号数组 + * + * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 + * + * 如果为空,则表示“全部可以发起”! + * + * 它和 {@link #visible} 的区别在于: + * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起 + * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的 + */ + @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 + private List<Long> startUserIds; + + /** + * 可管理用户编号数组 + * + * 关联 {@link AdminUserRespDTO#getId()} 字段的数组 + */ + @TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤 + private List<Long> managerUserIds; + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java index 4527241..60eae42 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java @@ -1,9 +1,10 @@ package com.iailab.module.bpm.dal.dataobject.definition; -import com.iailab.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.iailab.framework.mybatis.core.dataobject.BaseDO; +import com.sun.xml.bind.v2.TODO; import lombok.*; /** diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java index 3a25921..741634a 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java @@ -1,8 +1,9 @@ package com.iailab.module.bpm.dal.dataobject.definition; -import com.iailab.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.iailab.framework.mybatis.core.dataobject.BaseDO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -16,6 +17,7 @@ * @author iailab */ @TableName(value = "bpm_process_listener") +@KeySequence("bpm_process_listener_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @Builder @NoArgsConstructor @@ -42,8 +44,6 @@ * * 枚举 {@link com.iailab.module.bpm.enums.definition.BpmProcessListenerType} * - * 1. execution:ExecutionListener <a href="https://tkjohn.github.io/flowable-userguide/#executionListeners">执行监听器</a> - * 2. task:TaskListener <a href="https://tkjohn.github.io/flowable-userguide/#taskListeners">任务监听器</a> */ private String type; /** diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java index e45dea7..4c7e23f 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java @@ -1,11 +1,12 @@ package com.iailab.module.bpm.dal.dataobject.definition; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.iailab.framework.common.enums.CommonStatusEnum; -import com.iailab.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.iailab.framework.common.enums.CommonStatusEnum; +import com.iailab.framework.mybatis.core.dataobject.BaseDO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -19,6 +20,7 @@ * @author iailab */ @TableName(value = "bpm_user_group", autoResultMap = true) +@KeySequence("bpm_user_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @Builder @NoArgsConstructor diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java index a4ab251..2e983bb 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java @@ -7,6 +7,8 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.task.api.history.HistoricTaskInstance; /** * 流程抄送 DO @@ -51,19 +53,26 @@ * 冗余 ProcessInstance 的 category 字段 */ private String category; - /** - * 任务主键 + * 流程活动的编号 + * <p/> * - * 关联 Task 的 id 属性 + * 冗余 {@link FlowNode#getId()},对应 BPMN XML 节点编号 + * 原因:用于查询抄送节点的表单字段权限。因为仿钉钉/飞书的抄送节点 (ServiceTask),没有 taskId,只有 activityId + */ + private String activityId; + /** + * 流程活动的名字 + * + * 冗余 {@link FlowNode#getName()} + */ + private String activityName; + /** + * 流程活动的编号 + * + * 关联 {@link HistoricTaskInstance#getId()} */ private String taskId; - /** - * 任务名称 - * - * 冗余 Task 的 name 属性 - */ - private String taskName; /** * 用户编号(被抄送的用户编号) @@ -72,4 +81,9 @@ */ private Long userId; + /** + * 抄送意见 + */ + private String reason; + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmFormMapper.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmFormMapper.java index 24f5f06..521fee5 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmFormMapper.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmFormMapper.java @@ -11,7 +11,7 @@ /** * 动态表单 Mapper * - * @author 风里雾里 + * @author hou */ @Mapper public interface BpmFormMapper extends BaseMapperX<BpmFormDO> { diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java index 36e2dca..1e696ed 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java @@ -1,6 +1,7 @@ package com.iailab.module.bpm.dal.mysql.definition; import com.iailab.framework.mybatis.core.mapper.BaseMapperX; +import com.iailab.framework.mybatis.core.query.LambdaQueryWrapperX; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import org.apache.ibatis.annotations.Mapper; @@ -18,4 +19,9 @@ return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId); } + default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) { + update(updateObj, + new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId)); + } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java index ad6285a..58c55ca 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java @@ -6,6 +6,7 @@ import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import com.iailab.module.system.api.user.AdminUserApi; +import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate; import org.flowable.common.engine.api.delegate.event.FlowableEventListener; import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.spring.boot.EngineConfigurationConfigurer; @@ -56,12 +57,15 @@ @Bean public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer( ObjectProvider<FlowableEventListener> listeners, + ObjectProvider<FlowableFunctionDelegate> customFlowableFunctionDelegates, BpmActivityBehaviorFactory bpmActivityBehaviorFactory) { return configuration -> { // 注册监听器,例如说 BpmActivityEventListener configuration.setEventListeners(ListUtil.toList(listeners.iterator())); // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义 configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory); + // 设置自定义的函数 + configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator())); }; } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java index d176e42..3b36c24 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java @@ -1,5 +1,7 @@ package com.iailab.module.bpm.framework.flowable.core.behavior; +import cn.hutool.core.collection.CollUtil; +import com.iailab.framework.common.util.collection.SetUtils; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; @@ -48,9 +50,20 @@ super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); // 第二步,获取任务的所有处理人 - Set<Long> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution); - execution.setVariable(super.collectionVariable, assigneeUserIds); + @SuppressWarnings("unchecked") + Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); + if (assigneeUserIds == null) { + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); + execution.setVariable(super.collectionVariable, assigneeUserIds); + if (CollUtil.isEmpty(assigneeUserIds)) { + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 + assigneeUserIds = SetUtils.asSet((Long) null); + } + } return assigneeUserIds.size(); } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java index 289372e..e7ecc99 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmSequentialMultiInstanceBehavior.java @@ -1,5 +1,7 @@ package com.iailab.module.bpm.framework.flowable.core.behavior; +import cn.hutool.core.collection.CollUtil; +import com.iailab.framework.common.util.collection.SetUtils; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import lombok.Setter; @@ -42,9 +44,18 @@ super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); // 第二步,获取任务的所有处理人 - Set<Long> assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!! - execution.setVariable(super.collectionVariable, assigneeUserIds); + @SuppressWarnings("unchecked") + Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); + if (assigneeUserIds == null) { + assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); + execution.setVariable(super.collectionVariable, assigneeUserIds); + if (CollUtil.isEmpty(assigneeUserIds)) { + // 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过! + // 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务 + // 用途:1)审批人为空时;2)审批类型为自动通过、自动拒绝时 + assigneeUserIds = SetUtils.asSet((Long) null); + } + } return assigneeUserIds.size(); } - } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java index cf7b284..e60dae5 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/behavior/BpmUserTaskActivityBehavior.java @@ -14,6 +14,7 @@ import org.flowable.engine.impl.util.TaskHelper; import org.flowable.task.service.TaskService; import org.flowable.task.service.impl.persistence.entity.TaskEntity; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Set; @@ -36,14 +37,16 @@ } @Override + @Transactional(rollbackFor = Exception.class) protected void handleAssignments(TaskService taskService, String assignee, String owner, - List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, - DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { + List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, + DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) { // 第一步,获得任务的候选用户 Long assigneeUserId = calculateTaskCandidateUsers(execution); - Assert.notNull(assigneeUserId, "任务处理人不能为空"); // 第二步,设置作为负责人 - TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); + if (assigneeUserId != null) { + TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId)); + } } private Long calculateTaskCandidateUsers(DelegateExecution execution) { @@ -55,7 +58,10 @@ // 情况二,如果非多实例的任务,则计算任务处理人 // 第一步,先计算可处理该任务的处理人们 - Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsers(execution); + Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsersByTask(execution); + if (CollUtil.isEmpty(candidateUserIds)) { + return null; + } // 第二步,后随机选择一个任务的处理人 // 疑问:为什么一定要选择一个任务处理人? // 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。 diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java index d70f332..b1a364f 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java @@ -2,23 +2,28 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; import com.iailab.framework.common.enums.CommonStatusEnum; +import com.iailab.framework.common.util.object.ObjectUtils; import com.iailab.framework.datapermission.core.annotation.DataPermission; +import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; +import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.iailab.module.bpm.service.task.BpmProcessInstanceService; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.module.bpm.enums.ErrorCodeConstants.MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG; @@ -57,7 +62,14 @@ List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); // 遍历所有的 UserTask,校验审批人配置 userTaskList.forEach(userTask -> { - // 1. 非空校验 + // 1.1 非人工审批,无需校验审批人配置 + Integer approveType = BpmnModelUtils.parseApproveType(userTask); + if (ObjectUtils.equalsAny(approveType, + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + return; + } + // 1.2 非空校验 Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask); String param = BpmnModelUtils.parseCandidateParam(userTask); if (strategy == null) { @@ -79,20 +91,66 @@ * @return 用户编号集合 */ @DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人 - public Set<Long> calculateUsers(DelegateExecution execution) { - Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement()); - String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement()); + public Set<Long> calculateUsersByTask(DelegateExecution execution) { + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 + FlowElement flowElement = execution.getCurrentFlowElement(); + Integer approveType = BpmnModelUtils.parseApproveType(flowElement); + if (ObjectUtils.equalsAny(approveType, + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + return new HashSet<>(); + } + // 1.1 计算任务的候选人 - Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param); + Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); + String param = BpmnModelUtils.parseCandidateParam(flowElement); + Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param); // 1.2 移除被禁用的用户 removeDisableUsers(userIds); - // 2. 校验是否有候选人 + // 2. 候选人为空时,根据“审批人为空”的配置补充 if (CollUtil.isEmpty(userIds)) { - log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(), - execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param); - throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER); + userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) + .calculateUsersByTask(execution, param); + // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! } + + // 3. 移除发起人的用户 + ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class) + .getProcessInstance(execution.getProcessInstanceId()); + Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId()); + removeStartUserIfSkip(userIds, flowElement, Long.valueOf(processInstance.getStartUserId())); + return userIds; + } + + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { + // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); + Integer approveType = BpmnModelUtils.parseApproveType(flowElement); + if (ObjectUtils.equalsAny(approveType, + BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(), + BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + return new HashSet<>(); + } + + // 1.1 计算任务的候选人 + Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement); + String param = BpmnModelUtils.parseCandidateParam(flowElement); + Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByActivity(bpmnModel, activityId, param, + startUserId, processDefinitionId, processVariables); + // 1.2 移除被禁用的用户 + removeDisableUsers(userIds); + + // 2. 候选人为空时,根据“审批人为空”的配置补充 + if (CollUtil.isEmpty(userIds)) { + userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy()) + .calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables); + // ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!! + } + + // 3. 移除发起人的用户 + removeStartUserIfSkip(userIds, flowElement, startUserId); return userIds; } @@ -104,10 +162,31 @@ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds); assigneeUserIds.removeIf(id -> { AdminUserRespDTO user = userMap.get(id); - return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus()); + return user == null || CommonStatusEnum.isDisable(user.getStatus()); }); } + /** + * 如果“审批人与发起人相同时”,配置了 SKIP 跳过,则移除发起人 + * + * 注意:如果只有一个候选人,则不处理,避免无法审批 + * + * @param assigneeUserIds 当前分配的候选人 + * @param flowElement 当前节点 + * @param startUserId 发起人 + */ + @VisibleForTesting + void removeStartUserIfSkip(Set<Long> assigneeUserIds, FlowElement flowElement, Long startUserId) { + if (CollUtil.size(assigneeUserIds) <= 1) { + return; + } + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement); + if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { + return; + } + assigneeUserIds.remove(startUserId); + } + private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) { BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy); Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy); diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java index 73993a5..9fa6a69 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateStrategy.java @@ -1,16 +1,18 @@ package com.iailab.module.bpm.framework.flowable.core.candidate; import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.delegate.DelegateExecution; +import java.util.Map; import java.util.Set; /** * BPM 任务的候选人的策略接口 - * + * <p> * 例如说:分配审批人 * - * @author iailab + * @author hou */ public interface BpmTaskCandidateStrategy { @@ -29,14 +31,6 @@ void validateParam(String param); /** - * 基于执行任务,获得任务的候选用户们 - * - * @param execution 执行任务 - * @return 用户编号集合 - */ - Set<Long> calculateUsers(DelegateExecution execution, String param); - - /** * 是否一定要输入参数 * * @return 是否 @@ -45,4 +39,47 @@ return true; } + /** + * 基于候选人参数,获得任务的候选用户们 + * + * 注意:实现 calculateUsers 系列方法时,有两种选择: + * 1. 只重写 calculateUsers 默认方法 + * 2. 都重写 calculateUsersByTask 和 calculateUsersByActivity 两个方法 + * + * @param param 执行任务 + * @return 用户编号集合 + */ + default Set<Long> calculateUsers(String param) { + throw new UnsupportedOperationException("该分配方法未实现,请检查!"); + } + + /** + * 基于【执行任务】,获得任务的候选用户们 + * + * @param execution 执行任务 + * @return 用户编号集合 + */ + default Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { + return calculateUsers(param); + } + + /** + * 基于【流程活动】,获得任务的候选用户们 + * <p> + * 目的:用于获取未执行节点的候选用户们 + * + * @param bpmnModel 流程图 + * @param activityId 活动 ID (对应 Bpmn XML id) + * @param param 节点的参数 + * @param startUserId 流程发起人编号 + * @param processDefinitionId 流程定义编号 + * @param processVariables 流程变量 + * @return 用户编号集合 + */ + @SuppressWarnings("unused") + default Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { + return calculateUsers(param); + } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java deleted file mode 100644 index eab0881..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; -import com.iailab.module.bpm.service.task.BpmProcessInstanceService; -import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.UserTask; -import org.flowable.engine.delegate.DelegateExecution; -import org.flowable.engine.runtime.ProcessInstance; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.*; - -/** - * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类 - * - * @author iailab - */ -@Component -public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidateStrategy { - - @Resource - @Lazy // 延迟加载,避免循环依赖 - private BpmProcessInstanceService processInstanceService; - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.START_USER_SELECT; - } - - @Override - public void validateParam(String param) {} - - @Override - public Set<Long> calculateUsers(DelegateExecution execution, String param) { - ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); - Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); - Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); - Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", - execution.getProcessInstanceId()); - // 获得审批人 - List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); - return new LinkedHashSet<>(assignees); - } - - @Override - public boolean isParamRequired() { - return false; - } - - /** - * 获得发起人自选审批人的 UserTask 列表 - * - * @param bpmnModel BPMN 模型 - * @return UserTask 列表 - */ - public static List<UserTask> getStartUserSelectUserTaskList(BpmnModel bpmnModel) { - if (bpmnModel == null) { - return null; - } - List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); - if (CollUtil.isEmpty(userTaskList)) { - return null; - } - userTaskList.removeIf(userTask -> !Objects.equals(BpmnModelUtils.parseCandidateStrategy(userTask), - BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())); - return userTaskList; - } - -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java new file mode 100644 index 0000000..ce4a924 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/AbstractBpmTaskCandidateDeptLeaderStrategy.java @@ -0,0 +1,94 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.iailab.module.system.api.dept.DeptApi; +import com.iailab.module.system.api.dept.dto.DeptRespDTO; +import com.iailab.module.system.api.user.AdminUserApi; +import com.iailab.module.system.api.user.dto.AdminUserRespDTO; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * 部门的负责人 {@link BpmTaskCandidateStrategy} 抽象类 + * + * @author hou + */ +public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { + + @Resource + protected DeptApi deptApi; + @Resource + protected AdminUserApi adminUserApi; + + /** + * 获得指定层级的部门负责人,只有第 level 的负责人 + * + * @param dept 指定部门 + * @param level 第几级 + * @return 部门负责人的编号 + */ + protected Long getAssignLevelDeptLeaderId(DeptRespDTO dept, Integer level) { + Assert.isTrue(level > 0, "level 必须大于 0"); + if (dept == null) { + return null; + } + DeptRespDTO currentDept = dept; + for (int i = 1; i < level; i++) { + DeptRespDTO parentDept = deptApi.getDept(currentDept.getParentId()).getCheckedData(); + if (parentDept == null) { // 找不到父级部门,到了最高级。返回最高级的部门负责人 + break; + } + currentDept = parentDept; + } + return currentDept.getLeaderUserId(); + } + + /** + * 获得连续层级的部门负责人,包含 [1, level] 的负责人 + * + * @param deptIds 指定部门编号数组 + * @param level 最大层级 + * @return 连续部门负责人 Id + */ + protected Set<Long> getMultiLevelDeptLeaderIds(List<Long> deptIds, Integer level) { + Assert.isTrue(level > 0, "level 必须大于 0"); + if (CollUtil.isEmpty(deptIds)) { + return new HashSet<>(); + } + Set<Long> deptLeaderIds = new LinkedHashSet<>(); // 保证有序 + for (Long deptId : deptIds) { + DeptRespDTO dept = deptApi.getDept(deptId).getCheckedData(); + for (int i = 0; i < level; i++) { + if (dept.getLeaderUserId() != null) { + deptLeaderIds.add(dept.getLeaderUserId()); + } + DeptRespDTO parentDept = deptApi.getDept(dept.getParentId()).getCheckedData(); + if (parentDept == null) { // 找不到父级部门. 已经到了最高层级了 + break; + } + dept = parentDept; + } + } + return deptLeaderIds; + } + + /** + * 获取发起人的部门 + * + * @param startUserId 发起人 Id + */ + protected DeptRespDTO getStartUserDept(Long startUserId) { + AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData(); + if (startUser.getDeptId() == null) { // 找不到部门 + return null; + } + return deptApi.getDept(startUser.getDeptId()).getCheckedData(); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java new file mode 100644 index 0000000..259d80c --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderMultiStrategy.java @@ -0,0 +1,45 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.lang.Assert; +import com.iailab.framework.common.util.string.StrUtils; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; + +/** + * 连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author hou + */ +@Component +public class BpmTaskCandidateDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.MULTI_DEPT_LEADER_MULTI; + } + + @Override + public void validateParam(String param) { + // 参数格式: | 分隔:1)左边为部门(多个部门用 , 分隔)。2)右边为部门层级 + String[] params = param.split("\\|"); + Assert.isTrue(params.length == 2, "参数格式不匹配"); + List<Long> deptIds = StrUtils.splitToLong(params[0], ","); + int level = Integer.parseInt(params[1]); + // 校验部门存在 + deptApi.validateDeptList(deptIds).checkError(); + Assert.isTrue(level > 0, "部门层级必须大于 0"); + } + + @Override + public Set<Long> calculateUsers(String param) { + String[] params = param.split("\\|"); + List<Long> deptIds = StrUtils.splitToLong(params[0], ","); + int level = Integer.parseInt(params[1]); + return super.getMultiLevelDeptLeaderIds(deptIds, level); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java similarity index 86% rename from iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java rename to iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java index 2f8961a..01060b4 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptLeaderStrategy.java @@ -1,11 +1,10 @@ -package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; import com.iailab.framework.common.util.string.StrUtils; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.iailab.module.system.api.dept.DeptApi; import com.iailab.module.system.api.dept.dto.DeptRespDTO; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -17,7 +16,7 @@ /** * 部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 * - * @author kyle + * @author hou */ @Component public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { @@ -33,11 +32,11 @@ @Override public void validateParam(String param) { Set<Long> deptIds = StrUtils.splitToLongSet(param); - deptApi.validateDeptList(deptIds); + deptApi.validateDeptList(deptIds).checkError(); } @Override - public Set<Long> calculateUsers(DelegateExecution execution, String param) { + public Set<Long> calculateUsers(String param) { Set<Long> deptIds = StrUtils.splitToLongSet(param); List<DeptRespDTO> depts = deptApi.getDeptList(deptIds).getCheckedData(); return convertSet(depts, DeptRespDTO::getLeaderUserId); diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java similarity index 87% rename from iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java rename to iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java index d6dcf52..ce05a2d 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateDeptMemberStrategy.java @@ -1,4 +1,4 @@ -package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; import com.iailab.framework.common.util.string.StrUtils; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; @@ -6,7 +6,6 @@ import com.iailab.module.system.api.dept.DeptApi; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -15,10 +14,11 @@ import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; + /** * 部门的成员 {@link BpmTaskCandidateStrategy} 实现类 * - * @author kyle + * @author hou */ @Component public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy { @@ -36,11 +36,11 @@ @Override public void validateParam(String param) { Set<Long> deptIds = StrUtils.splitToLongSet(param); - deptApi.validateDeptList(deptIds); + deptApi.validateDeptList(deptIds).checkError(); } @Override - public Set<Long> calculateUsers(DelegateExecution execution, String param) { + public Set<Long> calculateUsers(String param) { Set<Long> deptIds = StrUtils.splitToLongSet(param); List<AdminUserRespDTO> users = adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(); return convertSet(users, AdminUserRespDTO::getId); diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java new file mode 100644 index 0000000..a041dbb --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java @@ -0,0 +1,70 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.lang.Assert; +import com.iailab.framework.common.util.number.NumberUtils; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.iailab.module.bpm.service.task.BpmProcessInstanceService; +import com.iailab.module.system.api.dept.dto.DeptRespDTO; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static cn.hutool.core.collection.ListUtil.toList; + +/** + * 发起人连续多级部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author hou + */ +@Component +public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { + + @Resource + @Lazy + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI; + } + + @Override + public void validateParam(String param) { + int level = Integer.parseInt(param); // 参数是部门的层级 + Assert.isTrue(level > 0, "部门的层级必须大于 0"); + } + + @Override + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { + int level = Integer.parseInt(param); // 参数是部门的层级 + // 获得流程发起人 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); + // 获取发起人的 multi 部门负责人 + DeptRespDTO dept = super.getStartUserDept(startUserId); + if (dept == null) { + return new HashSet<>(); + } + return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level); + } + + @Override + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { + int level = Integer.parseInt(param); // 参数是部门的层级 + DeptRespDTO dept = super.getStartUserDept(startUserId); + if (dept == null) { + return new HashSet<>(); + } + return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java new file mode 100644 index 0000000..c1f708f --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java @@ -0,0 +1,71 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.lang.Assert; +import com.iailab.framework.common.util.number.NumberUtils; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.iailab.module.bpm.service.task.BpmProcessInstanceService; +import com.iailab.module.system.api.dept.dto.DeptRespDTO; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.iailab.framework.common.util.collection.SetUtils.asSet; + +/** + * 发起人的部门负责人, 可以是上级部门负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author hou + */ +@Component +public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { + + @Resource + @Lazy // 避免循环依赖 + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER; + } + + @Override + public void validateParam(String param) { + // 参数是部门的层级 + Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0"); + } + + @Override + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { + // 获得流程发起人 + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId()); + // 获取发起人的部门负责人 + return getStartUserDeptLeader(startUserId, param); + } + + @Override + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { + // 获取发起人的部门负责人 + return getStartUserDeptLeader(startUserId, param); + } + + private Set<Long> getStartUserDeptLeader(Long startUserId, String param) { + int level = Integer.parseInt(param); // 参数是部门的层级 + DeptRespDTO dept = super.getStartUserDept(startUserId); + if (dept == null) { + return new HashSet<>(); + } + Long deptLeaderId = super.getAssignLevelDeptLeaderId(dept, level); + return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>(); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java new file mode 100644 index 0000000..cb117f1 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java @@ -0,0 +1,97 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import com.google.common.collect.Sets; +import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.iailab.module.bpm.service.task.BpmProcessInstanceService; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.ServiceTask; +import org.flowable.bpmn.model.Task; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.*; + +/** + * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类 + * + * @author hou + */ +@Component +public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.START_USER_SELECT; + } + + @Override + public void validateParam(String param) {} + + @Override + public boolean isParamRequired() { + return false; + } + + @Override + public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); + Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); + Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", + execution.getProcessInstanceId()); + // 获得审批人 + List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); + return new LinkedHashSet<>(assignees); + } + + @Override + public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { + if (processVariables == null) { + return Sets.newLinkedHashSet(); + } + Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables); + if (startUserSelectAssignees == null) { + return Sets.newLinkedHashSet(); + } + // 获得审批人 + List<Long> assignees = startUserSelectAssignees.get(activityId); + return new LinkedHashSet<>(assignees); + } + + /** + * 获得发起人自选审批人或抄送人的 Task 列表 + * + * @param bpmnModel BPMN 模型 + * @return Task 列表 + */ + public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) { + if (bpmnModel == null) { + return Collections.emptyList(); + } + List<Task> tasks = new ArrayList<>(); + tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class)); + tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class)); + if (CollUtil.isEmpty(tasks)) { + return Collections.emptyList(); + } + tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task), + BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())); + return tasks; + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormSDeptLeaderStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormSDeptLeaderStrategy.java new file mode 100644 index 0000000..ef2aa94 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormSDeptLeaderStrategy.java @@ -0,0 +1,56 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.form; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Assert; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.AbstractBpmTaskCandidateDeptLeaderStrategy; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; + +/** + * 表单内部门负责人 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author hou + */ +@Component +public class BpmTaskCandidateFormSDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy { + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.FORM_DEPT_LEADER; + } + + @Override + public void validateParam(String param) { + // 参数格式: | 分隔:1)左边为表单内部门字段。2)右边为部门层级 + String[] params = param.split("\\|"); + Assert.isTrue(params.length == 2, "参数格式不匹配"); + Assert.notEmpty(param, "表单内部门字段不能为空"); + int level = Integer.parseInt(params[1]); + Assert.isTrue(level > 0, "部门层级必须大于 0"); + } + + @Override + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { + String[] params = param.split("\\|"); + Object result = execution.getVariable(params[0]); + int level = Integer.parseInt(params[1]); + return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level); + } + + @Override + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, + String param, Long startUserId, String processDefinitionId, + Map<String, Object> processVariables) { + String[] params = param.split("\\|"); + Object result = processVariables == null ? null : processVariables.get(params[0]); + int level = Integer.parseInt(params[1]); + return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java new file mode 100644 index 0000000..ab8afe1 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java @@ -0,0 +1,47 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.form; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Assert; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.Set; + +/** + * 表单内用户字段 {@link BpmTaskCandidateUserStrategy} 实现类 + * + * @author hou + */ +@Component +public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrategy { + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.FORM_USER; + } + + @Override + public void validateParam(String param) { + Assert.notEmpty(param, "表单内用户字段不能为空"); + } + + @Override + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { + Object result = execution.getVariable(param); + return Convert.toSet(Long.class, result); + } + + @Override + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, + String param, Long startUserId, String processDefinitionId, + Map<String, Object> processVariables) { + Object result = processVariables == null ? null : processVariables.get(param); + return Convert.toSet(Long.class, result); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java new file mode 100644 index 0000000..e32a9f5 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java @@ -0,0 +1,73 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.other; + +import cn.hutool.core.lang.Assert; +import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; +import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * 审批人为空 {@link BpmTaskCandidateStrategy} 实现类 + * + * @author hou + */ +@Component +public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessDefinitionService processDefinitionService; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY; + } + + @Override + public void validateParam(String param) { + } + + @Override + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { + return getCandidateUsers(execution.getProcessDefinitionId(), execution.getCurrentFlowElement()); + } + + @Override + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { + FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); + return getCandidateUsers(processDefinitionId, flowElement); + } + + private Set<Long> getCandidateUsers(String processDefinitionId, FlowElement flowElement) { + // 情况一:指定人员审批 + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(flowElement); + if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) { + return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(flowElement)); + } + + // 情况二:流程管理员 + if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) { + BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processDefinitionId); + Assert.notNull(processDefinition, "流程定义({})不存在", processDefinitionId); + return new HashSet<>(processDefinition.getManagerUserIds()); + } + + // 都不满足,还是返回空 + return new HashSet<>(); + } + +} \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java similarity index 66% rename from iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java rename to iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java index b5be811..50d10e3 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java @@ -1,12 +1,14 @@ -package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.other; import cn.hutool.core.convert.Convert; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; +import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; +import java.util.Map; import java.util.Set; /** @@ -28,9 +30,16 @@ } @Override - public Set<Long> calculateUsers(DelegateExecution execution, String param) { + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { Object result = FlowableUtils.getExpressionValue(execution, param); return Convert.toSet(Long.class, result); } + @Override + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { + Object result = FlowableUtils.getExpressionValue(processVariables, param); + return Convert.toSet(Long.class, result); + } + } \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java similarity index 86% rename from iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java rename to iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java index 146e578..0e1ef21 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateGroupStrategy.java @@ -1,11 +1,10 @@ -package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; import com.iailab.framework.common.util.string.StrUtils; import com.iailab.module.bpm.dal.dataobject.definition.BpmUserGroupDO; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.iailab.module.bpm.service.definition.BpmUserGroupService; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -15,10 +14,11 @@ import static com.iailab.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + /** * 用户组 {@link BpmTaskCandidateStrategy} 实现类 * - * @author kyle + * @author hou */ @Component public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { @@ -34,11 +34,11 @@ @Override public void validateParam(String param) { Set<Long> groupIds = StrUtils.splitToLongSet(param); - userGroupService.getUserGroupList(groupIds); + userGroupService.validUserGroups(groupIds); } @Override - public Set<Long> calculateUsers(DelegateExecution execution, String param) { + public Set<Long> calculateUsers(String param) { Set<Long> groupIds = StrUtils.splitToLongSet(param); List<BpmUserGroupDO> groups = userGroupService.getUserGroupList(groupIds); return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream); diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java similarity index 89% rename from iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java rename to iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java index 6d244f5..ff12810 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidatePostStrategy.java @@ -1,4 +1,4 @@ -package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; import com.iailab.framework.common.util.string.StrUtils; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; @@ -6,7 +6,6 @@ import com.iailab.module.system.api.dept.PostApi; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -15,10 +14,11 @@ import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; + /** * 岗位 {@link BpmTaskCandidateStrategy} 实现类 * - * @author kyle + * @author hou */ @Component public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { @@ -40,7 +40,7 @@ } @Override - public Set<Long> calculateUsers(DelegateExecution execution, String param) { + public Set<Long> calculateUsers(String param) { Set<Long> postIds = StrUtils.splitToLongSet(param); List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(postIds).getCheckedData(); return convertSet(users, AdminUserRespDTO::getId); diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java similarity index 88% rename from iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java rename to iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java index 8290ca8..53aa384 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java @@ -1,11 +1,10 @@ -package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; import com.iailab.framework.common.util.string.StrUtils; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.iailab.module.system.api.permission.PermissionApi; import com.iailab.module.system.api.permission.RoleApi; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -14,7 +13,7 @@ /** * 角色 {@link BpmTaskCandidateStrategy} 实现类 * - * @author kyle + * @author hou */ @Component public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { @@ -36,7 +35,7 @@ } @Override - public Set<Long> calculateUsers(DelegateExecution execution, String param) { + public Set<Long> calculateUsers(String param) { Set<Long> roleIds = StrUtils.splitToLongSet(param); return permissionApi.getUserRoleIdListByRoleIds(roleIds).getCheckedData(); } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java new file mode 100644 index 0000000..f1cad5b --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java @@ -0,0 +1,57 @@ +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; + +import com.iailab.framework.common.util.collection.SetUtils; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.iailab.module.bpm.service.task.BpmProcessInstanceService; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.runtime.ProcessInstance; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Map; +import java.util.Set; + +/** + * 发起人自己 {@link BpmTaskCandidateUserStrategy} 实现类 + * <p> + * 适合场景:用于需要发起人信息复核等场景 + * + * @author hou + */ +@Component +public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessInstanceService processInstanceService; + + @Override + public BpmTaskCandidateStrategyEnum getStrategy() { + return BpmTaskCandidateStrategyEnum.START_USER; + } + + @Override + public void validateParam(String param) { + } + + @Override + public boolean isParamRequired() { + return false; + } + + @Override + public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); + return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId())); + } + + @Override + public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param, + Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { + return SetUtils.asSet(startUserId); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java similarity index 78% rename from iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java rename to iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java index 51836b3..e2b7521 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateUserStrategy.java @@ -1,19 +1,19 @@ -package com.iailab.module.bpm.framework.flowable.core.candidate.strategy; +package com.iailab.module.bpm.framework.flowable.core.candidate.strategy.user; +import cn.hutool.core.text.StrPool; import com.iailab.framework.common.util.string.StrUtils; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; import com.iailab.module.system.api.user.AdminUserApi; -import org.flowable.engine.delegate.DelegateExecution; import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.util.Set; +import java.util.LinkedHashSet; /** * 用户 {@link BpmTaskCandidateStrategy} 实现类 * - * @author kyle + * @author hou */ @Component public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { @@ -28,12 +28,12 @@ @Override public void validateParam(String param) { - adminUserApi.validateUserList(StrUtils.splitToLongSet(param)); + adminUserApi.validateUserList(StrUtils.splitToLongSet(param)).checkError(); } @Override - public Set<Long> calculateUsers(DelegateExecution execution, String param) { - return StrUtils.splitToLongSet(param); + public LinkedHashSet<Long> calculateUsers(String param) { + return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA)); } } \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmConstants.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmConstants.java deleted file mode 100644 index 01cc173..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmConstants.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.iailab.module.bpm.framework.flowable.core.enums; - -import org.flowable.engine.runtime.ProcessInstance; - -/** - * BPM 通用常量 - * - * @author iailab - */ -public class BpmConstants { - - /** - * 流程实例的变量 - 状态 - * - * @see ProcessInstance#getProcessVariables() - */ - public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS"; - /** - * 流程实例的变量 - 发起用户选择的审批人 Map - * - * @see ProcessInstance#getProcessVariables() - */ - public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; - - /** - * 任务的变量 - 状态 - * - * @see org.flowable.task.api.Task#getTaskLocalVariables() - */ - public static final String TASK_VARIABLE_STATUS = "TASK_STATUS"; - /** - * 任务的变量 - 理由 - * - * 例如说:审批通过、不通过的理由 - * - * @see org.flowable.task.api.Task#getTaskLocalVariables() - */ - public static final String TASK_VARIABLE_REASON = "TASK_REASON"; - -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java index b942855..53d913e 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmTaskCandidateStrategyEnum.java @@ -1,8 +1,11 @@ package com.iailab.module.bpm.framework.flowable.core.enums; import cn.hutool.core.util.ArrayUtil; +import com.iailab.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; import lombok.Getter; + +import java.util.Arrays; /** * BPM 任务的候选人策略枚举 @@ -13,17 +16,26 @@ */ @Getter @AllArgsConstructor -public enum BpmTaskCandidateStrategyEnum { +public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable { ROLE(10, "角色"), DEPT_MEMBER(20, "部门的成员"), // 包括负责人 DEPT_LEADER(21, "部门的负责人"), + MULTI_DEPT_LEADER_MULTI(23, "连续多级部门的负责人"), POST(22, "岗位"), USER(30, "用户"), START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人 + START_USER(36, "发起人自己"), // 申请人自己, 一般紧挨开始节点,常用于发起人信息审核场景 + START_USER_DEPT_LEADER(37, "发起人部门负责人"), + START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"), USER_GROUP(40, "用户组"), + FORM_USER(50, "表单内用户字段"), + FORM_DEPT_LEADER(51, "表单内部门负责人"), EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager + ASSIGN_EMPTY(1, "审批人为空"), ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmTaskCandidateStrategyEnum::getStrategy).toArray(); /** * 类型 @@ -38,4 +50,9 @@ return ArrayUtil.firstMatch(o -> o.getStrategy().equals(strategy), values()); } + @Override + public int[] array() { + return ARRAYS; + } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 258b780..903c201 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -23,4 +23,91 @@ */ String USER_TASK_CANDIDATE_PARAM = "candidateParam"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记边界事件类型 + */ + String BOUNDARY_EVENT_TYPE = "boundaryEventType"; + + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务超时执行动作 + */ + String USER_TASK_TIMEOUT_HANDLER_TYPE = "timeoutHandlerType"; + + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的审批人与发起人相同时,对应的处理类型 + */ + String USER_TASK_ASSIGN_START_USER_HANDLER_TYPE = "assignStartUserHandlerType"; + + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理类型 + */ + String USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE = "assignEmptyHandlerType"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务的空处理的指定用户编号数组 + */ + String USER_TASK_ASSIGN_USER_IDS = "assignEmptyUserIds"; + + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝处理类型 + */ + String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType"; + /** + * BPMN ExtensionElement 的扩展属性,用于标记用户任务拒绝后的退回的任务 Id + */ + String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId"; + + /** + * BPMN UserTask 的扩展属性,用于标记用户任务的审批类型 + */ + String USER_TASK_APPROVE_TYPE = "approveType"; + + /** + * BPMN UserTask 的扩展属性,用于标记用户任务的审批方式 + */ + String USER_TASK_APPROVE_METHOD = "approveMethod"; + + /** + * BPMN ExtensionElement 流程表单字段权限元素, 用于标记字段权限 + */ + String FORM_FIELD_PERMISSION_ELEMENT = "fieldsPermission"; + + /** + * BPMN ExtensionElement Attribute, 用于标记表单字段 + */ + String FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE = "field"; + /** + * BPMN ExtensionElement Attribute, 用于标记表单权限 + */ + String FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE = "permission"; + + /** + * BPMN ExtensionElement 操作按钮设置元素, 用于审批节点操作按钮设置 + */ + String BUTTON_SETTING_ELEMENT = "buttonsSetting"; + + /** + * BPMN ExtensionElement Attribute, 用于标记按钮编号 + */ + String BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE = "id"; + + /** + * BPMN ExtensionElement Attribute, 用于标记按钮显示名称 + */ + String BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE = "displayName"; + + /** + * BPMN ExtensionElement Attribute, 用于标记按钮是否启用 + */ + String BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE = "enable"; + + /** + * BPMN Start Event Node Id + */ + String START_EVENT_NODE_ID = "StartEvent"; + + /** + * 发起人节点 ID + */ + String START_USER_NODE_ID = "StartUserNode"; + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java new file mode 100644 index 0000000..35febbe --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java @@ -0,0 +1,62 @@ +package com.iailab.module.bpm.framework.flowable.core.enums; + +import org.flowable.engine.runtime.ProcessInstance; + +/** + * BPM Variable 通用常量 + * + * @author iailab + */ +public class BpmnVariableConstants { + + /** + * 流程实例的变量 - 状态 + * + * @see ProcessInstance#getProcessVariables() + */ + public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS"; + /** + * 流程实例的变量 - 理由 + * + * 例如说:审批不通过的理由(目前审核通过暂时不会记录) + * + * @see ProcessInstance#getProcessVariables() + */ + public static final String PROCESS_INSTANCE_VARIABLE_REASON = "PROCESS_REASON"; + /** + * 流程实例的变量 - 发起用户选择的审批人 Map + * + * @see ProcessInstance#getProcessVariables() + */ + public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; + /** + * 流程实例的变量 - 发起用户 ID + * + * @see ProcessInstance#getProcessVariables() + */ + public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID"; + /** + * 流程实例的变量 - 用于判断流程实例变量节点是否驳回. 格式 RETURN_FLAG_{节点 id} + * + * 目的是:驳回到发起节点时,因为审批人与发起人相同,所以被自动通过。但是,此时还是希望不要自动通过 + * + * @see ProcessInstance#getProcessVariables() + */ + public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; + + /** + * 任务的变量 - 状态 + * + * @see org.flowable.task.api.Task#getTaskLocalVariables() + */ + public static final String TASK_VARIABLE_STATUS = "TASK_STATUS"; + /** + * 任务的变量 - 理由 + * + * 例如说:审批通过、不通过的理由 + * + * @see org.flowable.task.api.Task#getTaskLocalVariables() + */ + public static final String TASK_VARIABLE_REASON = "TASK_REASON"; + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java new file mode 100644 index 0000000..d1eb914 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmCopyTaskDelegate.java @@ -0,0 +1,45 @@ +package com.iailab.module.bpm.framework.flowable.core.listener; + +import cn.hutool.core.collection.CollUtil; +import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import com.iailab.module.bpm.service.task.BpmProcessInstanceCopyService; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Set; + +/** + * 处理抄送用户的 {@link JavaDelegate} 的实现类 + * <p> + * 目前只有仿钉钉/飞书模式的【抄送节点】使用 + * + * @author hou + */ +@Component(BpmCopyTaskDelegate.BEAN_NAME) +public class BpmCopyTaskDelegate implements JavaDelegate { + + public static final String BEAN_NAME = "bpmCopyTaskDelegate"; + + @Resource + private BpmTaskCandidateInvoker taskCandidateInvoker; + + @Resource + private BpmProcessInstanceCopyService processInstanceCopyService; + + @Override + public void execute(DelegateExecution execution) { + // 1. 获得抄送人 + Set<Long> userIds = taskCandidateInvoker.calculateUsersByTask(execution); + if (CollUtil.isEmpty(userIds)) { + return; + } + // 2. 执行抄送 + FlowElement currentFlowElement = execution.getCurrentFlowElement(); + processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(), + currentFlowElement.getId(), currentFlowElement.getName(), null); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java index 635c55f..de25176 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java @@ -21,27 +21,20 @@ @Component public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener { - @Resource - @Lazy - private BpmProcessInstanceService processInstanceService; - public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() - .add(FlowableEngineEventType.PROCESS_CANCELLED) - .add(FlowableEngineEventType.PROCESS_COMPLETED) - .build(); + .add(FlowableEngineEventType.PROCESS_COMPLETED) + .build(); + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private BpmProcessInstanceService processInstanceService; public BpmProcessInstanceEventListener(){ super(PROCESS_INSTANCE_EVENTS); } @Override - protected void processCancelled(FlowableCancelledEvent event) { - processInstanceService.updateProcessInstanceWhenCancel(event); - } - - @Override protected void processCompleted(FlowableEngineEntityEvent event) { - processInstanceService.updateProcessInstanceWhenApprove((ProcessInstance)event.getEntity()); + processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity()); } - } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index c620e2a..d368f77 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -1,16 +1,25 @@ package com.iailab.module.bpm.framework.flowable.core.listener; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import com.iailab.module.bpm.service.task.BpmActivityService; +import com.iailab.framework.common.util.number.NumberUtils; +import com.iailab.module.bpm.enums.definition.BpmBoundaryEventType; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.iailab.module.bpm.service.definition.BpmModelService; import com.iailab.module.bpm.service.task.BpmTaskService; import com.google.common.collect.ImmutableSet; import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.BoundaryEvent; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.FlowElement; import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent; import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener; import org.flowable.engine.delegate.event.FlowableActivityCancelledEvent; import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.job.api.Job; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -29,36 +38,37 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @Resource - @Lazy // 解决循环依赖 - private BpmTaskService taskService; + @Lazy // 延迟加载,避免循环依赖 + private BpmModelService modelService; @Resource @Lazy // 解决循环依赖 - private BpmActivityService activityService; + private BpmTaskService taskService; public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() .add(FlowableEngineEventType.TASK_CREATED) .add(FlowableEngineEventType.TASK_ASSIGNED) // .add(FlowableEngineEventType.TASK_COMPLETED) // 由于审批通过时,已经记录了 task 的 status 为通过,所以不需要监听了。 .add(FlowableEngineEventType.ACTIVITY_CANCELLED) + .add(FlowableEngineEventType.TIMER_FIRED) // 监听审批超时 .build(); - public BpmTaskEventListener(){ + public BpmTaskEventListener() { super(TASK_EVENTS); } @Override protected void taskCreated(FlowableEngineEntityEvent event) { - taskService.updateTaskStatusWhenCreated((Task) event.getEntity()); + taskService.processTaskCreated((Task) event.getEntity()); } @Override protected void taskAssigned(FlowableEngineEntityEvent event) { - taskService.updateTaskExtAssign((Task)event.getEntity()); + taskService.processTaskAssigned((Task) event.getEntity()); } @Override protected void activityCancelled(FlowableActivityCancelledEvent event) { - List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId()); + List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId()); if (CollUtil.isEmpty(activityList)) { log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId()); return; @@ -68,8 +78,34 @@ if (StrUtil.isEmpty(activity.getTaskId())) { return; } - taskService.updateTaskStatusWhenCanceled(activity.getTaskId()); + taskService.processTaskCanceled(activity.getTaskId()); }); } + @Override + @SuppressWarnings("PatternVariableCanBeUsed") + protected void timerFired(FlowableEngineEntityEvent event) { + // 1.1 只处理 BoundaryEvent 边界计时时间 + String processDefinitionId = event.getProcessDefinitionId(); + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); + Job entity = (Job) event.getEntity(); + FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); + if (!(element instanceof BoundaryEvent)) { + return; + } + // 1.2 判断是否为超时处理 + BoundaryEvent boundaryEvent = (BoundaryEvent) element; + String boundaryEventType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, + BpmnModelConstants.BOUNDARY_EVENT_TYPE); + BpmBoundaryEventType bpmTimerBoundaryEventType = BpmBoundaryEventType.typeOf(NumberUtils.parseInt(boundaryEventType)); + if (ObjectUtil.notEqual(bpmTimerBoundaryEventType, BpmBoundaryEventType.USER_TASK_TIMEOUT)) { + return; + } + + // 2. 处理超时 + String timeoutHandlerType = BpmnModelUtils.parseBoundaryEventExtensionElement(boundaryEvent, + BpmnModelConstants.USER_TASK_TIMEOUT_HANDLER_TYPE); + String taskKey = boundaryEvent.getAttachedToRefId(); + taskService.processTaskTimeout(event.getProcessInstanceId(), taskKey, NumberUtils.parseInt(timeoutHandlerType)); + } } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index b0a4c83..2a79423 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -1,33 +1,353 @@ package com.iailab.module.bpm.framework.flowable.core.util; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Maps; +import com.iailab.framework.common.util.collection.CollectionUtils; import com.iailab.framework.common.util.number.NumberUtils; +import com.iailab.framework.common.util.string.StrUtils; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; +import com.iailab.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum; +import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum; +import com.iailab.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum; +import com.iailab.module.bpm.enums.definition.BpmUserTaskRejectHandlerType; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.impl.util.io.BytesStreamSource; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; + +import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE; +import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX; /** - * 流程模型转操作工具类 + * BPMN Model 操作工具类。目前分成三部分: + * + * 1. BPMN 修改 + 解析元素相关的方法 + * 2. BPMN 简单查找相关的方法 + * 3. BPMN 复杂遍历相关的方法 + * 4. BPMN 流程预测相关的方法 + * + * @author iailab */ +@Slf4j public class BpmnModelUtils { - public static Integer parseCandidateStrategy(FlowElement userTask) { - return NumberUtils.parseInt(userTask.getAttributeValue( - BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); + // ========== BPMN 修改 + 解析元素相关的方法 ========== + + public static void addExtensionElement(FlowElement element, String name, String value) { + if (value == null) { + return; + } + ExtensionElement extensionElement = new ExtensionElement(); + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); + extensionElement.setElementText(value); + extensionElement.setName(name); + element.addExtensionElement(extensionElement); } - public static String parseCandidateParam(FlowElement userTask) { - return userTask.getAttributeValue( - BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); + public static void addExtensionElement(FlowElement element, String name, Integer value) { + if (value == null) { + return; + } + addExtensionElement(element, name, String.valueOf(value)); } + + public static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) { + if (attributes == null) { + return; + } + ExtensionElement extensionElement = new ExtensionElement(); + extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); + extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); + extensionElement.setName(name); + attributes.forEach((key, value) -> { + ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value); + extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE); + extensionElement.addAttribute(extensionAttribute); + }); + element.addExtensionElement(extensionElement); + } + + /** + * 解析扩展元素 + * + * @param flowElement 节点 + * @param elementName 元素名称 + * @return 扩展元素 + */ + public static String parseExtensionElement(FlowElement flowElement, String elementName) { + if (flowElement == null) { + return null; + } + ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName)); + return element != null ? element.getElementText() : null; + } + + /** + * 给节点添加候选人元素 + * + * @param candidateStrategy 候选人策略 + * @param candidateParam 候选人参数,允许空 + * @param flowElement 节点 + */ + public static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) { + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, + candidateStrategy == null ? null : candidateStrategy.toString()); + addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam); + } + + /** + * 解析候选人策略 + * + * @param userTask 任务节点 + * @return 候选人策略 + */ + public static Integer parseCandidateStrategy(FlowElement userTask) { + Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue( + BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); + // TODO @芋艿 尝试从 ExtensionElement 取. 后续相关扩展是否都可以 存 extensionElement。 如表单权限。 按钮权限 + if (candidateStrategy == null) { + ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); + candidateStrategy = element != null ? NumberUtils.parseInt(element.getElementText()) : null; + } + return candidateStrategy; + } + + /** + * 解析候选人参数 + * + * @param userTask 任务节点 + * @return 候选人参数 + */ + public static String parseCandidateParam(FlowElement userTask) { + String candidateParam = userTask.getAttributeValue( + BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM); + if (candidateParam == null) { + ExtensionElement element = CollUtil.getFirst(userTask.getExtensionElements().get(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); + candidateParam = element != null ? element.getElementText() : null; + } + return candidateParam; + } + + /** + * 解析审批类型 + * + * @see BpmUserTaskApproveTypeEnum + * @param userTask 任务节点 + * @return 审批类型 + */ + public static Integer parseApproveType(FlowElement userTask) { + return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE)); + } + + /** + * 添加任务拒绝处理元素 + * + * @param rejectHandler 任务拒绝处理 + * @param userTask 任务节点 + */ + public static void addTaskRejectElements(BpmSimpleModelNodeVO.RejectHandler rejectHandler, UserTask userTask) { + if (rejectHandler == null) { + return; + } + addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType())); + addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId()); + } + + /** + * 解析任务拒绝处理类型 + * + * @param userTask 任务节点 + * @return 任务拒绝处理类型 + */ + public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) { + Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE)); + return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType); + } + + /** + * 解析任务拒绝返回任务节点 ID + * + * @param flowElement 任务节点 + * @return 任务拒绝返回任务节点 ID + */ + public static String parseReturnTaskId(FlowElement flowElement) { + return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID); + } + + /** + * 给节点添加用户任务的审批人与发起人相同时,处理类型枚举 + * + * @see BpmUserTaskAssignStartUserHandlerTypeEnum + * @param assignStartUserHandlerType 发起人处理类型 + * @param userTask 任务节点 + */ + public static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) { + if (assignStartUserHandlerType == null) { + return; + } + addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString()); + } + + /** + * 给节点添加用户任务的审批人为空时,处理类型枚举 + * + * @see BpmUserTaskAssignEmptyHandlerTypeEnum + * @param emptyHandler 空处理 + * @param userTask 任务节点 + */ + public static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) { + if (emptyHandler == null) { + return; + } + addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType())); + addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds())); + } + + /** + * 解析用户任务的审批人与发起人相同时,处理类型枚举 + * + * @param userTask 任务节点 + * @return 处理类型枚举 + */ + public static Integer parseAssignStartUserHandlerType(FlowElement userTask) { + return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE)); + } + + /** + * 解析用户任务的审批人为空时,处理类型枚举 + * + * @param userTask 任务节点 + * @return 处理类型枚举 + */ + public static Integer parseAssignEmptyHandlerType(FlowElement userTask) { + return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE)); + } + + /** + * 解析用户任务的审批人为空时,处理用户 ID 数组 + * + * @param userTask 任务节点 + * @return 处理用户 ID 数组 + */ + public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) { + return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ","); + } + + /** + * 给节点添加表单字段权限元素 + * + * @param fieldsPermissions 表单字段权限 + * @param flowElement 节点 + */ + public static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) { + if (CollUtil.isNotEmpty(fieldsPermissions)) { + fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item)); + } + } + + /** + * 解析表单字段权限 + * + * @param bpmnModel bpmnModel 对象 + * @param flowElementId 元素 ID + * @return 表单字段权限 + */ + public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) { + if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) { + return null; + } + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); + if (flowElement == null) { + return null; + } + List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(FORM_FIELD_PERMISSION_ELEMENT); + if (CollUtil.isEmpty(extensionElements)) { + return null; + } + Map<String, String> fieldsPermission = MapUtil.newHashMap(); + extensionElements.forEach(element -> { + String field = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_FIELD_ATTRIBUTE); + String permission = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, FORM_FIELD_PERMISSION_ELEMENT_PERMISSION_ATTRIBUTE); + if (StrUtil.isNotEmpty(field) && StrUtil.isNotEmpty(permission)) { + fieldsPermission.put(field, permission); + } + }); + return fieldsPermission; + } + + /** + * 给节点添加操作按钮设置元素 + */ + public static void addButtonsSetting(List<BpmSimpleModelNodeVO.OperationButtonSetting> buttonsSetting, UserTask userTask) { + if (CollUtil.isNotEmpty(buttonsSetting)) { + List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> { + Map<String, String> settingMap = Maps.newHashMapWithExpectedSize(3); + settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId())); + settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName()); + settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable())); + return settingMap; + }); + list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item)); + } + } + + /** + * 解析操作按钮设置 + * + * @param bpmnModel bpmnModel 对象 + * @param flowElementId 元素 ID + * @return 操作按钮设置 + */ + public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) { + FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId); + if (flowElement == null) { + return null; + } + List<ExtensionElement> extensionElements = flowElement.getExtensionElements().get(BUTTON_SETTING_ELEMENT); + if (CollUtil.isEmpty(extensionElements)) { + return null; + } + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size()); + extensionElements.forEach(element -> { + String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE); + String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE); + String enable = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE); + if (StrUtil.isNotEmpty(id)) { + BpmTaskRespVO.OperationButtonSetting setting = new BpmTaskRespVO.OperationButtonSetting(); + buttonSettings.put(Integer.valueOf(id), setting.setDisplayName(displayName).setEnable(Boolean.parseBoolean(enable))); + } + }); + return buttonSettings; + } + + /** + * 解析边界事件扩展元素 + * + * @param boundaryEvent 边界事件 + * @param customElement 元素 + * @return 扩展元素 + */ + public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) { + if (boundaryEvent == null) { + return null; + } + ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement)); + return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null); + } + + // ========== BPM 简单查找相关的方法 ========== /** * 根据节点,获取入口连线 @@ -74,15 +394,14 @@ * @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等 * @return 元素们 */ + @SuppressWarnings("unchecked") public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) { List<T> result = new ArrayList<>(); - model.getProcesses().forEach(process -> { - process.getFlowElements().forEach(flowElement -> { - if (flowElement.getClass().isAssignableFrom(clazz)) { - result.add((T) flowElement); - } - }); - }); + model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> { + if (flowElement.getClass().isAssignableFrom(clazz)) { + result.add((T) flowElement); + } + })); return result; } @@ -95,6 +414,12 @@ } // 从 flowElementList 找 return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent); + } + + public static EndEvent getEndEvent(BpmnModel model) { + Process process = model.getMainProcess(); + // 从 flowElementList 找 endEvent + return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent); } public static BpmnModel getBpmnModel(byte[] bpmnBytes) { @@ -111,10 +436,17 @@ return null; } BpmnXMLConverter converter = new BpmnXMLConverter(); - return new String(converter.convertToXML(model)); + return StrUtil.utf8Str(converter.convertToXML(model)); } - // ========== 遍历相关的方法 ========== + public static String getBpmnXml(byte[] bpmnBytes) { + if (ArrayUtil.isEmpty(bpmnBytes)) { + return null; + } + return StrUtil.utf8Str(bpmnBytes); + } + + // ========== BPMN 复杂遍历相关的方法 ========== /** * 找到 source 节点之前的所有用户任务节点 @@ -209,16 +541,16 @@ return userTaskList; } - /** * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 - * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 + * 不存在直接退回到子流程中的情况,但存在从子流程出去到父流程情况 * * @param source 起始节点 * @param target 目标节点 * @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 * @return 结果 */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) { visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; // 不能是开始事件和子流程 @@ -329,4 +661,136 @@ return userTaskList; } + // ========== BPMN 流程预测相关的方法 ========== + + /** + * 流程预测,返回 StartEvent、UserTask、ServiceTask、EndEvent 节点元素,最终是 List 串行结果 + * + * @param bpmnModel BPMN 图 + * @param variables 变量 + * @return 节点元素数组 + */ + public static List<FlowElement> simulateProcess(BpmnModel bpmnModel, Map<String, Object> variables) { + List<FlowElement> resultElements = new ArrayList<>(); + Set<FlowElement> visitElements = new HashSet<>(); + + // 从 StartEvent 开始遍历 + StartEvent startEvent = getStartEvent(bpmnModel); + simulateNextFlowElements(startEvent, variables, resultElements, visitElements); + + // 将 EndEvent 放在末尾。原因是,DFS 遍历,可能 EndEvent 在 resultElements 中 + List<FlowElement> endEvents = CollUtil.removeWithAddIf(resultElements, + flowElement -> flowElement instanceof EndEvent); + resultElements.addAll(endEvents); + return resultElements; + } + + @SuppressWarnings("PatternVariableCanBeUsed") + private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables, + List<FlowElement> resultElements, Set<FlowElement> visitElements) { + // 如果为空,或者已经遍历过,则直接结束 + if (currentElement == null) { + return; + } + if (visitElements.contains(currentElement)) { + return; + } + visitElements.add(currentElement); + + // 情况:StartEvent/EndEvent/UserTask/ServiceTask + if (currentElement instanceof StartEvent + || currentElement instanceof EndEvent + || currentElement instanceof UserTask + || currentElement instanceof ServiceTask) { + // 添加元素 + FlowNode flowNode = (FlowNode) currentElement; + resultElements.add(flowNode); + // 遍历子节点 + flowNode.getOutgoingFlows().forEach( + nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); + return; + } + + // 情况:ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的 + if (currentElement instanceof ExclusiveGateway) { + // 查找满足条件的 SequenceFlow 路径 + Gateway gateway = (Gateway) currentElement; + SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) + && evalConditionExpress(variables, flow.getConditionExpression())); + if (matchSequenceFlow == null) { + matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())); + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的 + if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) { + matchSequenceFlow = gateway.getOutgoingFlows().get(0); + } + } + // 遍历满足条件的 SequenceFlow 路径 + if (matchSequenceFlow != null) { + simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements); + } + return; + } + + // 情况:InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的 + if (currentElement instanceof InclusiveGateway) { + // 查找满足条件的 SequenceFlow 路径 + Gateway gateway = (Gateway) currentElement; + Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) + && evalConditionExpress(variables, flow.getConditionExpression())); + if (CollUtil.isEmpty(matchSequenceFlows)) { + matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(), + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())); + // 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的 + if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) { + matchSequenceFlows = gateway.getOutgoingFlows(); + } + } + // 遍历满足条件的 SequenceFlow 路径 + matchSequenceFlows.forEach( + flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements)); + } + + // 情况:ParallelGateway 并行,都满足,都走 + if (currentElement instanceof ParallelGateway) { + Gateway gateway = (Gateway) currentElement; + // 遍历子节点 + gateway.getOutgoingFlows().forEach( + nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); + return; + } + } + + /** + * 计算条件表达式是否为 true 满足条件 + * + * @param variables 流程实例 + * @param express 条件表达式 + * @return 是否满足条件 + */ + public static boolean evalConditionExpress(Map<String, Object> variables, String express) { + if (express == null) { + return Boolean.FALSE; + } + try { + Object result = FlowableUtils.getExpressionValue(variables, express); + return Boolean.TRUE.equals(result); + } catch (FlowableException ex) { + log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错", express, variables, ex); + return Boolean.FALSE; + } + } + + @SuppressWarnings("PatternVariableCanBeUsed") + public static boolean isSequentialUserTask(FlowElement flowElement) { + if (!(flowElement instanceof UserTask)) { + return false; + } + UserTask userTask = (UserTask) flowElement; + MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics(); + return loopCharacteristics != null && loopCharacteristics.isSequential(); + } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java index d865d35..3a73405 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/FlowableUtils.java @@ -1,11 +1,16 @@ package com.iailab.module.bpm.framework.flowable.core.util; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; import com.iailab.framework.tenant.core.context.TenantContextHolder; -import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants; +import com.iailab.framework.tenant.core.util.TenantUtils; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.api.variable.VariableContainer; import org.flowable.common.engine.impl.el.ExpressionManager; import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.common.engine.impl.variable.MapDelegateVariableContainer; +import org.flowable.engine.ManagementService; import org.flowable.engine.ProcessEngineConfiguration; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; @@ -16,11 +21,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Flowable 相关的工具方法 * - * @author iailab + * @author 芋道源码 */ public class FlowableUtils { @@ -37,6 +43,16 @@ public static String getTenantId() { Long tenantId = TenantContextHolder.getTenantId(); return tenantId != null ? String.valueOf(tenantId) : ProcessEngineConfiguration.NO_TENANT_ID; + } + + public static void execute(String tenantIdStr, Runnable runnable) { + if (ObjectUtil.isEmpty(tenantIdStr) + || Objects.equals(tenantIdStr, ProcessEngineConfiguration.NO_TENANT_ID)) { + runnable.run(); + } else { + Long tenantId = Long.valueOf(tenantIdStr); + TenantUtils.execute(tenantId, runnable); + } } // ========== Execution 相关的工具方法 ========== @@ -78,7 +94,28 @@ * @return 状态 */ private static Integer getProcessInstanceStatus(Map<String, Object> processVariables) { - return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); + return (Integer) processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); + } + + /** + * 获得流程实例的审批原因 + * + * @param processInstance 流程实例 + * @return 审批原因 + */ + public static String getProcessInstanceReason(HistoricProcessInstance processInstance) { + return (String) processInstance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); + } + + /** + * 获得流程实例的表单 + * + * @param processInstance 流程实例 + * @return 表单 + */ + public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) { + Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables()); + return filterProcessInstanceFormVariable(processVariables); } /** @@ -88,9 +125,8 @@ * @return 表单 */ public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) { - Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables()); - filterProcessInstanceFormVariable(formVariables); - return formVariables; + Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables()); + return filterProcessInstanceFormVariable(processVariables); } /** @@ -102,7 +138,7 @@ * @return 过滤后的表单 */ public static Map<String, Object> filterProcessInstanceFormVariable(Map<String, Object> processVariables) { - processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS); + processVariables.remove(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); return processVariables; } @@ -112,10 +148,23 @@ * @param processInstance 流程实例 * @return 发起用户选择的审批人 Map */ - @SuppressWarnings("unchecked") public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) { - return (Map<String, List<Long>>) processInstance.getProcessVariables().get( - BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); + return processInstance != null ? getStartUserSelectAssignees(processInstance.getProcessVariables()) : null; + } + + /** + * 获得流程实例的发起用户选择的审批人 Map + * + * @param processVariables 流程变量 + * @return 发起用户选择的审批人 Map + */ + @SuppressWarnings("unchecked") + public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) { + if (processVariables == null) { + return null; + } + return (Map<String, List<Long>>) processVariables.get( + BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES); } // ========== Task 相关的工具方法 ========== @@ -127,7 +176,7 @@ * @return 状态 */ public static Integer getTaskStatus(TaskInfo task) { - return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + return (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); } /** @@ -137,7 +186,7 @@ * @return 审批原因 */ public static String getTaskReason(TaskInfo task) { - return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON); + return (String) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_REASON); } /** @@ -161,20 +210,37 @@ * @return 过滤后的表单 */ public static Map<String, Object> filterTaskFormVariable(Map<String, Object> taskLocalVariables) { - taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS); - taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON); + taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_STATUS); + taskLocalVariables.remove(BpmnVariableConstants.TASK_VARIABLE_REASON); return taskLocalVariables; } // ========== Expression 相关的工具方法 ========== - public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) { - ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); - assert processEngineConfiguration != null; + private static Object getExpressionValue(VariableContainer variableContainer, String expressionString, + ProcessEngineConfigurationImpl processEngineConfiguration) { + assert processEngineConfiguration!= null; ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager(); - assert expressionManager != null; + assert expressionManager!= null; Expression expression = expressionManager.createExpression(expressionString); return expression.getValue(variableContainer); } + public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) { + ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); + if (processEngineConfiguration != null) { + return getExpressionValue(variableContainer, expressionString, processEngineConfiguration); + } + // 如果 ProcessEngineConfigurationImpl 获取不到,则需要通过 ManagementService 来获取 + ManagementService managementService = SpringUtil.getBean(ManagementService.class); + assert managementService != null; + return managementService.executeCommand(context -> + getExpressionValue(variableContainer, expressionString, CommandContextUtil.getProcessEngineConfiguration())); + } + + public static Object getExpressionValue(Map<String, Object> variable, String expressionString) { + VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty()); + return getExpressionValue(variableContainer, expressionString); + } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java new file mode 100644 index 0000000..8dfac59 --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/framework/flowable/core/util/SimpleModelUtils.java @@ -0,0 +1,686 @@ +package com.iailab.module.bpm.framework.flowable.core.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.*; +import com.iailab.framework.common.util.collection.CollectionUtils; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.iailab.module.bpm.enums.definition.*; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import com.iailab.module.bpm.framework.flowable.core.listener.BpmCopyTaskDelegate; +import org.flowable.bpmn.BpmnAutoLayout; +import org.flowable.bpmn.constants.BpmnXMLConstants; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.*; + +import java.util.*; + +import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; +import static com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; +import static java.util.Arrays.asList; + +/** + * 仿钉钉/飞书的模型相关的工具方法 + * <p> + * 1. 核心的逻辑实现,可见 {@link #buildBpmnModel(String, String, BpmSimpleModelNodeVO)} 方法 + * 2. 所有的 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素,可见 {@link NodeConvert} 实现类 + * + * @author jason + */ +public class SimpleModelUtils { + + private static final Map<BpmSimpleModelNodeType, NodeConvert> NODE_CONVERTS = MapUtil.newHashMap(); + + static { + List<NodeConvert> converts = asList(new StartNodeConvert(), new EndNodeConvert(), + new StartUserNodeConvert(), new ApproveNodeConvert(), new CopyNodeConvert(), + new ConditionBranchNodeConvert(), new ParallelBranchNodeConvert(), new InclusiveBranchNodeConvert()); + converts.forEach(convert -> NODE_CONVERTS.put(convert.getType(), convert)); + } + + /** + * 仿钉钉流程设计模型数据结构(json)转换成 Bpmn Model + * <p> + * 整体逻辑如下: + * 1. 创建:BpmnModel、Process 对象 + * 2. 转换:将 BpmSimpleModelNodeVO 转换成 BPMN FlowNode 元素 + * 3. 连接:构建并添加节点之间的连线 Sequence Flow + * + * @param processId 流程标识 + * @param processName 流程名称 + * @param simpleModelNode 仿钉钉流程设计模型数据结构 + * @return Bpmn Model + */ + public static BpmnModel buildBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { + // 1. 创建 BpmnModel + BpmnModel bpmnModel = new BpmnModel(); + bpmnModel.setTargetNamespace(BpmnXMLConstants.BPMN2_NAMESPACE); // 设置命名空间。不加这个,解析 Message 会报 NPE 异常 + // 创建 Process 对象 + Process process = new Process(); + process.setId(processId); + process.setName(processName); + process.setExecutable(Boolean.TRUE); + bpmnModel.addProcess(process); + + // 2.1 创建 StartNode 节点 + // 原因是:目前前端的第一个节点是“发起人节点”,所以这里构建一个 StartNode,用于创建 Bpmn 的 StartEvent 节点 + BpmSimpleModelNodeVO startNode = buildStartNode(); + startNode.setChildNode(simpleModelNode); + // 2.2 将前端传递的 simpleModelNode 数据结构(json),转换成从 BPMN FlowNode 元素,并添加到 Main Process 中 + traverseNodeToBuildFlowNode(startNode, process); + + // 3. 构建并添加节点之间的连线 Sequence Flow + EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); + traverseNodeToBuildSequenceFlow(process, startNode, endEvent.getId()); + + // 4. 自动布局 + new BpmnAutoLayout(bpmnModel).execute(); + return bpmnModel; + } + + private static BpmSimpleModelNodeVO buildStartNode() { + return new BpmSimpleModelNodeVO().setId(START_EVENT_NODE_ID) + .setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) + .setType(BpmSimpleModelNodeType.START_NODE.getType()); + } + + /** + * 遍历节点,构建 FlowNode 元素 + * + * @param node SIMPLE 节点 + * @param process BPMN 流程 + */ + private static void traverseNodeToBuildFlowNode(BpmSimpleModelNodeVO node, Process process) { + // 1. 判断是否有效节点 + if (!isValidNode(node)) { + return; + } + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); + Assert.notNull(nodeType, "模型节点类型({})不支持", node.getType()); + + // 2. 处理当前节点 + NodeConvert nodeConvert = NODE_CONVERTS.get(nodeType); + Assert.notNull(nodeConvert, "模型节点类型的转换器({})不存在", node.getType()); + List<? extends FlowElement> flowElements = nodeConvert.convertList(node); + flowElements.forEach(process::addFlowElement); + + // 3.1 情况一:如果当前是分支节点,并且存在条件节点,则处理每个条件的子节点 + if (BpmSimpleModelNodeType.isBranchNode(node.getType()) + && CollUtil.isNotEmpty(node.getConditionNodes())) { + // 注意:这里的 item.getChildNode() 处理的是每个条件的子节点,不是处理条件 + node.getConditionNodes().forEach(item -> traverseNodeToBuildFlowNode(item.getChildNode(), process)); + } + + // 3.2 情况二:如果有“子”节点,则递归处理子节点 + traverseNodeToBuildFlowNode(node.getChildNode(), process); + } + + /** + * 遍历节点,构建 SequenceFlow 元素 + * + * @param process Bpmn 流程 + * @param node 当前节点 + * @param targetNodeId 目标节点 ID + */ + private static void traverseNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { + // 1.1 无效节点返回 + if (!isValidNode(node)) { + return; + } + // 1.2 END_NODE 直接返回 + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + if (nodeType == BpmSimpleModelNodeType.END_NODE) { + return; + } + + // 2.1 情况一:普通节点 + if (!BpmSimpleModelNodeType.isBranchNode(node.getType())) { + traverseNormalNodeToBuildSequenceFlow(process, node, targetNodeId); + } else { + // 2.2 情况二:分支节点 + traverseBranchNodeToBuildSequenceFlow(process, node, targetNodeId); + } + } + + /** + * 遍历普通(非条件)节点,构建 SequenceFlow 元素 + * + * @param process Bpmn 流程 + * @param node 当前节点 + * @param targetNodeId 目标节点 ID + */ + private static void traverseNormalNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { + BpmSimpleModelNodeVO childNode = node.getChildNode(); + boolean isChildNodeValid = isValidNode(childNode); + // 情况一:有“子”节点,则建立连线 + // 情况二:没有“子节点”,则直接跟 targetNodeId 建立连线。例如说,结束节点、条件分支(分支节点的孩子节点或聚合节点)的最后一个节点 + String finalTargetNodeId = isChildNodeValid? childNode.getId() : targetNodeId; + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(node.getId(), finalTargetNodeId); + process.addFlowElement(sequenceFlow); + + // 因为有子节点,递归调用后续子节点 + if (isChildNodeValid) { + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); + } + } + + /** + * 遍历条件节点,构建 SequenceFlow 元素 + * + * @param process Bpmn 流程 + * @param node 当前节点 + * @param targetNodeId 目标节点 ID + */ + private static void traverseBranchNodeToBuildSequenceFlow(Process process, BpmSimpleModelNodeVO node, String targetNodeId) { + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); + BpmSimpleModelNodeVO childNode = node.getChildNode(); + List<BpmSimpleModelNodeVO> conditionNodes = node.getConditionNodes(); + Assert.notEmpty(conditionNodes, "分支节点的条件节点不能为空"); + // 分支终点节点 ID + String branchEndNodeId = null; + if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { // 条件分支 + // 分两种情况 1. 分支节点有孩子节点为孩子节点 Id 2. 分支节点孩子为无效节点时 (分支嵌套且为分支最后一个节点) 为分支终点节点 ID + branchEndNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; + } else if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE + || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) { // 并行分支或包容分支 + // 分支节点:分支终点节点 Id 为程序创建的网关集合节点。目前不会从前端传入。 + branchEndNodeId = buildGatewayJoinId(node.getId()); + } + Assert.notEmpty(branchEndNodeId, "分支终点节点 Id 不能为空"); + + // 3. 遍历分支节点 + // 下面的注释,以如下情况举例子。分支 1:A->B->C->D->E,分支 2:A->D->E。其中,A 为分支节点, D 为 A 孩子节点 + for (BpmSimpleModelNodeVO item : conditionNodes) { + Assert.isTrue(Objects.equals(item.getType(), BpmSimpleModelNodeType.CONDITION_NODE.getType()), + "条件节点类型({})不符合", item.getType()); + BpmSimpleModelNodeVO conditionChildNode = item.getChildNode(); + // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况 + if (isValidNode(conditionChildNode)) { + // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线 + SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item); + process.addFlowElement(sequenceFlow); + // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线 + traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId); + } else { + // 3.2 分支没有后续节点。例如说,建立 A->D 的连线 + SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item); + process.addFlowElement(sequenceFlow); + } + } + + // 4. 如果是并行分支、包容分支,由于是程序创建的聚合网关,需要手工创建聚合网关和下一个节点的连线 + if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE + || nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE ) { + String nextNodeId = isValidNode(childNode) ? childNode.getId() : targetNodeId; + SequenceFlow sequenceFlow = buildBpmnSequenceFlow(branchEndNodeId, nextNodeId); + process.addFlowElement(sequenceFlow); + } + + // 5. 递归调用后续节点 继续递归。例如说,建立 D->E 的连线 + traverseNodeToBuildSequenceFlow(process, childNode, targetNodeId); + } + + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId) { + return buildBpmnSequenceFlow(sourceId, targetId, null, null, null); + } + + private static SequenceFlow buildBpmnSequenceFlow(String sourceId, String targetId, + String sequenceFlowId, String sequenceFlowName, + String conditionExpression) { + Assert.notEmpty(sourceId, "sourceId 不能为空"); + Assert.notEmpty(targetId, "targetId 不能为空"); + // TODO @jason:如果 sequenceFlowId 不存在的时候,是不是要生成一个默认的 sequenceFlowId? @芋艿: 貌似不需要,Flowable 会默认生成;TODO @jason:建议还是搞一个,主要是后续好排查问题。 + // TODO @jason:如果 name 不存在的时候,是不是要生成一个默认的 name? @芋艿: 不需要生成默认的吧? 这个会在流程图展示的, 一般用户填写的。不好生成默认的吧;TODO @jason:建议还是搞一个,主要是后续好排查问题。 + SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); + if (StrUtil.isNotEmpty(sequenceFlowId)) { + sequenceFlow.setId(sequenceFlowId); + } + if (StrUtil.isNotEmpty(sequenceFlowName)) { + sequenceFlow.setName(sequenceFlowName); + } + if (StrUtil.isNotEmpty(conditionExpression)) { + sequenceFlow.setConditionExpression(conditionExpression); + } + return sequenceFlow; + } + + public static boolean isValidNode(BpmSimpleModelNodeVO node) { + return node != null && node.getId() != null; + } + + public static boolean isSequentialApproveNode(BpmSimpleModelNodeVO node) { + return BpmSimpleModelNodeType.APPROVE_NODE.getType().equals(node.getType()) + && BpmUserTaskApproveMethodEnum.SEQUENTIAL.getMethod().equals(node.getApproveMethod()); + } + + // ========== 各种 convert 节点的方法: BpmSimpleModelNodeVO => BPMN FlowElement ========== + + private interface NodeConvert { + + default List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) { + return Collections.singletonList(convert(node)); + } + + default FlowElement convert(BpmSimpleModelNodeVO node) { + throw new UnsupportedOperationException("请实现该方法"); + } + + BpmSimpleModelNodeType getType(); + + } + + private static class StartNodeConvert implements NodeConvert { + + @Override + public StartEvent convert(BpmSimpleModelNodeVO node) { + StartEvent startEvent = new StartEvent(); + startEvent.setId(node.getId()); + startEvent.setName(node.getName()); + return startEvent; + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.START_NODE; + } + + } + + private static class EndNodeConvert implements NodeConvert { + + @Override + public EndEvent convert(BpmSimpleModelNodeVO node) { + EndEvent endEvent = new EndEvent(); + endEvent.setId(node.getId()); + endEvent.setName(node.getName()); + // TODO @芋艿 + jason:要不要加一个终止定义? + return endEvent; + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.END_NODE; + } + + } + + private static class StartUserNodeConvert implements NodeConvert { + + @Override + public UserTask convert(BpmSimpleModelNodeVO node) { + UserTask userTask = new UserTask(); + userTask.setId(node.getId()); + userTask.setName(node.getName()); + + // 人工审批 + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, BpmUserTaskApproveTypeEnum.USER.getType()); + // 候选人策略为发起人自己 + addCandidateElements(BpmTaskCandidateStrategyEnum.START_USER.getStrategy(), null, userTask); + // 添加表单字段权限属性元素 + addFormFieldsPermission(node.getFieldsPermission(), userTask); + // 添加操作按钮配置属性元素 + addButtonsSetting(node.getButtonsSetting(), userTask); + // 使用自动通过策略 + // TODO @芋艿 复用了SKIP, 是否需要新加一个策略;TODO @芋艿:【回复】是不是应该类似飞书,搞个草稿状态。待定;还有一种策略,不标记自动通过,而是首次发起后,第一个节点,自动通过; + addAssignStartUserHandlerType(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType(), userTask); + return userTask; + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.START_USER_NODE; + } + + } + + private static class ApproveNodeConvert implements NodeConvert { + + @Override + public List<FlowElement> convertList(BpmSimpleModelNodeVO node) { + List<FlowElement> flowElements = new ArrayList<>(2); + // 1. 构建用户任务 + UserTask userTask = buildBpmnUserTask(node); + flowElements.add(userTask); + + // 2. 添加用户任务的 Timer Boundary Event, 用于任务的审批超时处理 + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { + BoundaryEvent boundaryEvent = buildUserTaskTimeoutBoundaryEvent(userTask, node.getTimeoutHandler()); + flowElements.add(boundaryEvent); + } + return flowElements; + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.APPROVE_NODE; + } + + /** + * 添加 UserTask 用户的审批超时 BoundaryEvent 事件 + * + * @param userTask 审批任务 + * @param timeoutHandler 超时处理器 + * @return BoundaryEvent 超时事件 + */ + private BoundaryEvent buildUserTaskTimeoutBoundaryEvent(UserTask userTask, + BpmSimpleModelNodeVO.TimeoutHandler timeoutHandler) { + // 1.1 定时器边界事件 + BoundaryEvent boundaryEvent = new BoundaryEvent(); + boundaryEvent.setId("Event-" + IdUtil.fastUUID()); + boundaryEvent.setCancelActivity(false); // 设置关联的任务为不会被中断 + boundaryEvent.setAttachedToRef(userTask); + // 1.2 定义超时时间、最大提醒次数 + TimerEventDefinition eventDefinition = new TimerEventDefinition(); + eventDefinition.setTimeDuration(timeoutHandler.getTimeDuration()); + if (Objects.equals(BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType(), timeoutHandler.getType()) && + timeoutHandler.getMaxRemindCount() != null && timeoutHandler.getMaxRemindCount() > 1) { + eventDefinition.setTimeCycle(String.format("R%d/%s", + timeoutHandler.getMaxRemindCount(), timeoutHandler.getTimeDuration())); + } + boundaryEvent.addEventDefinition(eventDefinition); + + // 2.1 添加定时器边界事件类型 + addExtensionElement(boundaryEvent, BOUNDARY_EVENT_TYPE, BpmBoundaryEventType.USER_TASK_TIMEOUT.getType()); + // 2.2 添加超时执行动作元素 + addExtensionElement(boundaryEvent, USER_TASK_TIMEOUT_HANDLER_TYPE, timeoutHandler.getType()); + return boundaryEvent; + } + + private UserTask buildBpmnUserTask(BpmSimpleModelNodeVO node) { + UserTask userTask = new UserTask(); + userTask.setId(node.getId()); + userTask.setName(node.getName()); + + // 如果不是审批人节点,则直接返回 + addExtensionElement(userTask, USER_TASK_APPROVE_TYPE, node.getApproveType()); + if (ObjectUtil.notEqual(node.getApproveType(), BpmUserTaskApproveTypeEnum.USER.getType())) { + return userTask; + } + + // 添加候选人元素 + addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), userTask); + // 添加表单字段权限属性元素 + addFormFieldsPermission(node.getFieldsPermission(), userTask); + // 添加操作按钮配置属性元素 + addButtonsSetting(node.getButtonsSetting(), userTask); + // 处理多实例(审批方式) + processMultiInstanceLoopCharacteristics(node.getApproveMethod(), node.getApproveRatio(), userTask); + // 添加任务被拒绝的处理元素 + addTaskRejectElements(node.getRejectHandler(), userTask); + // 添加用户任务的审批人与发起人相同时的处理元素 + addAssignStartUserHandlerType(node.getAssignStartUserHandlerType(), userTask); + // 添加用户任务的空处理元素 + addAssignEmptyHandlerType(node.getAssignEmptyHandler(), userTask); + // 设置审批任务的截止时间 + if (node.getTimeoutHandler() != null && node.getTimeoutHandler().getEnable()) { + userTask.setDueDate(node.getTimeoutHandler().getTimeDuration()); + } + return userTask; + } + + private void processMultiInstanceLoopCharacteristics(Integer approveMethod, Integer approveRatio, UserTask userTask) { + BpmUserTaskApproveMethodEnum approveMethodEnum = BpmUserTaskApproveMethodEnum.valueOf(approveMethod); + Assert.notNull(approveMethodEnum, "审批方式({})不能为空", approveMethodEnum); + // 添加审批方式的扩展属性 + addExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_METHOD, approveMethod); + if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RANDOM) { + // 随机审批,不需要设置多实例属性 + return; + } + + // 处理多实例审批方式 + MultiInstanceLoopCharacteristics multiInstanceCharacteristics = new MultiInstanceLoopCharacteristics(); + // 设置 collectionVariable。本系统用不到,仅仅为了 Flowable 校验不报错 + multiInstanceCharacteristics.setInputDataItem("${coll_userList}"); + if (approveMethodEnum == BpmUserTaskApproveMethodEnum.ANY) { + multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition()); + multiInstanceCharacteristics.setSequential(false); + } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.SEQUENTIAL) { + multiInstanceCharacteristics.setCompletionCondition(approveMethodEnum.getCompletionCondition()); + multiInstanceCharacteristics.setSequential(true); + multiInstanceCharacteristics.setLoopCardinality("1"); + } else if (approveMethodEnum == BpmUserTaskApproveMethodEnum.RATIO) { + Assert.notNull(approveRatio, "通过比例不能为空"); + multiInstanceCharacteristics.setCompletionCondition( + String.format(approveMethodEnum.getCompletionCondition(), String.format("%.2f", approveRatio / 100D))); + multiInstanceCharacteristics.setSequential(false); + } + userTask.setLoopCharacteristics(multiInstanceCharacteristics); + } + + } + + private static class CopyNodeConvert implements NodeConvert { + + @Override + public ServiceTask convert(BpmSimpleModelNodeVO node) { + ServiceTask serviceTask = new ServiceTask(); + serviceTask.setId(node.getId()); + serviceTask.setName(node.getName()); + serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION); + serviceTask.setImplementation("${" + BpmCopyTaskDelegate.BEAN_NAME + "}"); + + // 添加抄送候选人元素 + addCandidateElements(node.getCandidateStrategy(), node.getCandidateParam(), serviceTask); + // 添加表单字段权限属性元素 + addFormFieldsPermission(node.getFieldsPermission(), serviceTask); + return serviceTask; + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.COPY_NODE; + } + + } + + private static class ConditionBranchNodeConvert implements NodeConvert { + + @Override + public ExclusiveGateway convert(BpmSimpleModelNodeVO node) { + ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); + exclusiveGateway.setId(node.getId()); + // TODO @jason:setName + + // 设置默认的序列流(条件) + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), + item -> BooleanUtil.isTrue(item.getDefaultFlow())); + Assert.notNull(defaultSeqFlow, "条件分支节点({})的默认序列流不能为空", node.getId()); + exclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); + return exclusiveGateway; + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.CONDITION_BRANCH_NODE; + } + + } + + private static class ParallelBranchNodeConvert implements NodeConvert { + + @Override + public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) { + ParallelGateway parallelGateway = new ParallelGateway(); + parallelGateway.setId(node.getId()); + // TODO @jason:setName + + // 并行聚合网关由程序创建,前端不需要传入 + ParallelGateway joinParallelGateway = new ParallelGateway(); + joinParallelGateway.setId(buildGatewayJoinId(node.getId())); + // TODO @jason:setName + return CollUtil.newArrayList(parallelGateway, joinParallelGateway); + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE; + } + + } + + private static class InclusiveBranchNodeConvert implements NodeConvert { + + @Override + public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) { + InclusiveGateway inclusiveGateway = new InclusiveGateway(); + inclusiveGateway.setId(node.getId()); + // 设置默认的序列流(条件) + BpmSimpleModelNodeVO defaultSeqFlow = CollUtil.findOne(node.getConditionNodes(), + item -> BooleanUtil.isTrue(item.getDefaultFlow())); + Assert.notNull(defaultSeqFlow, "包容分支节点({})的默认序列流不能为空", node.getId()); + inclusiveGateway.setDefaultFlow(defaultSeqFlow.getId()); + // TODO @jason:setName + + // 并行聚合网关由程序创建,前端不需要传入 + InclusiveGateway joinInclusiveGateway = new InclusiveGateway(); + joinInclusiveGateway.setId(buildGatewayJoinId(node.getId())); + // TODO @jason:setName + return CollUtil.newArrayList(inclusiveGateway, joinInclusiveGateway); + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE; + } + + } + + public static class ConditionNodeConvert implements NodeConvert { + + @Override + public List<? extends FlowElement> convertList(BpmSimpleModelNodeVO node) { + // 原因是:正常情况下,它不会被调用到 + throw new UnsupportedOperationException("条件节点不支持转换"); + } + + @Override + public BpmSimpleModelNodeType getType() { + return BpmSimpleModelNodeType.CONDITION_NODE; + } + + public static SequenceFlow buildSequenceFlow(String sourceId, String targetId, + BpmSimpleModelNodeVO node) { + String conditionExpression = buildConditionExpression(node); + return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression); + } + + /** + * 构造条件表达式 + * + * @param node 条件节点 + */ + public static String buildConditionExpression(BpmSimpleModelNodeVO node) { + BpmSimpleModeConditionType conditionTypeEnum = BpmSimpleModeConditionType.valueOf(node.getConditionType()); + if (conditionTypeEnum == BpmSimpleModeConditionType.EXPRESSION) { + return node.getConditionExpression(); + } + if (conditionTypeEnum == BpmSimpleModeConditionType.RULE) { + BpmSimpleModelNodeVO.ConditionGroups conditionGroups = node.getConditionGroups(); + if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) { + return null; + } + List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> { + if (CollUtil.isEmpty(item.getRules())) { + return ""; + } + // 构造规则表达式 + List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> { + String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() + : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号 + return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide); + }); + // 构造条件组的表达式 + Boolean and = item.getAnd(); + return "(" + CollUtil.join(list, and ? " && " : " || ") + ")"; + }); + return String.format("${%s}", CollUtil.join(strConditionGroups, conditionGroups.getAnd() ? " && " : " || ")); + } + return null; + } + + } + + private static String buildGatewayJoinId(String id) { + return id + "_join"; + } + + // ========== SIMPLE 流程预测相关的方法 ========== + + public static List<BpmSimpleModelNodeVO> simulateProcess(BpmSimpleModelNodeVO rootNode, Map<String, Object> variables) { + List<BpmSimpleModelNodeVO> resultNodes = new ArrayList<>(); + + // 从头开始遍历 + simulateNextNode(rootNode, variables, resultNodes); + return resultNodes; + } + + private static void simulateNextNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables, + List<BpmSimpleModelNodeVO> resultNodes) { + // 如果不合法(包括为空),则直接结束 + if (!isValidNode(currentNode)) { + return; + } + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentNode.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + + // 情况:START_NODE/START_USER_NODE/APPROVE_NODE/COPY_NODE/END_NODE + if (nodeType == BpmSimpleModelNodeType.START_NODE + || nodeType == BpmSimpleModelNodeType.START_USER_NODE + || nodeType == BpmSimpleModelNodeType.APPROVE_NODE + || nodeType == BpmSimpleModelNodeType.COPY_NODE + || nodeType == BpmSimpleModelNodeType.END_NODE) { + // 添加元素 + resultNodes.add(currentNode); + } + + // 情况:CONDITION_BRANCH_NODE 排它,只有一个满足条件的。如果没有,就走默认的 + if (nodeType == BpmSimpleModelNodeType.CONDITION_BRANCH_NODE) { + // 查找满足条件的 BpmSimpleModelNodeVO 节点 + BpmSimpleModelNodeVO matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), + conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow()) + && evalConditionExpress(variables, conditionNode)); + if (matchConditionNode == null) { + matchConditionNode = CollUtil.findOne(currentNode.getConditionNodes(), + conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow())); + } + Assert.notNull(matchConditionNode, "找不到条件节点({})", currentNode); + // 遍历满足条件的 BpmSimpleModelNodeVO 节点 + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes); + } + + // 情况:INCLUSIVE_BRANCH_NODE 包容,多个满足条件的。如果没有,就走默认的 + if (nodeType == BpmSimpleModelNodeType.INCLUSIVE_BRANCH_NODE) { + // 查找满足条件的 BpmSimpleModelNodeVO 节点 + Collection<BpmSimpleModelNodeVO> matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), + conditionNode -> !BooleanUtil.isTrue(conditionNode.getDefaultFlow()) + && evalConditionExpress(variables, conditionNode)); + if (CollUtil.isEmpty(matchConditionNodes)) { + matchConditionNodes = CollUtil.filterNew(currentNode.getConditionNodes(), + conditionNode -> BooleanUtil.isTrue(conditionNode.getDefaultFlow())); + } + Assert.isTrue(!matchConditionNodes.isEmpty(), "找不到条件节点({})", currentNode); + // 遍历满足条件的 BpmSimpleModelNodeVO 节点 + matchConditionNodes.forEach(matchConditionNode -> + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); + } + + // 情况:PARALLEL_BRANCH_NODE 并行,都满足,都走 + if (nodeType == BpmSimpleModelNodeType.PARALLEL_BRANCH_NODE) { + // 遍历所有 BpmSimpleModelNodeVO 节点 + currentNode.getConditionNodes().forEach(matchConditionNode -> + simulateNextNode(matchConditionNode.getChildNode(), variables, resultNodes)); + } + + // 遍历子节点 + simulateNextNode(currentNode.getChildNode(), variables, resultNodes); + } + + public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO conditionNode) { + return BpmnModelUtils.evalConditionExpress(variables, ConditionNodeConvert.buildConditionExpression(conditionNode)); + } + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryService.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryService.java index e0bcd44..ffe2f73 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryService.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryService.java @@ -82,4 +82,11 @@ */ List<BpmCategoryDO> getCategoryListByStatus(Integer status); + /** + * 批量更新流程分类的排序:每个分类的 sort 值,从 0 开始递增 + * + * @param ids 分类编号列表 + */ + void updateCategorySortBatch(List<Long> ids); + } \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryServiceImpl.java index f020701..2ba5bf6 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryServiceImpl.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmCategoryServiceImpl.java @@ -9,12 +9,15 @@ import com.iailab.module.bpm.dal.dataobject.definition.BpmCategoryDO; import com.iailab.module.bpm.dal.mysql.category.BpmCategoryMapper; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; @@ -56,7 +59,7 @@ private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) { BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName()); if (category == null - || ObjUtil.equal(category.getId(), updateReqVO.getId())) { + || ObjUtil.equal(category.getId(), updateReqVO.getId())) { return; } throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName()); @@ -65,7 +68,7 @@ private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) { BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode()); if (category == null - || ObjUtil.equal(category.getId(), updateReqVO.getId())) { + || ObjUtil.equal(category.getId(), updateReqVO.getId())) { return; } throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode()); @@ -108,4 +111,20 @@ return bpmCategoryMapper.selectListByStatus(status); } + @Override + @Transactional(rollbackFor = Exception.class) + public void updateCategorySortBatch(List<Long> ids) { + // 校验分类都存在 + List<BpmCategoryDO> categories = bpmCategoryMapper.selectBatchIds(ids); + if (categories.size() != ids.size()) { + throw exception(CATEGORY_NOT_EXISTS); + } + + // 批量更新排序 + List<BpmCategoryDO> updateList = IntStream.range(0, ids.size()) + .mapToObj(index -> new BpmCategoryDO().setId(ids.get(index)).setSort(index)) + .collect(Collectors.toList()); + bpmCategoryMapper.updateBatch(updateList); + } + } \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelService.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelService.java index 9ab3e1e..a70d5f7 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelService.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelService.java @@ -1,13 +1,13 @@ package com.iailab.module.bpm.service.definition; -import com.iailab.framework.common.pojo.PageResult; -import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; -import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; -import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import org.flowable.bpmn.model.BpmnModel; import org.flowable.engine.repository.Model; import javax.validation.Valid; +import java.util.List; /** * Flowable流程模型接口 @@ -17,21 +17,20 @@ public interface BpmModelService { /** - * 获得流程模型分页 + * 获得流程模型列表 * - * @param pageVO 分页查询 - * @return 流程模型分页 + * @param name 模型名称 + * @return 流程模型列表 */ - PageResult<Model> getModelPage(BpmModelPageReqVO pageVO); + List<Model> getModelList(String name); /** * 创建流程模型 * * @param modelVO 创建信息 - * @param bpmnXml BPMN XML * @return 创建的流程模型的编号 */ - String createModel(@Valid BpmModelCreateReqVO modelVO, String bpmnXml); + String createModel(@Valid BpmModelSaveReqVO modelVO); /** * 获得流程模块 @@ -50,33 +49,53 @@ byte[] getModelBpmnXML(String id); /** + * 修改流程模型的 BPMN XML + * + * @param id 编号 + * @param bpmnXml BPMN XML + */ + void updateModelBpmnXml(String id, String bpmnXml); + + /** * 修改流程模型 * + * @param userId 用户编号 * @param updateReqVO 更新信息 */ - void updateModel(@Valid BpmModelUpdateReqVO updateReqVO); + void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO); + + /** + * 批量更新模型排序 + * + * @param userId 用户编号 + * @param ids 编号列表 + */ + void updateModelSortBatch(Long userId, List<String> ids); /** * 将流程模型,部署成一个流程定义 * + * @param userId 用户编号 * @param id 编号 */ - void deployModel(String id); + void deployModel(Long userId, String id); /** * 删除模型 * + * @param userId 用户编号 * @param id 编号 */ - void deleteModel(String id); + void deleteModel(Long userId, String id); /** * 修改模型的状态,实际更新的部署的流程定义的状态 * + * @param userId 用户编号 * @param id 编号 * @param state 状态 */ - void updateModelState(String id, Integer state); + void updateModelState(Long userId, String id, Integer state); /** * 获得流程定义编号对应的 BPMN Model @@ -86,4 +105,22 @@ */ BpmnModel getBpmnModelByDefinitionId(String processDefinitionId); + // ========== 仿钉钉/飞书的精简模型 ========= + + /** + * 获取仿钉钉流程设计模型结构 + * + * @param modelId 流程模型编号 + * @return 仿钉钉流程设计模型结构 + */ + BpmSimpleModelNodeVO getSimpleModel(String modelId); + + /** + * 更新仿钉钉流程设计模型 + * + * @param userId 用户编号 + * @param reqVO 请求信息 + */ + void updateSimpleModel(Long userId, @Valid BpmSimpleModelUpdateReqVO reqVO); + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java index d611acd..49bd31f 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmModelServiceImpl.java @@ -1,19 +1,21 @@ package com.iailab.module.bpm.service.definition; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; -import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.json.JsonUtils; -import com.iailab.framework.common.util.object.PageUtils; import com.iailab.framework.common.util.validation.ValidationUtils; -import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelCreateReqVO; -import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO; -import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelUpdateReqVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; +import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO; import com.iailab.module.bpm.convert.definition.BpmModelConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; import com.iailab.module.bpm.enums.definition.BpmModelFormTypeEnum; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; +import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils; import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; @@ -32,18 +34,18 @@ import javax.annotation.Resource; import javax.validation.Valid; import java.util.List; +import java.util.Map; import java.util.Objects; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.iailab.framework.common.util.collection.CollectionUtils.convertMap; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; /** * Flowable流程模型实现 * 主要进行 Flowable {@link Model} 的维护 * - * @author yunlongn * @author iailab - * @author jason */ @Service @Validated @@ -61,91 +63,118 @@ private BpmTaskCandidateInvoker taskCandidateInvoker; @Override - public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) { + public List<Model> getModelList(String name) { ModelQuery modelQuery = repositoryService.createModelQuery(); - if (StrUtil.isNotBlank(pageVO.getKey())) { - modelQuery.modelKey(pageVO.getKey()); + if (StrUtil.isNotEmpty(name)) { + modelQuery.modelNameLike(name); } - if (StrUtil.isNotBlank(pageVO.getName())) { - modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配 - } - if (StrUtil.isNotBlank(pageVO.getCategory())) { - modelQuery.modelCategory(pageVO.getCategory()); - } - // 执行查询 - long count = modelQuery.count(); - if (count == 0) { - return PageResult.empty(count); - } - // 关闭多租户查询,不添加tenantId条件 - if (StrUtil.isNotBlank(FlowableUtils.getTenantId())) { - modelQuery.modelTenantId(FlowableUtils.getTenantId()); - } - List<Model> models = modelQuery - .orderByCreateTime().desc() - .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); - return new PageResult<>(models, count); + return modelQuery.list(); } @Override @Transactional(rollbackFor = Exception.class) - public String createModel(@Valid BpmModelCreateReqVO createReqVO, String bpmnXml) { + public String createModel(@Valid BpmModelSaveReqVO createReqVO) { if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) { throw exception(MODEL_KEY_VALID); } - // 校验流程标识已经存在 + // 1. 校验流程标识已经存在 Model keyModel = getModelByKey(createReqVO.getKey()); if (keyModel != null) { throw exception(MODEL_KEY_EXISTS, createReqVO.getKey()); } - // 创建流程定义 + // 2.1 创建流程定义 + createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序 Model model = repositoryService.newModel(); - BpmModelConvert.INSTANCE.copyToCreateModel(model, createReqVO); + BpmModelConvert.INSTANCE.copyToModel(model, createReqVO); model.setTenantId(FlowableUtils.getTenantId()); - // 保存流程定义 + // 2.2 保存流程定义 repositoryService.saveModel(model); - // 保存 BPMN XML - saveModelBpmnXml(model, bpmnXml); return model.getId(); } @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 - public void updateModel(@Valid BpmModelUpdateReqVO updateReqVO) { - // 校验流程模型存在 - Model model = getModel(updateReqVO.getId()); + public void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO) { + // 1. 校验流程模型存在 + Model model = validateModelManager(updateReqVO.getId(), userId); + + // 修改流程定义 + BpmModelConvert.INSTANCE.copyToModel(model, updateReqVO); + // 更新模型 + repositoryService.saveModel(model); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateModelSortBatch(Long userId, List<String> ids) { + // 1.1 校验流程模型存在 + List<Model> models = repositoryService.createModelQuery() + .modelTenantId(FlowableUtils.getTenantId()).list(); + models.removeIf(model ->!ids.contains(model.getId())); + if (ids.size() != models.size()) { + throw exception(MODEL_NOT_EXISTS); + } + Map<String, Model> modelMap = convertMap(models, Model::getId); + // 1.2 校验是否为管理员 + ids.forEach(id -> validateModelManager(id, userId)); + + // 保存排序 + long sort = System.currentTimeMillis(); // 使用时间戳 - i 作为排序 + for (int i = ids.size() - 1; i > 0; i--) { + Model model = modelMap.get(ids.get(i)); + // 更新模型 + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model).setSort(sort); + model.setMetaInfo(JsonUtils.toJsonString(metaInfo)); + repositoryService.saveModel(model); + // 更新排序 + processDefinitionService.updateProcessDefinitionSortByModelId(model.getId(), sort); + sort--; + } + } + + private Model validateModelExists(String id) { + Model model = repositoryService.getModel(id); if (model == null) { throw exception(MODEL_NOT_EXISTS); } + return model; + } - // 修改流程定义 - BpmModelConvert.INSTANCE.copyToUpdateModel(model, updateReqVO); - // 更新模型 - repositoryService.saveModel(model); - // 更新 BPMN XML - saveModelBpmnXml(model, updateReqVO.getBpmnXml()); + /** + * 校验是否有流程模型的管理权限 + * + * @param id 流程模型编号 + * @param userId 用户编号 + * @return 流程模型 + */ + private Model validateModelManager(String id, Long userId) { + Model model = validateModelExists(id); + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); + if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) { + throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER, model.getName()); + } + return model; } @Override @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务 - public void deployModel(String id) { + public void deployModel(Long userId, String id) { // 1.1 校验流程模型存在 - Model model = getModel(id); - if (ObjectUtils.isEmpty(model)) { - throw exception(MODEL_NOT_EXISTS); - } + Model model = validateModelManager(id, userId); // 1.2 校验流程图 byte[] bpmnBytes = getModelBpmnXML(model.getId()); validateBpmnXml(bpmnBytes); // 1.3 校验表单已配 - BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class); + BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model); BpmFormDO form = validateFormConfig(metaInfo); // 1.4 校验任务分配规则已配置 taskCandidateInvoker.validateBpmnConfig(bpmnBytes); + // 1.5 获取仿钉钉流程设计器模型数据 + String simpleJson = getModelSimpleJson(model.getId()); // 2.1 创建流程定义 - String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form); + String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form); // 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。 updateProcessDefinitionSuspended(model.getDeploymentId()); @@ -177,12 +206,10 @@ @Override @Transactional(rollbackFor = Exception.class) - public void deleteModel(String id) { + public void deleteModel(Long userId, String id) { // 校验流程模型存在 - Model model = getModel(id); - if (model == null) { - throw exception(MODEL_NOT_EXISTS); - } + Model model = validateModelManager(id, userId); + // 执行删除 repositoryService.deleteModel(id); // 禁用流程定义 @@ -190,12 +217,9 @@ } @Override - public void updateModelState(String id, Integer state) { + public void updateModelState(Long userId, String id, Integer state) { // 1.1 校验流程模型存在 - Model model = getModel(id); - if (model == null) { - throw exception(MODEL_NOT_EXISTS); - } + Model model = validateModelManager(id, userId); // 1.2 校验流程定义存在 ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId()); if (definition == null) { @@ -211,13 +235,34 @@ return repositoryService.getBpmnModel(processDefinitionId); } + @Override + public BpmSimpleModelNodeVO getSimpleModel(String modelId) { + Model model = validateModelExists(modelId); + // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据 + String json = getModelSimpleJson(model.getId()); + return JsonUtils.parseObject(json, BpmSimpleModelNodeVO.class); + } + + @Override + public void updateSimpleModel(Long userId, BpmSimpleModelUpdateReqVO reqVO) { + // 1. 校验流程模型存在 + Model model = validateModelManager(reqVO.getId(), userId); + + // 2.1 JSON 转换成 bpmnModel + BpmnModel bpmnModel = SimpleModelUtils.buildBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModel()); + // 2.2 保存 Bpmn XML + updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); + // 2.3 保存 JSON 数据 + updateModelSimpleJson(model.getId(), reqVO.getSimpleModel()); + } + /** * 校验流程表单已配置 * * @param metaInfo 流程模型元数据 * @return 表单配置 */ - private BpmFormDO validateFormConfig(BpmModelMetaInfoRespDTO metaInfo) { + private BpmFormDO validateFormConfig(BpmModelMetaInfoVO metaInfo) { if (metaInfo == null || metaInfo.getFormType() == null) { throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG); } @@ -239,16 +284,34 @@ } } - private void saveModelBpmnXml(Model model, String bpmnXml) { + @Override + public void updateModelBpmnXml(String id, String bpmnXml) { if (StrUtil.isEmpty(bpmnXml)) { return; } - repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml)); + repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); + } + + @SuppressWarnings("JavaExistingMethodCanBeUsed") + private String getModelSimpleJson(String id) { + byte[] bytes = repositoryService.getModelEditorSourceExtra(id); + if (ArrayUtil.isEmpty(bytes)) { + return null; + } + return StrUtil.utf8Str(bytes); + } + + private void updateModelSimpleJson(String id, BpmSimpleModelNodeVO node) { + if (node == null) { + return; + } + byte[] bytes = JsonUtils.toJsonByte(node); + repositoryService.addModelEditorSourceExtra(id, bytes); } /** * 挂起 deploymentId 对应的流程定义 - * + * <p> * 注意:这里一个 deploymentId 只关联一个流程定义 * * @param deploymentId 流程发布Id @@ -265,7 +328,9 @@ } private Model getModelByKey(String key) { - return repositoryService.createModelQuery().modelKey(key).singleResult(); + return repositoryService.createModelQuery() + .modelTenantId(FlowableUtils.getTenantId()) + .modelKey(key).singleResult(); } @Override @@ -277,5 +342,4 @@ public byte[] getModelBpmnXML(String id) { return repositoryService.getModelEditorSource(id); } - } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionService.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionService.java index 5b9fa15..0788948 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionService.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionService.java @@ -1,6 +1,7 @@ package com.iailab.module.bpm.service.definition; import com.iailab.framework.common.pojo.PageResult; +import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; @@ -40,7 +41,7 @@ * @param suspensionState 中断状态 * @return 流程定义列表 */ - List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState); + List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState, String categoryId); /** * 基于流程模型,创建流程定义 @@ -48,10 +49,12 @@ * @param model 流程模型 * @param modelMetaInfo 流程模型元信息 * @param bpmnBytes BPMN XML 字节数组 + * @param simpleJson SIMPLE Model JSON * @param form 表单 * @return 流程编号 */ - String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, byte[] bpmnBytes, BpmFormDO form); + String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, + byte[] bpmnBytes, String simpleJson, BpmFormDO form); /** * 更新流程定义状态 @@ -60,6 +63,14 @@ * @param state 状态 */ void updateProcessDefinitionState(String id, Integer state); + + /** + * 更新模型编号 + * + * @param modelId 流程定义编号 + * @param sort 排序 + */ + void updateProcessDefinitionSortByModelId(String modelId, Long sort); /** * 获得流程定义对应的 BPMN @@ -134,6 +145,15 @@ ProcessDefinition getActiveProcessDefinition(String key); /** + * 判断用户是否可以使用该流程定义,进行流程的发起 + * + * @param processDefinition 流程定义 + * @param userId 用户编号 + * @return 是否可以发起流程 + */ + boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId); + + /** * 获得 ids 对应的 Deployment Map * * @param ids 部署编号的数组 diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java index a59f234..3519a91 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java @@ -5,6 +5,7 @@ import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.object.BeanUtils; import com.iailab.framework.common.util.object.PageUtils; +import com.iailab.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import com.iailab.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; import com.iailab.module.bpm.dal.dataobject.definition.BpmFormDO; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; @@ -13,6 +14,7 @@ import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; import org.flowable.bpmn.model.BpmnModel; import org.flowable.common.engine.impl.db.SuspensionState; import org.flowable.engine.RepositoryService; @@ -36,8 +38,6 @@ * 流程定义实现 * 主要进行 Flowable {@link ProcessDefinition} 和 {@link Deployment} 的维护 * - * @author yunlongn - * @author ZJQ * @author iailab */ @Service @@ -79,7 +79,22 @@ @Override public ProcessDefinition getActiveProcessDefinition(String key) { - return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult(); + return repositoryService.createProcessDefinitionQuery() + .processDefinitionTenantId(FlowableUtils.getTenantId()) + .processDefinitionKey(key).active().singleResult(); + } + + @Override + public boolean canUserStartProcessDefinition(BpmProcessDefinitionInfoDO processDefinition, Long userId) { + if (processDefinition == null) { + return false; + } + // 为空,则所有人都可以发起 + if (CollUtil.isEmpty(processDefinition.getStartUserIds())) { + return true; + } + // 不为空,则需要存在里面 + return processDefinition.getStartUserIds().contains(userId); } @Override @@ -103,8 +118,8 @@ } @Override - public String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, - byte[] bpmnBytes, BpmFormDO form) { + public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo, + byte[] bpmnBytes, String simpleJson, BpmFormDO form) { // 创建 Deployment 部署 Deployment deploy = repositoryService.createDeployment() .key(model.getKey()).name(model.getName()).category(model.getCategory()) @@ -129,7 +144,9 @@ // 插入拓展表 BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class) - .setModelId(model.getId()).setProcessDefinitionId(definition.getId()); + .setModelId(model.getId()).setProcessDefinitionId(definition.getId()) + .setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson); + if (form != null) { definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf()); } @@ -155,6 +172,11 @@ } @Override + public void updateProcessDefinitionSortByModelId(String modelId, Long sort) { + processDefinitionMapper.updateByModelId(modelId, new BpmProcessDefinitionInfoDO().setSort(sort)); + } + + @Override public BpmnModel getProcessDefinitionBpmnModel(String id) { return repositoryService.getBpmnModel(id); } @@ -172,6 +194,7 @@ @Override public PageResult<ProcessDefinition> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) { ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); + query.processDefinitionTenantId(FlowableUtils.getTenantId()); if (StrUtil.isNotBlank(pageVO.getKey())) { query.processDefinitionKey(pageVO.getKey()); } @@ -186,7 +209,7 @@ } @Override - public List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState) { + public List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState, String categoryId) { // 拼接查询条件 ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery(); if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), suspensionState)) { @@ -194,13 +217,11 @@ } else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), suspensionState)) { query.active(); } - // 执行查询 - // 关闭多租户查询,不添加tenantId条件 - if (StrUtil.isNotBlank(FlowableUtils.getTenantId())) { - query.processDefinitionTenantId(FlowableUtils.getTenantId()); - } else { - query.processDefinitionWithoutTenantId(); + if(ObjectUtils.isNotEmpty(categoryId)) { + query.processDefinitionCategory(categoryId); } + // 执行查询 + query.processDefinitionTenantId(FlowableUtils.getTenantId()); return query.list(); } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageService.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageService.java index 619667d..b93cd17 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageService.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageService.java @@ -3,6 +3,7 @@ import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; +import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; import javax.validation.Valid; @@ -36,4 +37,11 @@ */ void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO); + /** + * 发送任务审批超时的消息 + * + * @param reqDTO 发送信息 + */ + void sendMessageWhenTaskTimeout(@Valid BpmMessageSendWhenTaskTimeoutReqDTO reqDTO); + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageServiceImpl.java index 5f054f2..6199a8a 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageServiceImpl.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/BpmMessageServiceImpl.java @@ -6,6 +6,7 @@ import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO; +import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; import com.iailab.module.system.api.sms.SmsSendApi; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -61,6 +62,16 @@ BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams)); } + @Override + public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) { + Map<String, Object> templateParams = new HashMap<>(); + templateParams.put("processInstanceName", reqDTO.getProcessInstanceName()); + templateParams.put("taskName", reqDTO.getTaskName()); + templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId())); + smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(), + BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams)).checkError(); + } + private String getProcessInstanceDetailUrl(String taskId) { return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId; } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java new file mode 100644 index 0000000..b1ed3ce --- /dev/null +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/message/dto/BpmMessageSendWhenTaskTimeoutReqDTO.java @@ -0,0 +1,42 @@ +package com.iailab.module.bpm.service.message.dto; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * BPM 发送任务审批超时 Request DTO + */ +@Data +public class BpmMessageSendWhenTaskTimeoutReqDTO { + + /** + * 流程实例的编号 + */ + @NotEmpty(message = "流程实例的编号不能为空") + private String processInstanceId; + /** + * 流程实例的名字 + */ + @NotEmpty(message = "流程实例的名字不能为空") + private String processInstanceName; + + /** + * 流程任务的编号 + */ + @NotEmpty(message = "流程任务的编号不能为空") + private String taskId; + /** + * 流程任务的名字 + */ + @NotEmpty(message = "流程任务的名字不能为空") + private String taskName; + + /** + * 审批人的用户编号 + */ + @NotNull(message = "审批人的用户编号不能为空") + private Long assigneeUserId; + +} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityService.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityService.java deleted file mode 100644 index 69055f0..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityService.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.iailab.module.bpm.service.task; - -import com.iailab.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import org.flowable.engine.history.HistoricActivityInstance; - -import java.util.List; - -/** - * BPM 活动实例 Service 接口 - * - * @author iailab - */ -public interface BpmActivityService { - - /** - * 获得指定流程实例的活动实例列表 - * - * @param processInstanceId 流程实例的编号 - * @return 活动实例列表 - */ - List<BpmActivityRespVO> getActivityListByProcessInstanceId(String processInstanceId); - - /** - * 获得执行编号对应的活动实例 - * - * @param executionId 执行编号 - * @return 活动实例 - */ - List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId); - -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityServiceImpl.java deleted file mode 100644 index 9896aef..0000000 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmActivityServiceImpl.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.iailab.module.bpm.service.task; - -import com.iailab.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import com.iailab.module.bpm.convert.task.BpmActivityConvert; -import lombok.extern.slf4j.Slf4j; -import org.flowable.engine.HistoryService; -import org.flowable.engine.history.HistoricActivityInstance; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; -import java.util.List; - - -/** - * BPM 活动实例 Service 实现类 - * - * @author iailab - */ -@Service -@Slf4j -@Validated -public class BpmActivityServiceImpl implements BpmActivityService { - - @Resource - private HistoryService historyService; - - @Override - public List<BpmActivityRespVO> getActivityListByProcessInstanceId(String processInstanceId) { - List<HistoricActivityInstance> activityList = historyService.createHistoricActivityInstanceQuery() - .processInstanceId(processInstanceId).list(); - return BpmActivityConvert.INSTANCE.convertList(activityList); - } - - @Override - public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) { - return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); - } - -} diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyService.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyService.java index 8d37b1b..894e2f6 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -3,7 +3,9 @@ import com.iailab.framework.common.pojo.PageResult; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; import com.iailab.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; +import org.flowable.bpmn.model.FlowNode; +import javax.validation.constraints.NotEmpty; import java.util.Collection; /** @@ -14,12 +16,29 @@ public interface BpmProcessInstanceCopyService { /** - * 流程实例的抄送 + * 【管理员】流程实例的抄送 * * @param userIds 抄送的用户编号 + * @param reason 抄送意见 * @param taskId 流程任务编号 */ - void createProcessInstanceCopy(Collection<Long> userIds, String taskId); + void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId); + + /** + * 【自动抄送】流程实例的抄送 + * + * @param userIds 抄送的用户编号 + * @param reason 抄送意见 + * @param processInstanceId 流程编号 + * @param activityId 流程活动编号(对应 {@link FlowNode#getId()}) + * @param activityName 任务编号(对应 {@link FlowNode#getName()}) + * @param taskId 任务编号,允许空 + */ + void createProcessInstanceCopy(Collection<Long> userIds, String reason, + @NotEmpty(message = "流程实例编号不能为空") String processInstanceId, + @NotEmpty(message = "流程活动编号不能为空") String activityId, + @NotEmpty(message = "流程活动名字不能为空") String activityName, + String taskId); /** * 获得抄送的流程的分页 diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index f78ce88..48414cb 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -22,6 +22,7 @@ import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.util.collection.CollectionUtils.convertList; + /** * 流程抄送 Service 实现类 * @@ -47,19 +48,25 @@ private BpmProcessDefinitionService processDefinitionService; @Override - public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) { - // 1.1 校验任务存在 + public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId) { Task task = taskService.getTask(taskId); if (ObjectUtil.isNull(task)) { throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); } - // 1.2 校验流程实例存在 - String processInstanceId = task.getProcessInstanceId(); + // 执行抄送 + createProcessInstanceCopy(userIds, reason, + task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getId(), task.getName()); + } + + @Override + public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String processInstanceId, + String activityId, String activityName, String taskId) { + // 1.1 校验流程实例存在 ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } - // 1.3 校验流程定义存在 + // 1.2 校验流程定义存在 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( processInstance.getProcessDefinitionId()); if (processDefinition == null) { @@ -68,9 +75,10 @@ // 2. 创建抄送流程 List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() - .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId())) + .setUserId(userId).setReason(reason).setStartUserId(Long.valueOf(processInstance.getStartUserId())) .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) - .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(task.getName())); + .setCategory(processDefinition.getCategory()).setTaskId(taskId) + .setActivityId(activityId).setActivityName(activityName)); processInstanceCopyMapper.insertBatch(copyList); } @@ -80,4 +88,5 @@ return processInstanceCopyMapper.selectPage(userId, pageReqVO); } + } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceService.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceService.java index e14f225..69b9668 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceService.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceService.java @@ -2,10 +2,7 @@ import com.iailab.framework.common.pojo.PageResult; import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; -import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; -import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; -import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; -import org.flowable.engine.delegate.event.FlowableCancelledEvent; +import com.iailab.module.bpm.controller.admin.task.vo.instance.*; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.runtime.ProcessInstance; @@ -19,9 +16,11 @@ /** * 流程实例 Service 接口 * - * @author iailab + * @author 芋道源码 */ public interface BpmProcessInstanceService { + + // ========== Query 查询相关方法 ========== /** * 获得流程实例 @@ -85,6 +84,28 @@ PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, @Valid BpmProcessInstancePageReqVO pageReqVO); + // TODO @芋艿:重点在 review 下 + /** + * 获取审批详情。 + * <p> + * 可以是准备发起的流程、进行中的流程、已经结束的流程 + * + * @param loginUserId 登录人的用户编号 + * @param reqVO 请求信息 + * @return 流程实例的进度 + */ + BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO); + + /** + * 获取流程实例的 BPMN 模型视图 + * + * @param id 流程实例的编号 + * @return BPMN 模型视图 + */ + BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id); + + // ========== Update 写入相关方法 ========== + /** * 创建流程实例(提供给前端) * @@ -114,31 +135,26 @@ /** * 管理员取消流程实例 * - * @param userId 用户编号 + * @param userId 用户编号 * @param cancelReqVO 取消信息 */ void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO); /** - * 更新 ProcessInstance 拓展记录为取消 + * 更新 ProcessInstance 为不通过 * - * @param event 流程取消事件 + * @param processInstance 流程实例 + * @param reason 理由。例如说,审批不通过时,需要传递该值 */ - void updateProcessInstanceWhenCancel(FlowableCancelledEvent event); + void updateProcessInstanceReject(ProcessInstance processInstance, String reason); + + // ========== Event 事件相关方法 ========== /** - * 更新 ProcessInstance 拓展记录为完成 + * 处理 ProcessInstance 完成事件,例如说:审批通过、不通过、取消 * * @param instance 流程任务 */ - void updateProcessInstanceWhenApprove(ProcessInstance instance); - - /** - * 更新 ProcessInstance 拓展记录为不通过 - * - * @param id 流程编号 - * @param reason 理由。例如说,审批不通过时,需要传递该值 - */ - void updateProcessInstanceReject(String id, String reason); + void processProcessInstanceCompleted(ProcessInstance instance); } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 9a1cfa0..cbe75f9 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package com.iailab.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.object.PageUtils; import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageReqVO; import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert; import com.iailab.module.bpm.enums.task.BpmDeleteReasonEnum; import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants; import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.message.BpmMessageService; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author iailab */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) { processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 1.2 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel); if (CollUtil.isEmpty(userTaskList)) { return; } // 2. 校验发起人自选审批人的 UserTask 是否都配置了 userTaskList.forEach(userTask -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 管理员取消,不用校验是否为自己的 AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); // 2. 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。 deleteProcessInstance(cancelReqVO.getId(), BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法 } @Override public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) { // 1. 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceReject 方法(审批不通过),已经进行更新了 if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 2. 更新流程实例 status runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus())); } @Override public void updateProcessInstanceWhenApprove(ProcessInstance instance) { // 1. 更新流程实例 status runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.APPROVE.getStatus()); // 2. 发送流程被【通过】的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); // 3. 发送流程实例的状态事件 // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceReject(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程 ProcessInstance processInstance = getProcessInstance(id); deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason))); // 3. 发送流程被【不通过】的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason)); // 4. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } } \ No newline at end of file +package com.iailab.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.json.JsonUtils; import com.iailab.framework.common.util.object.ObjectUtils; import com.iailab.framework.common.util.object.PageUtils; import com.iailab.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import com.iailab.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import com.iailab.module.bpm.controller.admin.task.vo.instance.*; import com.iailab.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; import com.iailab.module.bpm.convert.task.BpmProcessInstanceConvert; import com.iailab.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; import com.iailab.module.bpm.enums.ErrorCodeConstants; import com.iailab.module.bpm.enums.definition.BpmModelTypeEnum; import com.iailab.module.bpm.enums.definition.BpmSimpleModelNodeType; import com.iailab.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import com.iailab.module.bpm.enums.task.BpmReasonEnum; import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum; import com.iailab.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; import com.iailab.module.bpm.framework.flowable.core.candidate.strategy.dept.BpmTaskCandidateStartUserSelectStrategy; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import com.iailab.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.framework.flowable.core.util.SimpleModelUtils; import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.message.BpmMessageService; import com.iailab.module.system.api.dept.DeptApi; import com.iailab.module.system.api.dept.dto.DeptRespDTO; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.history.HistoricTaskInstance; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.util.*; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.util.collection.CollectionUtils.*; import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static com.iailab.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNodeTask; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.flowable.bpmn.constants.BpmnXMLConstants.*; /** * 流程实例 Service 实现类 * <p> * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" /> * <p> * HistoricProcessInstance & ProcessInstance 的关系: * 1. <a href=" https://my.oschina.net/843294669/blog/71902" /> * <p> * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private HistoryService historyService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource @Lazy // 避免循环依赖 private BpmTaskService taskService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceEventPublisher processInstanceEventPublisher; @Resource private BpmTaskCandidateInvoker taskCandidateInvoker; // ========== Query 查询相关方法 ========== @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery() .includeProcessVariables() .processInstanceId(id) .singleResult(); } @Override public List<ProcessInstance> getProcessInstances(Set<String> ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult(); } @Override public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, BpmProcessInstancePageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery() .includeProcessVariables() .processInstanceTenantId(FlowableUtils.getTenantId()) .orderByProcessInstanceStartTime().desc(); if (userId != null) { // 【我的流程】菜单时,需要传递该字段 processInstanceQuery.startedBy(String.valueOf(userId)); } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段 processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId())); } if (StrUtil.isNotEmpty(pageReqVO.getName())) { processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%"); } if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionKey())) { processInstanceQuery.processDefinitionKey(pageReqVO.getProcessDefinitionKey()); } if (StrUtil.isNotEmpty(pageReqVO.getCategory())) { processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory()); } if (pageReqVO.getStatus() != null) { processInstanceQuery.variableValueEquals(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus()); } if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) { processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0])); processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1])); } // 查询数量 long processInstanceCount = processInstanceQuery.count(); if (processInstanceCount == 0) { return PageResult.empty(processInstanceCount); } // 查询列表 List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize()); return new PageResult<>(processInstanceList, processInstanceCount); } private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel, String activityId, String taskId) { // 1. 获取流程活动编号。流程活动 Id 为空事,从流程任务中获取流程活动 Id if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) { activityId = Optional.ofNullable(taskService.getHistoricTask(taskId)) .map(HistoricTaskInstance::getTaskDefinitionKey).orElse(null); } if (StrUtil.isEmpty(activityId)) { return null; } // 2. 从 BpmnModel 中解析表单字段权限 return BpmnModelUtils.parseFormFieldsPermission(bpmnModel, activityId); } @Override public BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1.1 从 reqVO 中,读取公共变量 Long startUserId = loginUserId; // 流程发起人 HistoricProcessInstance historicProcessInstance = null; // 流程实例 Integer processInstanceStatus = BpmProcessInstanceStatusEnum.NOT_START.getStatus(); // 流程状态 Map<String, Object> processVariables = reqVO.getProcessVariables(); // 流程变量 // 1.2 如果是流程已发起的场景,则使用流程实例的数据 if (reqVO.getProcessInstanceId() != null) { historicProcessInstance = getHistoricProcessInstance(reqVO.getProcessInstanceId()); if (historicProcessInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); } startUserId = Long.valueOf(historicProcessInstance.getStartUserId()); processInstanceStatus = FlowableUtils.getProcessInstanceStatus(historicProcessInstance); processVariables = historicProcessInstance.getProcessVariables(); } // 1.3 读取其它相关数据 ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition( historicProcessInstance != null ? historicProcessInstance.getProcessDefinitionId() : reqVO.getProcessDefinitionId()); BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId()); BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId()); // 2.1 已结束 + 进行中的活动节点 List<ActivityNode> endActivityNodes = null; // 已结束的审批信息 List<ActivityNode> runActivityNodes = null; // 进行中的审批信息 List<HistoricActivityInstance> activities = null; // 流程实例列表 if (reqVO.getProcessInstanceId() != null) { activities = taskService.getActivityListByProcessInstanceId(reqVO.getProcessInstanceId()); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(reqVO.getProcessInstanceId(), true); endActivityNodes = getEndActivityNodeList(startUserId, bpmnModel, processDefinitionInfo, historicProcessInstance, processInstanceStatus, activities, tasks); runActivityNodes = getRunApproveNodeList(startUserId, bpmnModel, processDefinition, processVariables, activities, tasks); } // 2.2 流程已经结束,直接 return,无需预测 if (BpmProcessInstanceStatusEnum.isProcessEndStatus(processInstanceStatus)) { return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, null, null); } // 3.1 计算当前登录用户的待办任务 // TODO @jason:有一个极端情况,如果一个用户有 2 个 task A 和 B,A 已经通过,B 需要审核。这个时,通过 A 进来,todo 拿到 B,会不会表单权限不一致哈。 BpmTaskRespVO todoTask = taskService.getFirstTodoTask(loginUserId, reqVO.getProcessInstanceId()); // 3.2 预测未运行节点的审批信息 List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, processDefinitionInfo, processVariables, activities); // 4. 拼接最终数据 return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, processInstanceStatus, endActivityNodes, runActivityNodes, simulateActivityNodes, todoTask); } /** * 拼接审批详情的最终数据 * <p> * 主要是,拼接审批人的用户信息、部门信息 */ private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO, BpmnModel bpmnModel, ProcessDefinition processDefinition, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance processInstance, Integer processInstanceStatus, List<ActivityNode> endApprovalNodeInfos, List<ActivityNode> runningApprovalNodeInfos, List<ActivityNode> simulateApprovalNodeInfos, BpmTaskRespVO todoTask) { // 1. 获取所有需要读取用户信息的 userIds List<ActivityNode> approveNodes = newArrayList(asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos)); Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds(processInstance, approveNodes, todoTask); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); // 2. 表单权限 Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), reqVO.getTaskId()); // 3. 拼接数据 return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition, processDefinitionInfo, processInstance, processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap); } /** * 获得【已结束】的活动节点们 */ private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 遍历 tasks 列表,只处理已结束的 UserTask // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); List<ActivityNode> approvalNodes = convertList(endTasks, task -> { FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) ? BpmSimpleModelNodeType.START_USER_NODE.getType() : BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setStatus(FlowableUtils.getTaskStatus(task)) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); // 如果是取消状态,则跳过 if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) { return null; } return activityNode; }); // 遍历 activities,只处理已结束的 StartEvent、EndEvent List<HistoricActivityInstance> endActivities = filterList(activities, activity -> activity.getEndTime() != null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_EVENT_START, ELEMENT_EVENT_END))); endActivities.forEach(activity -> { // StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点 if (ELEMENT_EVENT_START.equals(activity.getActivityType()) && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) { ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID) .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus()); ActivityNode startNode = new ActivityNode().setId(startTask.getId()) .setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType()) .setStatus(startTask.getStatus()).setTasks(ListUtil.of(startTask)) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); approvalNodes.add(0, startNode); return; } // EndEvent if (ELEMENT_EVENT_END.equals(activity.getActivityType())) { if (BpmProcessInstanceStatusEnum.isRejectStatus(processInstanceStatus)) { // 拒绝情况下,不需要展示 EndEvent 结束节点。原因是:前端已经展示 x 效果,无需重复展示 return; } ActivityNode endNode = new ActivityNode().setId(activity.getId()) .setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()).setStatus(processInstanceStatus) .setStartTime(DateUtils.of(activity.getStartTime())).setEndTime(DateUtils.of(activity.getEndTime())); String reason = FlowableUtils.getProcessInstanceReason(historicProcessInstance); if (StrUtil.isNotEmpty(reason)) { endNode.setTasks(singletonList(new ActivityNodeTask().setId(endNode.getId()) .setStatus(endNode.getStatus()).setReason(reason))); } approvalNodes.add(endNode); } }); return approvalNodes; } /** * 获得【进行中】的活动节点们 */ private List<ActivityNode> getRunApproveNodeList(Long startUserId, BpmnModel bpmnModel, ProcessDefinition processDefinition, Map<String, Object> processVariables, List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { // 构建运行中的任务,基于 activityId 分组 List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER))); Map<String, List<HistoricActivityInstance>> runningTaskMap = convertMultiMap(runActivities, HistoricActivityInstance::getActivityId); // 按照 activityId 分组,构建 ApprovalNodeInfo 节点 Map<String, HistoricTaskInstance> taskMap = convertMap(tasks, HistoricTaskInstance::getId); return convertList(runningTaskMap.entrySet(), entry -> { String activityId = entry.getKey(); List<HistoricActivityInstance> taskActivities = entry.getValue(); // 构建活动节点 FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); HistoricActivityInstance firstActivity = CollUtil.getFirst(taskActivities); // 取第一个任务,会签/或签的任务,开始时间相同 ActivityNode activityNode = new ActivityNode().setId(firstActivity.getActivityId()).setName(firstActivity.getActivityName()) .setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()).setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) .setStartTime(DateUtils.of(CollUtil.getFirst(taskActivities).getStartTime())) .setTasks(new ArrayList<>()); // 处理每个任务的 tasks 属性 for (HistoricActivityInstance activity : taskActivities) { HistoricTaskInstance task = taskMap.get(activity.getTaskId()); activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); // 加签子任务,需要过滤掉已经完成的加签子任务 List<HistoricTaskInstance> childrenTasks = filterList( taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), childTask -> childTask.getEndTime() == null); if (CollUtil.isNotEmpty(childrenTasks)) { activityNode.getTasks().addAll(convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo)); } } // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task if (BpmnModelUtils.isSequentialUserTask(flowNode)) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, flowNode.getId(), startUserId, processDefinition.getId(), processVariables); // 截取当前审批人位置后面的候选人,不包含当前审批人 ActivityNodeTask approvalTaskInfo = CollUtil.getFirst(activityNode.getTasks()); Assert.notNull(approvalTaskInfo, "任务不能为空"); int index = CollUtil.indexOf(candidateUserIds, userId -> ObjectUtils.equalsAny(userId, approvalTaskInfo.getOwner(), approvalTaskInfo.getAssignee())); // 委派或者向前加签情况,需要先比较 owner activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); } return activityNode; }); } /** * 获得【预测(未来)】的活动节点们 */ private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, List<HistoricActivityInstance> activities) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录 Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); // 情况一:BPMN 设计器 if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables); return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel, processDefinitionInfo, processVariables, flowElement, runActivityIds)); } // 情况二:SIMPLE 设计器 if (Objects.equals(BpmModelTypeEnum.SIMPLE.getType(), processDefinitionInfo.getModelType())) { BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables); return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel, processDefinitionInfo, processVariables, simpleNode, runActivityIds)); } throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); } private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, BpmSimpleModelNodeVO node, Set<String> runActivityIds) { // TODO @芋艿:【可优化】在驳回场景下,未来的预测准确性不高。原因是,驳回后,HistoricActivityInstance 包括了历史的操作,不是只有 startEvent 到当前节点的记录 if (runActivityIds.contains(node.getId())) { return null; } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName()) .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy()) .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); // 1. 开始节点/审批节点 if (ObjectUtils.equalsAny(node.getType(), BpmSimpleModelNodeType.START_USER_NODE.getType(), BpmSimpleModelNodeType.APPROVE_NODE.getType())) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); activityNode.setCandidateUserIds(candidateUserIds); return activityNode; } // 2. 结束节点 if (BpmSimpleModelNodeType.END_NODE.getType().equals(node.getType())) { return activityNode; } // 3. 抄送节点 if (CollUtil.isEmpty(runActivityIds) && // 流程发起时:需要展示抄送节点,用于选择抄送人 BpmSimpleModelNodeType.COPY_NODE.getType().equals(node.getType())) { return activityNode; } return null; } private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, FlowElement node, Set<String> runActivityIds) { if (runActivityIds.contains(node.getId())) { return null; } ActivityNode activityNode = new ActivityNode().setId(node.getId()).setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); // 1. 开始节点 if (node instanceof StartEvent) { return activityNode.setName(BpmSimpleModelNodeType.START_USER_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.START_USER_NODE.getType()); } // 2. 审批节点 if (node instanceof UserTask) { List<Long> candidateUserIds = getTaskCandidateUserList(bpmnModel, node.getId(), startUserId, processDefinitionInfo.getProcessDefinitionId(), processVariables); return activityNode.setName(node.getName()).setNodeType(BpmSimpleModelNodeType.APPROVE_NODE.getType()) .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) .setCandidateUserIds(candidateUserIds); } // 3. 结束节点 if (node instanceof EndEvent) { return activityNode.setName(BpmSimpleModelNodeType.END_NODE.getName()) .setNodeType(BpmSimpleModelNodeType.END_NODE.getType()); } return null; } private List<Long> getTaskCandidateUserList(BpmnModel bpmnModel, String activityId, Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { Set<Long> userIds = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId, startUserId, processDefinitionId, processVariables); return new ArrayList<>(userIds); } @Override public BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id) { // 1.1 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } // 1.2 获得流程定义 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()); if (bpmnModel == null) { return null; } BpmSimpleModelNodeVO simpleModel = null; BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo( processInstance.getProcessDefinitionId()); if (processDefinitionInfo != null && BpmModelTypeEnum.SIMPLE.getType().equals(processDefinitionInfo.getModelType())) { simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO.class); } // 1.3 获得流程实例对应的活动实例列表 + 任务列表 List<HistoricActivityInstance> activities = taskService.getActivityListByProcessInstanceId(id); List<HistoricTaskInstance> tasks = taskService.getTaskListByProcessInstanceId(id, true); // 2.1 拼接进度信息 Set<String> unfinishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() == null); Set<String> finishedTaskActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.notEqual(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); Set<String> finishedSequenceFlowActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId, activityInstance -> activityInstance.getEndTime() != null && ObjectUtil.equals(activityInstance.getActivityType(), BpmnXMLConstants.ELEMENT_SEQUENCE_FLOW)); // 特殊:会签情况下,会有部分已完成(审批)、部分未完成(待审批),此时需要 finishedTaskActivityIds 移除掉 unfinishedTaskActivityIds.removeAll(finishedTaskActivityIds); // 特殊:如果流程实例被拒绝,则需要计算是哪个活动节点。 // 注意,只取最后一个。因为会存在多次拒绝的情况,拒绝驳回到指定节点 Set<String> rejectTaskActivityIds = CollUtil.newHashSet(); if (BpmProcessInstanceStatusEnum.isRejectStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) { tasks.stream() .filter(task -> BpmTaskStatusEnum.isRejectStatus(FlowableUtils.getTaskStatus(task))) .max(Comparator.comparing(HistoricTaskInstance::getEndTime)) .ifPresent(reject -> rejectTaskActivityIds.add(reject.getTaskDefinitionKey())); finishedTaskActivityIds.removeAll(rejectTaskActivityIds); } // 2.2 拼接基础信息 Set<Long> userIds = BpmProcessInstanceConvert.INSTANCE.parseUserIds02(processInstance, tasks); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); return BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceBpmnModelView(processInstance, tasks, bpmnModel, simpleModel, unfinishedTaskActivityIds, finishedTaskActivityIds, finishedSequenceFlowActivityIds, rejectTaskActivityIds, userMap, deptMap); } // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getStartUserSelectAssignees()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getStartUserSelectAssignees()); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map<String, Object> variables, String businessKey, Map<String, List<Long>> startUserSelectAssignees) { // 1.1 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(definition.getId()); if (processDefinitionInfo == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } // 1.2 校验是否能够发起 if (!processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId)) { throw exception(PROCESS_INSTANCE_START_USER_CAN_START); } // 1.3 校验发起人自选审批人 validateStartUserSelectAssignees(definition, startUserSelectAssignees); // 2. 创建流程实例 if (variables == null) { variables = new HashMap<>(); } FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用 variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, userId); // 设置流程变量,发起人 ID variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中 BpmProcessInstanceStatusEnum.RUNNING.getStatus()); if (CollUtil.isNotEmpty(startUserSelectAssignees)) { variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees); } ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); return instance.getId(); } private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) { // 1. 获得发起人自选审批人的 UserTask/ServiceTask 列表 BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId()); List<Task> tasks = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectTaskList(bpmnModel); if (CollUtil.isEmpty(tasks)) { return; } // 2. 校验发起人自选审批人的审批人和抄送人是否都配置了 tasks.forEach(task -> { List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(task.getId()) : null; if (CollUtil.isEmpty(assignees)) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, task.getName()); } Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees); assignees.forEach(assignee -> { if (userMap.get(assignee) == null) { throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, task.getName(), assignee); } }); }); } @Override public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 1.2 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 2. 取消流程 updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason())); } @Override public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 2. 取消流程 AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); updateProcessInstanceCancel(cancelReqVO.getId(), BpmReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason())); } private void updateProcessInstanceCancel(String id, String reason) { // 1. 更新流程实例 status runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.CANCEL.getStatus()); runtimeService.setVariable(id, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, reason); // 2. 结束流程 taskService.moveTaskToEnd(id); } @Override public void updateProcessInstanceReject(ProcessInstance processInstance, String reason) { runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus()); runtimeService.setVariable(processInstance.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON, BpmReasonEnum.REJECT_TASK.format(reason)); } // ========== Event 事件相关方法 ========== @Override public void processProcessInstanceCompleted(ProcessInstance instance) { // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号 FlowableUtils.execute(instance.getTenantId(), () -> { // 1.1 获取当前状态 Integer status = (Integer) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS); String reason = (String) instance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON); // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态 // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了 if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) { status = BpmProcessInstanceStatusEnum.APPROVE.getStatus(); runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status); } // 2. 发送对应的消息通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance)); } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) { messageService.sendMessageWhenProcessInstanceReject( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); } // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); }); } } \ No newline at end of file diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskService.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskService.java index e5015c0..1a03a1b 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskService.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskService.java @@ -3,8 +3,11 @@ import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.collection.CollectionUtils; import com.iailab.module.bpm.controller.admin.task.vo.task.*; +import com.iailab.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.task.api.Task; +import org.flowable.task.api.TaskInfo; import org.flowable.task.api.history.HistoricTaskInstance; import javax.validation.Valid; @@ -16,9 +19,11 @@ * 流程任务实例 Service 接口 * * @author jason - * @author iailab + * @author 芋道源码 */ public interface BpmTaskService { + + // ========== Query 查询相关方法 ========== /** * 获得待办的流程任务分页 @@ -28,6 +33,15 @@ * @return 流程任务分页 */ PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO); + + /** + * 获得用户在指定流程下,首个需要处理(待办)的任务 + * + * @param userId 用户编号 + * @param processInstanceId 流程实例编号 + * @return 待办任务 + */ + BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId); /** * 获得已办的流程任务分页 @@ -70,9 +84,88 @@ * 获得指定流程实例的流程任务列表,包括所有状态的 * * @param processInstanceId 流程实例的编号 + * @param asc 是否升序 * @return 流程任务列表 */ - List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId); + List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc); + + /** + * 获取任务 + * + * @param id 任务编号 + * @return 任务 + */ + Task getTask(String id); + + /** + * 获取历史任务 + * + * @param id 任务编号 + * @return 历史任务 + */ + HistoricTaskInstance getHistoricTask(String id); + + /** + * 获取历史任务列表 + * + * @param taskIds 任务编号集合 + * @return 历史任务列表 + */ + List<HistoricTaskInstance> getHistoricTasks(Collection<String> taskIds); + + /** + * 根据条件查询正在进行中的任务 + * + * @param processInstanceId 流程实例编号,不允许为空 + * @param assigned 是否分配了审批人,允许空 + * @param taskDefineKey 任务定义 Key,允许空 + */ + List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, + Boolean assigned, + String taskDefineKey); + + /** + * 获取当前任务的可退回的 UserTask 集合 + * + * @param id 当前的任务 ID + * @return 可以退回的节点列表 + */ + List<UserTask> getUserTaskListByReturn(String id); + + /** + * 获取指定任务的子任务列表(多层) + * + * @param parentTaskId 父任务 ID + * @param tasks 任务列表 + * @return 子任务列表 + */ + <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks); + + /** + * 获取指定任务的子任务列表 + * + * @param parentTaskId 父任务ID + * @return 子任务列表 + */ + List<Task> getTaskListByParentTaskId(String parentTaskId); + + /** + * 获得指定流程实例的活动实例列表 + * + * @param processInstanceId 流程实例的编号 + * @return 活动实例列表 + */ + List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId); + + /** + * 获得执行编号对应的活动实例 + * + * @param executionId 执行编号 + * @return 活动实例 + */ + List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId); + + // ========== Update 写入相关方法 ========== /** * 通过任务 @@ -99,47 +192,17 @@ void transferTask(Long userId, BpmTaskTransferReqVO reqVO); /** - * 更新 Task 状态,在创建时 + * 将指定流程实例的、进行中的流程任务,移动到结束节点 * - * @param task 任务实体 + * @param processInstanceId 流程编号 */ - void updateTaskStatusWhenCreated(Task task); + void moveTaskToEnd(String processInstanceId); /** - * 更新 Task 状态,在取消时 - * - * @param taskId 任务的编号 - */ - void updateTaskStatusWhenCanceled(String taskId); - - /** - * 更新 Task 拓展记录,并发送通知 - * - * @param task 任务实体 - */ - void updateTaskExtAssign(Task task); - - /** - * 获取任务 - * - * @param id 任务编号 - * @return 任务 - */ - Task getTask(String id); - - /** - * 获取当前任务的可回退的 UserTask 集合 - * - * @param id 当前的任务 ID - * @return 可以回退的节点列表 - */ - List<UserTask> getUserTaskListByReturn(String id); - - /** - * 将任务回退到指定的 targetDefinitionKey 位置 + * 将任务退回到指定的 targetDefinitionKey 位置 * * @param userId 用户编号 - * @param reqVO 回退的任务key和当前所在的任务ID + * @param reqVO 退回的任务key和当前所在的任务ID */ void returnTask(Long userId, BpmTaskReturnReqVO reqVO); @@ -168,19 +231,48 @@ void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO); /** - * 获取指定任务的子任务列表 + * 抄送任务 * - * @param parentTaskId 父任务ID - * @return 子任务列表 + * @param userId 用户编号 + * @param reqVO 通过请求 */ - List<Task> getTaskListByParentTaskId(String parentTaskId); + void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO); + + // ========== Event 事件相关方法 ========== /** - * 通过任务 ID,查询任务名 Map + * 处理 Task 创建事件,目前是 + * <p> + * 1. 更新它的状态为审批中 + * 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 + * <p> + * 注意:它的触发时机,晚于 {@link #processTaskAssigned(Task)} 之后 * - * @param taskIds 任务 ID - * @return 任务 ID 与名字的 Map + * @param task 任务实体 */ - Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds); + void processTaskCreated(Task task); + + /** + * 处理 Task 取消事件,目前是更新它的状态为已取消 + * + * @param taskId 任务的编号 + */ + void processTaskCanceled(String taskId); + + /** + * 处理 Task 设置审批人事件,目前是发送审批消息 + * + * @param task 任务实体 + */ + void processTaskAssigned(Task task); + + /** + * 处理 Task 审批超时事件,可能会处理多个当前审批中的任务 + * + * @param processInstanceId 流程示例编号 + * @param taskDefineKey 任务 Key + * @param handlerType 处理类型,参见 {@link BpmUserTaskTimeoutHandlerTypeEnum} + */ + void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType); } diff --git a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java index 8014f5e..2a6a3a1 100644 --- a/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/iailab-module-bpm/iailab-module-bpm-biz/src/main/java/com/iailab/module/bpm/service/task/BpmTaskServiceImpl.java @@ -1,40 +1,47 @@ package com.iailab.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.iailab.framework.common.pojo.CommonResult; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.*; +import cn.hutool.extra.spring.SpringUtil; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.number.NumberUtils; +import com.iailab.framework.common.util.object.ObjectUtils; import com.iailab.framework.common.util.object.PageUtils; -import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.framework.web.core.util.WebFrameworkUtils; import com.iailab.module.bpm.controller.admin.task.vo.task.*; import com.iailab.module.bpm.convert.task.BpmTaskConvert; +import com.iailab.module.bpm.enums.definition.*; import com.iailab.module.bpm.enums.task.BpmCommentTypeEnum; -import com.iailab.module.bpm.enums.task.BpmDeleteReasonEnum; +import com.iailab.module.bpm.enums.task.BpmReasonEnum; import com.iailab.module.bpm.enums.task.BpmTaskSignTypeEnum; import com.iailab.module.bpm.enums.task.BpmTaskStatusEnum; -import com.iailab.module.bpm.framework.flowable.core.enums.BpmConstants; +import com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; +import com.iailab.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import com.iailab.module.bpm.framework.flowable.core.util.FlowableUtils; import com.iailab.module.bpm.service.definition.BpmModelService; +import com.iailab.module.bpm.service.definition.BpmProcessDefinitionService; import com.iailab.module.bpm.service.message.BpmMessageService; +import com.iailab.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; +import com.iailab.module.system.api.dept.DeptApi; +import com.iailab.module.system.api.dept.dto.DeptRespDTO; import com.iailab.module.system.api.user.AdminUserApi; import com.iailab.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; +import org.flowable.engine.history.HistoricActivityInstance; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.DelegationState; import org.flowable.task.api.Task; +import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskQuery; import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.task.api.history.HistoricTaskInstanceQuery; @@ -44,7 +51,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.Assert; import javax.annotation.Resource; import javax.validation.Valid; @@ -54,11 +60,12 @@ import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.framework.common.util.collection.CollectionUtils.*; import static com.iailab.module.bpm.enums.ErrorCodeConstants.*; +import static com.iailab.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG; /** * 流程任务实例 Service 实现类 * - * @author iailab + * @author 芋道源码 * @author jason */ @Slf4j @@ -77,14 +84,20 @@ @Resource private BpmProcessInstanceService processInstanceService; @Resource + private BpmProcessDefinitionService bpmProcessDefinitionService; + @Resource private BpmProcessInstanceCopyService processInstanceCopyService; @Resource - private BpmModelService bpmModelService; + private BpmModelService modelService; @Resource private BpmMessageService messageService; @Resource private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + // ========== Query 查询相关方法 ========== @Override public PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageVO) { @@ -98,7 +111,7 @@ } if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); - taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1])); + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); } long count = taskQuery.count(); if (count == 0) { @@ -106,6 +119,41 @@ } List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); return new PageResult<>(tasks, count); + } + + @Override + public BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId) { + if (processInstanceId == null) { + return null; + } + // 1. 查询所有任务 + List<Task> tasks = taskService.createTaskQuery() + .active() + .processInstanceId(processInstanceId) + .includeTaskLocalVariables() + .includeProcessVariables() + .orderByTaskCreateTime().asc() // 按创建时间升序 + .list(); + if (CollUtil.isEmpty(tasks)) { + return null; + } + + // 2.1 查询我的首个任务 + Task todoTask = CollUtil.findOne(tasks, task -> { + return isAssignUserTask(userId, task) // 当前用户为审批人 + || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签) + }); + if (todoTask == null) { + return null; + } + // 2.2 查询该任务的子任务 + List<Task> childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), tasks); + + // 3. 转换返回 + BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId()); + Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting( + bpmnModel, todoTask.getTaskDefinitionKey()); + return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting); } @Override @@ -120,7 +168,7 @@ } if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); - taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1])); + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); } // 执行查询 long count = taskQuery.count(); @@ -142,7 +190,7 @@ } if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) { taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0])); - taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[1])); + taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1])); } // 执行查询 long count = taskQuery.count(); @@ -162,17 +210,246 @@ } @Override - public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId) { - List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery() + public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc) { + HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery() .includeTaskLocalVariables() - .processInstanceId(processInstanceId) - .orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序 - .list(); + .processInstanceId(processInstanceId); + if (Boolean.TRUE.equals(asc)) { + query.orderByHistoricTaskInstanceStartTime().asc(); + } else { + query.orderByHistoricTaskInstanceStartTime().desc(); + } + return query.list(); + } + + /** + * 校验任务是否存在,并且是否是分配给自己的任务 + * + * @param userId 用户 id + * @param taskId task id + */ + private Task validateTask(Long userId, String taskId) { + Task task = validateTaskExist(taskId); + // 为什么判断 assignee 非空的情况下? + // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过 + if (StrUtil.isNotBlank(task.getAssignee()) + && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) { + throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); + } + return task; + } + + private Task validateTaskExist(String id) { + Task task = getTask(id); + if (task == null) { + throw exception(TASK_NOT_EXISTS); + } + return task; + } + + @Override + public Task getTask(String id) { + return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); + } + + @Override + public HistoricTaskInstance getHistoricTask(String id) { + return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); + } + + @Override + public List<HistoricTaskInstance> getHistoricTasks(Collection<String> taskIds) { + return historyService.createHistoricTaskInstanceQuery().taskIds(taskIds).includeTaskLocalVariables().list(); + } + + @Override + public List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId, Boolean assigned, String defineKey) { + Assert.notNull(processInstanceId, "processInstanceId 不能为空"); + TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId).active() + .includeTaskLocalVariables(); + if (BooleanUtil.isTrue(assigned)) { + taskQuery.taskAssigned(); + } else if (BooleanUtil.isFalse(assigned)) { + taskQuery.taskUnassigned(); + } + if (StrUtil.isNotEmpty(defineKey)) { + taskQuery.taskDefinitionKey(defineKey); + } + return taskQuery.list(); + } + + @Override + public List<UserTask> getUserTaskListByReturn(String id) { + // 1.1 校验当前任务 task 存在 + Task task = validateTaskExist(id); + // 1.2 根据流程定义获取流程模型信息 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + if (source == null) { + throw exception(TASK_NOT_EXISTS); + } + + // 2.1 查询该任务的前置任务节点的 key 集合 + List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); + if (CollUtil.isEmpty(previousUserList)) { + return Collections.emptyList(); + } + // 2.2 过滤:只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 + previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); + return previousUserList; + } + + @Override + public <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks) { if (CollUtil.isEmpty(tasks)) { return Collections.emptyList(); } - return tasks; + Map<String, List<T>> parentTaskMap = convertMultiMap( + filterList(tasks, task -> StrUtil.isNotEmpty(task.getParentTaskId())), TaskInfo::getParentTaskId); + if (CollUtil.isEmpty(parentTaskMap)) { + return Collections.emptyList(); + } + + List<T> result = new ArrayList<>(); + // 1. 递归获取子级 + Stack<String> stack = new Stack<>(); + stack.push(parentTaskId); + // 2. 递归遍历 + for (int i = 0; i < Short.MAX_VALUE; i++) { + if (stack.isEmpty()) { + break; + } + // 2.1 获取子任务们 + String taskId = stack.pop(); + List<T> childTaskList = filterList(tasks, task -> StrUtil.equals(task.getParentTaskId(), taskId)); + // 2.2 如果非空,则添加到 stack 进一步递归 + if (CollUtil.isNotEmpty(childTaskList)) { + stack.addAll(convertList(childTaskList, TaskInfo::getId)); + result.addAll(childTaskList); + } + } + return result; } + + /** + * 获得所有子任务列表 + * + * @param parentTask 父任务 + * @return 所有子任务列表 + */ + private List<Task> getAllChildTaskList(Task parentTask) { + List<Task> result = new ArrayList<>(); + // 1. 递归获取子级 + Stack<Task> stack = new Stack<>(); + stack.push(parentTask); + // 2. 递归遍历 + for (int i = 0; i < Short.MAX_VALUE; i++) { + if (stack.isEmpty()) { + break; + } + // 2.1 获取子任务们 + Task task = stack.pop(); + List<Task> childTaskList = getTaskListByParentTaskId(task.getId()); + // 2.2 如果非空,则添加到 stack 进一步递归 + if (CollUtil.isNotEmpty(childTaskList)) { + stack.addAll(childTaskList); + result.addAll(childTaskList); + } + } + return result; + } + + @Override + public List<Task> getTaskListByParentTaskId(String parentTaskId) { + String tableName = managementService.getTableName(TaskEntity.class); + // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 + String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); + } + + /** + * 获取子任务个数 + * + * @param parentTaskId 父任务 ID + * @return 剩余子任务个数 + */ + private Long getTaskCountByParentTaskId(String parentTaskId) { + String tableName = managementService.getTableName(TaskEntity.class); + String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); + } + + /** + * 获得任务根任务的父任务编号 + * + * @param task 任务 + * @return 根任务的父任务编号 + */ + private String getTaskRootParentId(Task task) { + if (task == null || task.getParentTaskId() == null) { + return null; + } + for (int i = 0; i < Short.MAX_VALUE; i++) { + Task parentTask = getTask(task.getParentTaskId()); + if (parentTask == null) { + return null; + } + if (parentTask.getParentTaskId() == null) { + return parentTask.getId(); + } + task = parentTask; + } + throw new IllegalArgumentException(String.format("Task(%s) 层级过深,无法获取父节点编号", task.getId())); + } + + @Override + public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) { + return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) + .orderByHistoricActivityInstanceStartTime().asc().list(); + } + + @Override + public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) { + return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); + } + + /** + * 判断指定用户,是否是当前任务的审批人 + * + * @param userId 用户编号 + * @param task 任务 + * @return 是否 + */ + private boolean isAssignUserTask(Long userId, Task task) { + Long assignee = NumberUtil.parseLong(task.getAssignee(), null); + return ObjectUtil.equals(userId, assignee); + } + + /** + * 判断指定用户,是否是当前任务的拥有人 + * + * @param userId 用户编号 + * @param task 任务 + * @return 是否 + */ + private boolean isOwnerUserTask(Long userId, Task task) { + Long assignee = NumberUtil.parseLong(task.getOwner(), null); + return ObjectUtil.equal(userId, assignee); + } + + /** + * 判断指定用户,是否是当前任务的加签人 + * + * @param userId 用户 Id + * @param task 任务 + * @return 是否 + */ + private boolean isAddSignUserTask(Long userId, Task task) { + return (isAssignUserTask(userId, task) || isOwnerUserTask(userId, task)) + && BpmTaskSignTypeEnum.of(task.getScopeType()) != null; + } + + // ========== Update 写入相关方法 ========== @Override @Transactional(rollbackFor = Exception.class) @@ -183,11 +460,6 @@ ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); if (instance == null) { throw exception(PROCESS_INSTANCE_NOT_EXISTS); - } - - // 2. 抄送用户 - if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { - processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId()); } // 情况一:被委派的任务,不调用 complete 去完成任务 @@ -203,15 +475,17 @@ } // 情况三:审批普通的任务。大多数情况下,都是这样 - // 3.1 更新 task 状态、原因 + // 2.1 更新 task 状态、原因 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason()); - // 3.2 添加评论 + // 2.2 添加评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); - // 3.3 调用 BPM complete 去完成任务 + // 2.3 调用 BPM complete 去完成任务 // 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用 if (CollUtil.isNotEmpty(reqVO.getVariables())) { Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables()); + // 修改表单的值需要存储到 ProcessInstance 变量 + runtimeService.setVariables(task.getProcessInstanceId(), variables); taskService.complete(task.getId(), variables, true); } else { taskService.complete(task.getId()); @@ -245,7 +519,7 @@ /** * 如果父任务是有前后【加签】的任务,如果它【加签】出来的子任务都被处理,需要处理父任务: - * + * <p> * 1. 如果是【向前】加签,则需要重新激活父任务,让它可以被审批 * 2. 如果是【向后】加签,则需要完成父任务,让它完成审批 * @@ -278,11 +552,11 @@ taskService.resolveTask(parentTaskId); // 3.1.2 更新流程任务 status updateTaskStatus(parentTaskId, BpmTaskStatusEnum.RUNNING.getStatus()); - // 3.2 情况二:处理向【向后】加签 + // 3.2 情况二:处理向【向后】加签 } else if (BpmTaskSignTypeEnum.AFTER.getType().equals(scopeType)) { // 只有 parentTask 处于 APPROVING 的情况下,才可以继续 complete 完成 // 否则,一个未审批的 parentTask 任务,在加签出来的任务都被减签的情况下,就直接完成审批,这样会存在问题 - Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); + Integer status = (Integer) parentTask.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); if (ObjectUtil.notEqual(status, BpmTaskStatusEnum.APPROVING.getStatus())) { return; } @@ -327,135 +601,59 @@ throw exception(PROCESS_INSTANCE_NOT_EXISTS); } - // 2.1 更新流程实例为不通过 + // 2.1 更新流程任务为不通过 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.REJECT.getStatus(), reqVO.getReason()); - // 2.2 添加评论 + // 2.2 添加流程评论 taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); + // 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过 + // 疑问:为什么要标记未通过呢? + // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 + if (task.getParentTaskId() != null) { + String rootParentId = getTaskRootParentId(task); + updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(), + BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); + taskService.addComment(rootParentId, task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), + BpmCommentTypeEnum.REJECT.formatComment("加签任务不通过")); + } - // 3. 更新流程实例,审批不通过! - processInstanceService.updateProcessInstanceReject(instance.getProcessInstanceId(), reqVO.getReason()); + // 3. 根据不同的 RejectHandler 处理策略 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + // 3.1 情况一:驳回到指定的任务节点 + BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement); + if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) { + String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement); + Assert.notNull(returnTaskId, "退回的节点不能为空"); + returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId()) + .setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason())); + return; + } + // 3.2 情况二:直接结束,审批不通过 + processInstanceService.updateProcessInstanceReject(instance, reqVO.getReason()); // 标记不通过 + moveTaskToEnd(task.getProcessInstanceId()); // 结束流程 } /** * 更新流程任务的 status 状态 * - * @param id 任务编号 + * @param id 任务编号 * @param status 状态 */ private void updateTaskStatus(String id, Integer status) { - taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_STATUS, status); + taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_STATUS, status); } /** * 更新流程任务的 status 状态、reason 理由 * - * @param id 任务编号 + * @param id 任务编号 * @param status 状态 * @param reason 理由(审批通过、审批不通过的理由) */ private void updateTaskStatusAndReason(String id, Integer status, String reason) { updateTaskStatus(id, status); - taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_REASON, reason); - } - - /** - * 校验任务是否存在,并且是否是分配给自己的任务 - * - * @param userId 用户 id - * @param taskId task id - */ - private Task validateTask(Long userId, String taskId) { - Task task = validateTaskExist(taskId); - if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) { - throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); - } - return task; - } - - @Override - public void updateTaskStatusWhenCreated(Task task) { - Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); - if (status != null) { - log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); - return; - } - updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); - } - - @Override - public void updateTaskStatusWhenCanceled(String taskId) { - Task task = getTask(taskId); - // 1. 可能只是活动,不是任务,所以查询不到 - if (task == null) { - log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId); - return; - } - - // 2. 更新 task 状态 + 原因 - Integer status = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS); - if (BpmTaskStatusEnum.isEndStatus(status)) { - log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); - return; - } - updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmDeleteReasonEnum.CANCEL_BY_SYSTEM.getReason()); - // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 - } - - @Override - public void updateTaskExtAssign(Task task) { - // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - - @Override - public void afterCommit() { - if (StrUtil.isEmpty(task.getAssignee())) { - return; - } - ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); - AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); - messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); - } - - }); - } - - private Task validateTaskExist(String id) { - Task task = getTask(id); - if (task == null) { - throw exception(TASK_NOT_EXISTS); - } - return task; - } - - @Override - public Task getTask(String id) { - return taskService.createTaskQuery().taskId(id).includeTaskLocalVariables().singleResult(); - } - - private HistoricTaskInstance getHistoricTask(String id) { - return historyService.createHistoricTaskInstanceQuery().taskId(id).includeTaskLocalVariables().singleResult(); - } - - @Override - public List<UserTask> getUserTaskListByReturn(String id) { - // 1.1 校验当前任务 task 存在 - Task task = validateTaskExist(id); - // 1.2 根据流程定义获取流程模型信息 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId()); - FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); - if (source == null) { - throw exception(TASK_NOT_EXISTS); - } - - // 2.1 查询该任务的前置任务节点的 key 集合 - List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null); - if (CollUtil.isEmpty(previousUserList)) { - return Collections.emptyList(); - } - // 2.2 过滤:只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 - previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); - return previousUserList; + taskService.setVariableLocal(id, BpmnVariableConstants.TASK_VARIABLE_REASON, reason); } @Override @@ -470,12 +668,12 @@ FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(), reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId()); - // 2. 调用 Flowable 框架的回退逻辑 + // 2. 调用 Flowable 框架的退回逻辑 returnTask(task, targetElement, reqVO); } /** - * 回退流程节点时,校验目标任务节点是否可回退 + * 退回流程节点时,校验目标任务节点是否可退回 * * @param sourceKey 当前任务节点 Key * @param targetKey 目标任务节点 key @@ -484,7 +682,7 @@ */ private FlowElement validateTargetTaskCanReturn(String sourceKey, String targetKey, String processDefinitionId) { // 1.1 获取流程模型信息 - BpmnModel bpmnModel = bpmModelService.getBpmnModelByDefinitionId(processDefinitionId); + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); // 1.3 获取当前任务节点元素 FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, sourceKey); // 1.3 获取跳转的节点元素 @@ -493,7 +691,7 @@ throw exception(TASK_TARGET_NODE_NOT_EXISTS); } - // 2.2 只有串行可到达的节点,才可以回退。类似非串行、子流程无法退回 + // 2.2 只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 if (!BpmnModelUtils.isSequentialReachable(source, target, null)) { throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); } @@ -501,10 +699,10 @@ } /** - * 执行回退逻辑 + * 执行退回逻辑 * - * @param currentTask 当前回退的任务 - * @param targetElement 需要回退到的目标任务 + * @param currentTask 当前退回的任务 + * @param targetElement 需要退回到的目标任务 * @param reqVO 前端参数封装 */ public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { @@ -517,9 +715,9 @@ List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); - // 2. 给当前要被回退的 task 数组,设置回退意见 + // 2. 给当前要被退回的 task 数组,设置退回意见 taskList.forEach(task -> { - // 需要排除掉,不需要设置回退意见的任务 + // 需要排除掉,不需要设置退回意见的任务 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { return; } @@ -530,7 +728,11 @@ updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason()); }); - // 3. 执行驳回 + // 3. 设置流程变量节点驳回标记:用于驳回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略。导致自动通过 + runtimeService.setVariable(currentTask.getProcessInstanceId(), + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE); + + // 4. 执行驳回 runtimeService.createChangeActivityStateBuilder() .processInstanceId(currentTask.getProcessInstanceId()) .moveActivityIdsToSingleActivityId(returnTaskKeyList, // 当前要跳转的节点列表( 1 或多) @@ -562,9 +764,7 @@ taskService.setOwner(taskId, task.getAssignee()); // 3.2 执行委派,将任务委派给 delegateUser taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); - // 3.3 更新 task 状态。 - // 为什么不更新原因?因为原因目前主要给审批通过、不通过时使用 - updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus()); + // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中 } @Override @@ -591,6 +791,35 @@ // 3.2 执行转派(审批人),将任务转派给 assigneeUser // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!! taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString()); + } + + @Override + public void moveTaskToEnd(String processInstanceId) { + List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null); + if (CollUtil.isEmpty(taskList)) { + return; + } + + // 1. 其它未结束的任务,直接取消 + // 疑问:为什么不通过 updateTaskStatusWhenCanceled 监听取消,而是直接提前调用呢? + // 回答:详细见 updateTaskStatusWhenCanceled 的方法,加签的场景 + taskList.forEach(task -> { + Integer otherTaskStatus = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); + if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { + return; + } + processTaskCanceled(task.getId()); + }); + + // 2. 终止流程 + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(taskList.get(0).getProcessDefinitionId()); + List<String> activityIds = CollUtil.newArrayList(convertSet(taskList, Task::getTaskDefinitionKey)); + EndEvent endEvent = BpmnModelUtils.getEndEvent(bpmnModel); + Assert.notNull(endEvent, "结束节点不能未空"); + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstanceId) + .moveActivityIdsToSingleActivityId(activityIds, endEvent.getId()) + .changeState(); } @Override @@ -657,7 +886,7 @@ List<Long> currentAssigneeList = convertListByFlatMap(taskList, task -> // 需要考虑 owner 的情况,因为向后加签时,它暂时没 assignee 而是 owner Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner()))); if (CollUtil.containsAny(currentAssigneeList, reqVO.getUserIds())) { - List<AdminUserRespDTO> userList = adminUserApi.getUserList( CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())).getCheckedData(); + List<AdminUserRespDTO> userList = adminUserApi.getUserList(CollUtil.intersection(currentAssigneeList, reqVO.getUserIds())).getCheckedData(); throw exception(TASK_SIGN_CREATE_USER_REPEAT, String.join(",", convertList(userList, AdminUserRespDTO::getNickname))); } return taskEntity; @@ -666,8 +895,8 @@ /** * 创建加签子任务 * - * @param userIds 被加签的用户 ID - * @param taskEntity 被加签的任务 + * @param userIds 被加签的用户 ID + * @param taskEntity 被加签的任务 */ private void createSignTaskList(List<String> userIds, TaskEntityImpl taskEntity) { if (CollUtil.isEmpty(userIds)) { @@ -696,7 +925,7 @@ // 2.1 向前加签,设置审批人 if (BpmTaskSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) { task.setAssignee(assignee); - // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 + // 2.2 向后加签,设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 } else { task.setOwner(assignee); } @@ -742,6 +971,11 @@ handleParentTaskIfSign(task.getParentTaskId()); } + @Override + public void copyTask(Long userId, BpmTaskCopyReqVO reqVO) { + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId()); + } + /** * 校验任务是否能被减签 * @@ -763,61 +997,208 @@ return task; } - /** - * 获得所有子任务列表 - * - * @param parentTask 父任务 - * @return 所有子任务列表 - */ - private List<Task> getAllChildTaskList(Task parentTask) { - List<Task> result = new ArrayList<>(); - // 1. 递归获取子级 - Stack<Task> stack = new Stack<>(); - stack.push(parentTask); - // 2. 递归遍历 - for (int i = 0; i < Short.MAX_VALUE; i++) { - if (stack.isEmpty()) { - break; - } - // 2.1 获取子任务们 - Task task = stack.pop(); - List<Task> childTaskList = getTaskListByParentTaskId(task.getId()); - // 2.2 如果非空,则添加到 stack 进一步递归 - if (CollUtil.isNotEmpty(childTaskList)) { - stack.addAll(childTaskList); - result.addAll(childTaskList); - } - } - return result; - } + // ========== Event 事件相关方法 ========== @Override - public List<Task> getTaskListByParentTaskId(String parentTaskId) { - String tableName = managementService.getTableName(TaskEntity.class); - // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询 - String sql = "select ID_,NAME_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}"; - return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list(); + public void processTaskCreated(Task task) { + // 1. 设置为待办中 + Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); + if (status != null) { + log.error("[updateTaskStatusWhenCreated][taskId({}) 已经有状态({})]", task.getId(), status); + return; + } + updateTaskStatus(task.getId(), BpmTaskStatusEnum.RUNNING.getStatus()); + + // 2. 处理自动通过的情况,例如说:1)无审批人时,是否自动通过、不通过;2)非【人工审核】时,是否自动通过、不通过 + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (processInstance == null) { + log.error("[processTaskCreated][taskId({}) 没有找到流程实例]", task.getId()); + return; + } + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + Integer approveType = BpmnModelUtils.parseApproveType(userTaskElement); + Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(userTaskElement); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCompletion(int transactionStatus) { + // 特殊情况:部分情况下,TransactionSynchronizationManager 注册 afterCommit 监听时,不会被调用,但是 afterCompletion 可以 + // 例如说:第一个 task 就是配置【自动通过】或者【自动拒绝】时 + if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) { + return; + } + // TODO 芋艿:可以后续优化成 getSelf(); + // 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝 + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) { + // 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝 + if (!ObjectUtil.isAllEmpty(task.getAssignee(), task.getOwner())) { + return; + } + if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) { + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason())); + } else if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.REJECT.getType())) { + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_REJECT.getReason())); + } + // 特殊情况二:【自动审核】审批类型为自动通过、不通过 + } else { + if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType())) { + SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_APPROVE.getReason())); + } else if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) { + SpringUtil.getBean(BpmTaskService.class).rejectTask(null, new BpmTaskRejectReqVO() + .setId(task.getId()).setReason(BpmReasonEnum.APPROVE_TYPE_AUTO_REJECT.getReason())); + } + } + } + + }); } /** - * 获取子任务个数 - * - * @param parentTaskId 父任务 ID - * @return 剩余子任务个数 + * 重要补充说明:该方法目前主要有两个情况会调用到: + * <p> + * 1. 或签场景 + 审批通过:一个或签有多个审批时,如果 A 审批通过,其它或签 B、C 等任务会被 Flowable 自动删除,此时需要通过该方法更新状态为已取消 + * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 */ - private Long getTaskCountByParentTaskId(String parentTaskId) { - String tableName = managementService.getTableName(TaskEntity.class); - String sql = "SELECT COUNT(1) from " + tableName + " WHERE PARENT_TASK_ID_=#{parentTaskId}"; - return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count(); + @Override + public void processTaskCanceled(String taskId) { + Task task = getTask(taskId); + // 1. 可能只是活动,不是任务,所以查询不到 + if (task == null) { + log.error("[updateTaskStatusWhenCanceled][taskId({}) 任务不存在]", taskId); + return; + } + + // 2. 更新 task 状态 + 原因 + Integer status = (Integer) task.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_STATUS); + if (BpmTaskStatusEnum.isEndStatus(status)) { + log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); + return; + } + updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); + // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 } @Override - public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) { - if (CollUtil.isEmpty(taskIds)) { - return Collections.emptyMap(); + public void processTaskAssigned(Task task) { + // 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + if (StrUtil.isEmpty(task.getAssignee())) { + log.error("[processTaskAssigned][taskId({}) 没有分配到负责人]", task.getId()); + return; + } + ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); + if (processInstance == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程实例]", task.getId()); + return; + } + // 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理 + if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) { + // 判断是否为退回或者驳回:如果是退回或者驳回不走这个策略 + // TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识 + Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(), + String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class); + if (ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { + BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processInstance.getProcessDefinitionId()); + if (bpmnModel == null) { + log.error("[processTaskAssigned][taskId({}) 没有找到流程模型]", task.getId()); + return; + } + FlowElement userTaskElement = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); + Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(userTaskElement); + + // 情况一:自动跳过 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP.getReason())); + return; + } + // 情况二:转交给部门负责人审批 + if (ObjectUtils.equalsAny(assignStartUserHandlerType, + BpmUserTaskAssignStartUserHandlerTypeEnum.TRANSFER_DEPT_LEADER.getType())) { + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); + Assert.notNull(startUser, "提交人({})信息为空", processInstance.getStartUserId()); + DeptRespDTO dept = startUser.getDeptId() != null ? deptApi.getDept(startUser.getDeptId()).getCheckedData() : null; + Assert.notNull(dept, "提交人({})部门({})信息为空", processInstance.getStartUserId(), startUser.getDeptId()); + // 找不到部门负责人的情况下,自动审批通过 + // noinspection DataFlowIssue + if (dept.getLeaderUserId() == null) { + getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND.getReason())); + return; + } + // 找得到部门负责人的情况下,修改负责人 + if (ObjectUtil.notEqual(dept.getLeaderUserId(), startUser.getId())) { + getSelf().transferTask(Long.valueOf(task.getAssignee()), new BpmTaskTransferReqVO() + .setId(task.getId()).setAssigneeUserId(dept.getLeaderUserId()) + .setReason(BpmReasonEnum.ASSIGN_START_USER_TRANSFER_DEPT_LEADER.getReason())); + return; + } + // 如果部门负责人是自己,还是自己审批吧~ + } + } + } + + AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId())).getCheckedData(); + messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task)); + } + + }); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType) { + ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); + if (processInstance == null) { + log.error("[processTaskTimeout][processInstanceId({}) 没有找到流程实例]", processInstanceId); + return; } - List<Task> tasks = taskService.createTaskQuery().taskIds(taskIds).list(); - return convertMap(tasks, Task::getId, Task::getName); + List<Task> taskList = getRunningTaskListByProcessInstanceId(processInstanceId, true, taskDefineKey); + // TODO 优化:未来需要考虑加签的情况 + if (CollUtil.isEmpty(taskList)) { + log.error("[processTaskTimeout][processInstanceId({}) 定义Key({}) 没有找到任务]", processInstanceId, taskDefineKey); + return; + } + + taskList.forEach(task -> FlowableUtils.execute(task.getTenantId(), () -> { + // 情况一:自动提醒 + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REMINDER.getType())) { + messageService.sendMessageWhenTaskTimeout(new BpmMessageSendWhenTaskTimeoutReqDTO() + .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) + .setTaskId(task.getId()).setTaskName(task.getName()).setAssigneeUserId(Long.parseLong(task.getAssignee()))); + return; + } + + // 情况二:自动同意 + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.APPROVE.getType())) { + approveTask(Long.parseLong(task.getAssignee()), + new BpmTaskApproveReqVO().setId(task.getId()).setReason(BpmReasonEnum.TIMEOUT_APPROVE.getReason())); + return; + } + + // 情况三:自动拒绝 + if (Objects.equals(handlerType, BpmUserTaskTimeoutHandlerTypeEnum.REJECT.getType())) { + rejectTask(Long.parseLong(task.getAssignee()), + new BpmTaskRejectReqVO().setId(task.getId()).setReason(BpmReasonEnum.REJECT_TASK.getReason())); + } + })); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private BpmTaskServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); } } diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/dao/MmModelArithSettingsDao.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/dao/MmModelArithSettingsDao.java index 002bcd6..82b30a0 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/dao/MmModelArithSettingsDao.java +++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/dao/MmModelArithSettingsDao.java @@ -4,6 +4,7 @@ import com.iailab.framework.tenant.core.db.dynamic.TenantDS; import com.iailab.module.model.mcs.pre.entity.MmModelArithSettingsEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import java.util.List; import java.util.Map; @@ -30,4 +31,6 @@ * @param list */ void insertList(List<MmModelArithSettingsEntity> list); + + void updatePyFile(@Param("likeValue") String likeValue,@Param("value") String value); } diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmModelArithSettingsService.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmModelArithSettingsService.java index e26bfe7..dff36f4 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmModelArithSettingsService.java +++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/MmModelArithSettingsService.java @@ -13,4 +13,6 @@ void saveList(List<MmModelArithSettingsEntity> list); List<MmModelArithSettingsEntity> getByModelId(String modelId); + + void updatePyFile(String pyModule, String fileName); } diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmModelArithSettingsServiceImpl.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmModelArithSettingsServiceImpl.java index 8059a9f..fafb596 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmModelArithSettingsServiceImpl.java +++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/pre/service/impl/MmModelArithSettingsServiceImpl.java @@ -53,4 +53,9 @@ modelIdMap.put(modelId, list); return list; } + + @Override + public void updatePyFile(String pyModule, String fileName) { + baseMapper.updatePyFile(pyModule + "." + fileName.substring(0,fileName.lastIndexOf("_")),pyModule + "." + fileName); + } } diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/dao/StScheduleModelSettingDao.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/dao/StScheduleModelSettingDao.java index 6bd43c5..bb02cdc 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/dao/StScheduleModelSettingDao.java +++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/dao/StScheduleModelSettingDao.java @@ -4,6 +4,7 @@ import com.iailab.framework.tenant.core.db.dynamic.TenantDS; import com.iailab.module.model.mcs.sche.entity.StScheduleModelSettingEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; /** * @author PanZhibao @@ -13,4 +14,5 @@ @TenantDS @Mapper public interface StScheduleModelSettingDao extends BaseMapperX<StScheduleModelSettingEntity> { + void updatePyFile(@Param("likeValue") String likeValue, @Param("value") String value); } \ No newline at end of file diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleModelSettingService.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleModelSettingService.java index d396d0d..29590b0 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleModelSettingService.java +++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/StScheduleModelSettingService.java @@ -18,4 +18,6 @@ void deleteByModelId(String modelId); void saveList(String modelId, List<StScheduleModelSettingSaveReqVO> saveList); + + void updatePyFile(String pyModule, String fileName); } \ No newline at end of file diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleModelSettingServiceImpl.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleModelSettingServiceImpl.java index d21da80..c348b78 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleModelSettingServiceImpl.java +++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mcs/sche/service/impl/StScheduleModelSettingServiceImpl.java @@ -53,4 +53,9 @@ baseDao.insert(entity); } } + + @Override + public void updatePyFile(String pyModule, String fileName) { + baseDao.updatePyFile(pyModule + "." + fileName.substring(0,fileName.lastIndexOf("_")),pyModule + "." + fileName); + } } \ No newline at end of file diff --git a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java index 51e677b..f141bdf 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java +++ b/iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/model/mpk/service/impl/MpkFileServiceImpl.java @@ -14,6 +14,8 @@ import com.iailab.framework.security.core.util.SecurityFrameworkUtils; import com.iailab.framework.tenant.core.context.TenantContextHolder; import com.iailab.module.infra.api.config.ConfigApi; +import com.iailab.module.model.mcs.pre.service.MmModelArithSettingsService; +import com.iailab.module.model.mcs.sche.service.StScheduleModelSettingService; import com.iailab.module.model.mpk.common.MdkConstant; import com.iailab.module.model.mpk.common.utils.CmdUtils; import com.iailab.module.model.mpk.common.utils.DllUtils; @@ -67,6 +69,12 @@ @Autowired private ProjectPackageHistoryModelService projectPackageHistoryModelService; + + @Autowired + private MmModelArithSettingsService mmModelArithSettingsService; + + @Autowired + private StScheduleModelSettingService stScheduleModelSettingService; @Autowired private ConfigApi configApi; @@ -204,6 +212,11 @@ } } + // 修改预测项pyFile参数 + mmModelArithSettingsService.updatePyFile(dto.getPyModule(),fileName); + // 修改调度项pyFile参数 + stScheduleModelSettingService.updatePyFile(dto.getPyModule(),fileName); + MpkFileEntity entity = ConvertUtils.sourceToTarget(dto, MpkFileEntity.class); entity.setUpdater(SecurityFrameworkUtils.getLoginUserId()); entity.setUpdateDate(new Date()); diff --git a/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/MmModelArithSettings.xml b/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/MmModelArithSettings.xml index 822a969..1aa314a 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/MmModelArithSettings.xml +++ b/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/MmModelArithSettings.xml @@ -2,6 +2,9 @@ <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.iailab.module.model.mcs.pre.dao.MmModelArithSettingsDao"> + <update id="updatePyFile"> + update t_mm_model_arith_settings set value = #{value} where `key` = 'pyFile' and `value` like CONCAT(#{likeValue},'%') + </update> <select id="getMmModelArithSettings" resultType="com.iailab.module.model.mcs.pre.entity.MmModelArithSettingsEntity" parameterType="map"> SELECT diff --git a/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/StScheduleModelSettingDao.xml b/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/StScheduleModelSettingDao.xml new file mode 100644 index 0000000..48e93df --- /dev/null +++ b/iailab-module-model/iailab-module-model-biz/src/main/resources/mapper/mcs/StScheduleModelSettingDao.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > +<mapper namespace="com.iailab.module.model.mcs.sche.dao.StScheduleModelSettingDao"> + <update id="updatePyFile"> + update t_st_schedule_model_setting set value = #{value} where `key` = 'pyFile' and `value` like CONCAT(#{likeValue},'%') + </update> +</mapper> \ No newline at end of file diff --git a/iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm b/iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm index 1025477..ddf78cf 100644 --- a/iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm +++ b/iailab-module-model/iailab-module-model-biz/src/main/resources/template/cpp.vm @@ -23,7 +23,6 @@ jstring keyJString = env->NewStringUTF("pyFile"); jobject javaValueObj = env->CallObjectMethod(settings, getMID, keyJString); const char* strValue = env->GetStringUTFChars((jstring)javaValueObj, NULL); - cout << strValue << endl; PyObject* pModule = create_py_module(strValue); /*PyObject* pModule = create_py_module("${pyModule}.${pyName}");*/ diff --git a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java index 6d58734..81dc938 100644 --- a/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java +++ b/iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/app/AppServiceImpl.java @@ -220,6 +220,7 @@ LambdaQueryWrapperX<MenuDO> menuWrapper = new LambdaQueryWrapperX<>(); menuWrapper.eq(MenuDO::getAppId, app.getId()); menuWrapper.eq(MenuDO::getType, MenuTypeEnum.DIR.getType()); + menuWrapper.eq(MenuDO::getParentId, 0); MenuDO menu = menuMapper.selectOne(menuWrapper); TenantDO tenantDO = tenantMapper.selectById(app.getTenantId()); if(ObjectUtils.isNotEmpty(menu) && ObjectUtils.isNotEmpty(tenantDO)) { -- Gitblit v1.9.3