Commit 3d1b66d40b57b636da7fb1fcd06be5d07aa58488
Committed by
Gitee
!1556 feat: 审批,拒绝任务时允许添加附件
Merge pull request !1556 from Jason/master-jdk17-bpm-bug-fix
Showing
9 changed files
with
106 additions
and
31 deletions
yudao-dependencies/pom.xml
| @@ -46,7 +46,7 @@ | @@ -46,7 +46,7 @@ | ||
| 46 | <jedis-mock.version>1.1.12</jedis-mock.version> | 46 | <jedis-mock.version>1.1.12</jedis-mock.version> |
| 47 | <mockito-inline.version>5.2.0</mockito-inline.version> | 47 | <mockito-inline.version>5.2.0</mockito-inline.version> |
| 48 | <!-- Bpm 工作流相关 --> | 48 | <!-- Bpm 工作流相关 --> |
| 49 | - <flowable.version>7.2.0</flowable.version> | 49 | + <flowable.version>8.0.0</flowable.version> |
| 50 | <!-- 工具类相关 --> | 50 | <!-- 工具类相关 --> |
| 51 | <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version> | 51 | <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version> |
| 52 | <jsoup.version>1.22.2</jsoup.version> | 52 | <jsoup.version>1.22.2</jsoup.version> |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
| @@ -107,6 +107,9 @@ public class BpmApprovalDetailRespVO { | @@ -107,6 +107,9 @@ public class BpmApprovalDetailRespVO { | ||
| 107 | @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") | 107 | @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") |
| 108 | private String signPicUrl; | 108 | private String signPicUrl; |
| 109 | 109 | ||
| 110 | + @Schema(description = "附件", example = "[https://test.yudao.iocoder.cn/20260609/test.txt]") | ||
| 111 | + private List<String> attachments; | ||
| 112 | + | ||
| 110 | } | 113 | } |
| 111 | 114 | ||
| 112 | } | 115 | } |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
| @@ -21,6 +21,9 @@ public class BpmTaskApproveReqVO { | @@ -21,6 +21,9 @@ public class BpmTaskApproveReqVO { | ||
| 21 | @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") | 21 | @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") |
| 22 | private String signPicUrl; | 22 | private String signPicUrl; |
| 23 | 23 | ||
| 24 | + @Schema(description = "附件", example = "[https://test.yudao.iocoder.cn/20260609/test.txt]") | ||
| 25 | + private List<String> attachments; | ||
| 26 | + | ||
| 24 | @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) | 27 | @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) |
| 25 | private Map<String, Object> variables; | 28 | private Map<String, Object> variables; |
| 26 | 29 |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java
| @@ -5,6 +5,8 @@ import lombok.Data; | @@ -5,6 +5,8 @@ import lombok.Data; | ||
| 5 | 5 | ||
| 6 | import jakarta.validation.constraints.NotEmpty; | 6 | import jakarta.validation.constraints.NotEmpty; |
| 7 | 7 | ||
| 8 | +import java.util.List; | ||
| 9 | + | ||
| 8 | @Schema(description = "管理后台 - 不通过流程任务的 Request VO") | 10 | @Schema(description = "管理后台 - 不通过流程任务的 Request VO") |
| 9 | @Data | 11 | @Data |
| 10 | public class BpmTaskRejectReqVO { | 12 | public class BpmTaskRejectReqVO { |
| @@ -16,4 +18,7 @@ public class BpmTaskRejectReqVO { | @@ -16,4 +18,7 @@ public class BpmTaskRejectReqVO { | ||
| 16 | @Schema(description = "审批意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "不错不错!") | 18 | @Schema(description = "审批意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "不错不错!") |
| 17 | private String reason; | 19 | private String reason; |
| 18 | 20 | ||
| 21 | + @Schema(description = "附件", example = "[https://test.yudao.iocoder.cn/20260609/test.txt]") | ||
| 22 | + private List<String> attachments; | ||
| 23 | + | ||
| 19 | } | 24 | } |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
| @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | ||
| 9 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; | 9 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 10 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | 10 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 11 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 11 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 12 | +import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; | ||
| 12 | import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; | 13 | import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| 13 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; | 14 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; |
| 14 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; | 15 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; |
| @@ -21,7 +22,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | @@ -21,7 +22,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO; | ||
| 21 | import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert; | 22 | import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert; |
| 22 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; | 23 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 23 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; | 24 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 24 | -import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; | ||
| 25 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; | 25 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| 26 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; | 26 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| 27 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; | 27 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| @@ -34,6 +34,7 @@ import org.flowable.bpmn.model.BpmnModel; | @@ -34,6 +34,7 @@ import org.flowable.bpmn.model.BpmnModel; | ||
| 34 | import org.flowable.engine.history.HistoricProcessInstance; | 34 | import org.flowable.engine.history.HistoricProcessInstance; |
| 35 | import org.flowable.engine.repository.ProcessDefinition; | 35 | import org.flowable.engine.repository.ProcessDefinition; |
| 36 | import org.flowable.engine.runtime.ProcessInstance; | 36 | import org.flowable.engine.runtime.ProcessInstance; |
| 37 | +import org.flowable.engine.task.Attachment; | ||
| 37 | import org.flowable.task.api.Task; | 38 | import org.flowable.task.api.Task; |
| 38 | import org.flowable.task.api.history.HistoricTaskInstance; | 39 | import org.flowable.task.api.history.HistoricTaskInstance; |
| 39 | import org.mapstruct.Mapper; | 40 | import org.mapstruct.Mapper; |
| @@ -82,7 +83,7 @@ public interface BpmProcessInstanceConvert { | @@ -82,7 +83,7 @@ public interface BpmProcessInstanceConvert { | ||
| 82 | if (CollUtil.isNotEmpty(respVO.getTasks())) { | 83 | if (CollUtil.isNotEmpty(respVO.getTasks())) { |
| 83 | respVO.getTasks().forEach(task -> { | 84 | respVO.getTasks().forEach(task -> { |
| 84 | AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee()); | 85 | AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee()); |
| 85 | - if (assigneeUser!= null) { | 86 | + if (assigneeUser != null) { |
| 86 | task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class)); | 87 | task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class)); |
| 87 | MapUtils.findAndThen(deptMap, assigneeUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); | 88 | MapUtils.findAndThen(deptMap, assigneeUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); |
| 88 | } | 89 | } |
| @@ -137,10 +138,10 @@ public interface BpmProcessInstanceConvert { | @@ -137,10 +138,10 @@ public interface BpmProcessInstanceConvert { | ||
| 137 | 138 | ||
| 138 | default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) { | 139 | default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) { |
| 139 | return new BpmMessageSendWhenProcessInstanceRejectReqDTO() | 140 | return new BpmMessageSendWhenProcessInstanceRejectReqDTO() |
| 140 | - .setProcessInstanceName(instance.getName()) | ||
| 141 | - .setProcessInstanceId(instance.getId()) | ||
| 142 | - .setReason(reason) | ||
| 143 | - .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); | 141 | + .setProcessInstanceName(instance.getName()) |
| 142 | + .setProcessInstanceId(instance.getId()) | ||
| 143 | + .setReason(reason) | ||
| 144 | + .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); | ||
| 144 | } | 145 | } |
| 145 | 146 | ||
| 146 | default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, | 147 | default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, |
| @@ -183,8 +184,8 @@ public interface BpmProcessInstanceConvert { | @@ -183,8 +184,8 @@ public interface BpmProcessInstanceConvert { | ||
| 183 | } | 184 | } |
| 184 | 185 | ||
| 185 | default UserSimpleBaseVO buildUser(Long userId, | 186 | default UserSimpleBaseVO buildUser(Long userId, |
| 186 | - Map<Long, AdminUserRespDTO> userMap, | ||
| 187 | - Map<Long, DeptRespDTO> deptMap) { | 187 | + Map<Long, AdminUserRespDTO> userMap, |
| 188 | + Map<Long, DeptRespDTO> deptMap) { | ||
| 188 | if (userId == null) { | 189 | if (userId == null) { |
| 189 | return null; | 190 | return null; |
| 190 | } | 191 | } |
| @@ -200,13 +201,14 @@ public interface BpmProcessInstanceConvert { | @@ -200,13 +201,14 @@ public interface BpmProcessInstanceConvert { | ||
| 200 | return userVO; | 201 | return userVO; |
| 201 | } | 202 | } |
| 202 | 203 | ||
| 203 | - default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) { | 204 | + default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task, List<Attachment> attachments) { |
| 204 | if (task == null) { | 205 | if (task == null) { |
| 205 | return null; | 206 | return null; |
| 206 | } | 207 | } |
| 207 | return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class) | 208 | return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class) |
| 208 | .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)) | 209 | .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)) |
| 209 | - .setSignPicUrl(FlowableUtils.getTaskSignPicUrl(task)); | 210 | + .setSignPicUrl(FlowableUtils.getTaskSignPicUrl(task)) |
| 211 | + .setAttachments(convertList(attachments, Attachment::getUrl)); | ||
| 210 | } | 212 | } |
| 211 | 213 | ||
| 212 | default Set<Long> parseUserIds(HistoricProcessInstance processInstance, | 214 | default Set<Long> parseUserIds(HistoricProcessInstance processInstance, |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmAttachmentTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.enums.task; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import lombok.Getter; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 附件类型枚举 | ||
| 8 | + * | ||
| 9 | + * @author jason | ||
| 10 | + */ | ||
| 11 | +@Getter | ||
| 12 | +@AllArgsConstructor | ||
| 13 | +public enum BpmAttachmentTypeEnum { | ||
| 14 | + | ||
| 15 | + TASK_ATTACHMENT("1", "用户任务附件"); | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 操作类型 | ||
| 19 | + * <p> | ||
| 20 | + * 由于 BPM attachment 类型为 String,所以这里就不使用 Integer | ||
| 21 | + */ | ||
| 22 | + private final String type; | ||
| 23 | + /** | ||
| 24 | + * 操作名字 | ||
| 25 | + */ | ||
| 26 | + private final String name; | ||
| 27 | +} |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
| @@ -25,6 +25,7 @@ import cn.iocoder.yudao.module.bpm.dal.redis.BpmProcessIdRedisDAO; | @@ -25,6 +25,7 @@ import cn.iocoder.yudao.module.bpm.dal.redis.BpmProcessIdRedisDAO; | ||
| 25 | import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; | 25 | import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; |
| 26 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; | 26 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; |
| 27 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum; | 27 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum; |
| 28 | +import cn.iocoder.yudao.module.bpm.enums.task.BpmAttachmentTypeEnum; | ||
| 28 | import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; | 29 | import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; |
| 29 | import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; | 30 | import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; |
| 30 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; | 31 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| @@ -57,6 +58,7 @@ import org.flowable.engine.repository.ProcessDefinition; | @@ -57,6 +58,7 @@ import org.flowable.engine.repository.ProcessDefinition; | ||
| 57 | import org.flowable.engine.runtime.Execution; | 58 | import org.flowable.engine.runtime.Execution; |
| 58 | import org.flowable.engine.runtime.ProcessInstance; | 59 | import org.flowable.engine.runtime.ProcessInstance; |
| 59 | import org.flowable.engine.runtime.ProcessInstanceBuilder; | 60 | import org.flowable.engine.runtime.ProcessInstanceBuilder; |
| 61 | +import org.flowable.engine.task.Attachment; | ||
| 60 | import org.flowable.task.api.Task; | 62 | import org.flowable.task.api.Task; |
| 61 | import org.flowable.task.api.history.HistoricTaskInstance; | 63 | import org.flowable.task.api.history.HistoricTaskInstance; |
| 62 | import org.springframework.context.annotation.Lazy; | 64 | import org.springframework.context.annotation.Lazy; |
| @@ -404,8 +406,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -404,8 +406,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 404 | // 遍历 tasks 列表,只处理已结束的 UserTask | 406 | // 遍历 tasks 列表,只处理已结束的 UserTask |
| 405 | // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 | 407 | // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 |
| 406 | List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); | 408 | List<HistoricTaskInstance> endTasks = filterList(tasks, task -> task.getEndTime() != null); |
| 409 | + // 获取已完成节点的附件 | ||
| 410 | + Set<String> endTaskIds = convertSet(endTasks, HistoricTaskInstance::getId); | ||
| 411 | + List<Attachment> attachments = taskService.getAttachments(historicProcessInstance.getId(), endTaskIds, BpmAttachmentTypeEnum.TASK_ATTACHMENT); | ||
| 412 | + Map<String, List<Attachment>> taskAttachmentMap = convertMultiMap(attachments, Attachment::getTaskId); | ||
| 407 | List<ActivityNode> approvalNodes = convertList(endTasks, task -> { | 413 | List<ActivityNode> approvalNodes = convertList(endTasks, task -> { |
| 408 | FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); | 414 | FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| 415 | + List<Attachment> taskAttachments = taskAttachmentMap.get(task.getId()); | ||
| 409 | ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) | 416 | ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) |
| 410 | .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) | 417 | .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) |
| 411 | ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() | 418 | ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() |
| @@ -414,7 +421,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -414,7 +421,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 414 | .setStatus(getEndActivityNodeStatus(task)) | 421 | .setStatus(getEndActivityNodeStatus(task)) |
| 415 | .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) | 422 | .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) |
| 416 | .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) | 423 | .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) |
| 417 | - .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); | 424 | + .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task, taskAttachments))); |
| 418 | // 如果是取消状态,则跳过 | 425 | // 如果是取消状态,则跳过 |
| 419 | if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) { | 426 | if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) { |
| 420 | return null; | 427 | return null; |
| @@ -528,14 +535,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | @@ -528,14 +535,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService | ||
| 528 | if (task == null) { | 535 | if (task == null) { |
| 529 | continue; | 536 | continue; |
| 530 | } | 537 | } |
| 531 | - activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); | 538 | + activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task, null)); |
| 532 | // 加签子任务,需要过滤掉已经完成的加签子任务 | 539 | // 加签子任务,需要过滤掉已经完成的加签子任务 |
| 533 | List<HistoricTaskInstance> childrenTasks = filterList( | 540 | List<HistoricTaskInstance> childrenTasks = filterList( |
| 534 | taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), | 541 | taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), |
| 535 | childTask -> childTask.getEndTime() == null); | 542 | childTask -> childTask.getEndTime() == null); |
| 536 | if (CollUtil.isNotEmpty(childrenTasks)) { | 543 | if (CollUtil.isNotEmpty(childrenTasks)) { |
| 537 | activityNode.getTasks().addAll( | 544 | activityNode.getTasks().addAll( |
| 538 | - convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo)); | 545 | + convertList(childrenTasks, item->BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(item, null))); |
| 539 | } | 546 | } |
| 540 | } | 547 | } |
| 541 | // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task | 548 | // 处理每个任务的 candidateUsers 属性:如果是依次审批,需要预测它的后续审批人。因为 Task 是审批完一个,创建一个新的 Task |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
| @@ -4,9 +4,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -4,9 +4,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 5 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 6 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; | 6 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; |
| 7 | +import cn.iocoder.yudao.module.bpm.enums.task.BpmAttachmentTypeEnum; | ||
| 7 | import jakarta.validation.Valid; | 8 | import jakarta.validation.Valid; |
| 8 | import org.flowable.bpmn.model.UserTask; | 9 | import org.flowable.bpmn.model.UserTask; |
| 9 | import org.flowable.engine.history.HistoricActivityInstance; | 10 | import org.flowable.engine.history.HistoricActivityInstance; |
| 11 | +import org.flowable.engine.task.Attachment; | ||
| 10 | import org.flowable.task.api.Task; | 12 | import org.flowable.task.api.Task; |
| 11 | import org.flowable.task.api.TaskInfo; | 13 | import org.flowable.task.api.TaskInfo; |
| 12 | import org.flowable.task.api.history.HistoricTaskInstance; | 14 | import org.flowable.task.api.history.HistoricTaskInstance; |
| @@ -14,6 +16,7 @@ import org.flowable.task.api.history.HistoricTaskInstance; | @@ -14,6 +16,7 @@ import org.flowable.task.api.history.HistoricTaskInstance; | ||
| 14 | import java.util.Collection; | 16 | import java.util.Collection; |
| 15 | import java.util.List; | 17 | import java.util.List; |
| 16 | import java.util.Map; | 18 | import java.util.Map; |
| 19 | +import java.util.Set; | ||
| 17 | 20 | ||
| 18 | /** | 21 | /** |
| 19 | * 流程任务实例 Service 接口 | 22 | * 流程任务实例 Service 接口 |
| @@ -184,6 +187,17 @@ public interface BpmTaskService { | @@ -184,6 +187,17 @@ public interface BpmTaskService { | ||
| 184 | */ | 187 | */ |
| 185 | List<HistoricTaskInstance> getFinishedTaskListByProcessInstanceIdWithoutCancel(String processInstanceId); | 188 | List<HistoricTaskInstance> getFinishedTaskListByProcessInstanceIdWithoutCancel(String processInstanceId); |
| 186 | 189 | ||
| 190 | + | ||
| 191 | + /** | ||
| 192 | + * 根据条件获取附件 | ||
| 193 | + * | ||
| 194 | + * @param processInstanceId 流程 id | ||
| 195 | + * @param taskIds 任务 id 集合 | ||
| 196 | + * @param attachmentType 附件类型 | ||
| 197 | + * @return 附件集合 | ||
| 198 | + */ | ||
| 199 | + List<Attachment> getAttachments(String processInstanceId, Set<String> taskIds, BpmAttachmentTypeEnum attachmentType); | ||
| 200 | + | ||
| 187 | // ========== Update 写入相关方法 ========== | 201 | // ========== Update 写入相关方法 ========== |
| 188 | 202 | ||
| 189 | /** | 203 | /** |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.service.task; | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.service.task; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.convert.Convert; | 4 | import cn.hutool.core.convert.Convert; |
| 5 | +import cn.hutool.core.io.FileUtil; | ||
| 5 | import cn.hutool.core.lang.Assert; | 6 | import cn.hutool.core.lang.Assert; |
| 6 | import cn.hutool.core.util.*; | 7 | import cn.hutool.core.util.*; |
| 7 | import cn.hutool.extra.spring.SpringUtil; | 8 | import cn.hutool.extra.spring.SpringUtil; |
| @@ -19,10 +20,7 @@ import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; | @@ -19,10 +20,7 @@ import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; | ||
| 19 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; | 20 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 20 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; | 21 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 21 | import cn.iocoder.yudao.module.bpm.enums.definition.*; | 22 | import cn.iocoder.yudao.module.bpm.enums.definition.*; |
| 22 | -import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum; | ||
| 23 | -import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; | ||
| 24 | -import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskSignTypeEnum; | ||
| 25 | -import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; | 23 | +import cn.iocoder.yudao.module.bpm.enums.task.*; |
| 26 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; | 24 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 27 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; | 25 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| 28 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; | 26 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; |
| @@ -49,6 +47,7 @@ import org.flowable.engine.history.HistoricActivityInstance; | @@ -49,6 +47,7 @@ import org.flowable.engine.history.HistoricActivityInstance; | ||
| 49 | import org.flowable.engine.runtime.ActivityInstance; | 47 | import org.flowable.engine.runtime.ActivityInstance; |
| 50 | import org.flowable.engine.runtime.Execution; | 48 | import org.flowable.engine.runtime.Execution; |
| 51 | import org.flowable.engine.runtime.ProcessInstance; | 49 | import org.flowable.engine.runtime.ProcessInstance; |
| 50 | +import org.flowable.engine.task.Attachment; | ||
| 52 | import org.flowable.task.api.DelegationState; | 51 | import org.flowable.task.api.DelegationState; |
| 53 | import org.flowable.task.api.Task; | 52 | import org.flowable.task.api.Task; |
| 54 | import org.flowable.task.api.TaskInfo; | 53 | import org.flowable.task.api.TaskInfo; |
| @@ -510,6 +509,18 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -510,6 +509,18 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 510 | .orderByHistoricTaskInstanceStartTime().asc().list(); | 509 | .orderByHistoricTaskInstanceStartTime().asc().list(); |
| 511 | } | 510 | } |
| 512 | 511 | ||
| 512 | + @Override | ||
| 513 | + public List<Attachment> getAttachments(String processInstanceId, Set<String> taskIds, BpmAttachmentTypeEnum attachmentType) { | ||
| 514 | + List<Attachment> result = taskService.getProcessInstanceAttachments(processInstanceId); | ||
| 515 | + if (CollUtil.isNotEmpty(taskIds)) { | ||
| 516 | + result = filterList(result, attachment -> taskIds.contains(attachment.getTaskId())); | ||
| 517 | + } | ||
| 518 | + if (attachmentType != null) { | ||
| 519 | + result = filterList(result, attachment -> attachmentType.getType().equals(attachment.getType())); | ||
| 520 | + } | ||
| 521 | + return result; | ||
| 522 | + } | ||
| 523 | + | ||
| 513 | /** | 524 | /** |
| 514 | * 判断指定用户,是否是当前任务的审批人 | 525 | * 判断指定用户,是否是当前任务的审批人 |
| 515 | * | 526 | * |
| @@ -592,6 +603,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -592,6 +603,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 592 | // 2.2 添加评论 | 603 | // 2.2 添加评论 |
| 593 | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), | 604 | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), |
| 594 | BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); | 605 | BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason())); |
| 606 | + // 2.3 添加附件 | ||
| 607 | + if (CollUtil.isNotEmpty(reqVO.getAttachments())) { | ||
| 608 | + reqVO.getAttachments().forEach(attachment -> taskService.createAttachment(BpmAttachmentTypeEnum.TASK_ATTACHMENT.getType(), | ||
| 609 | + task.getId(), task.getProcessInstanceId(), FileUtil.getName(URLUtil.getPath(attachment)), null, attachment)); | ||
| 610 | + } | ||
| 595 | 611 | ||
| 596 | // 3. 设置流程变量。如果流程变量前端传空,需要从历史实例中获取,原因:前端表单如果在当前节点无可编辑的字段时 variables 一定会为空 | 612 | // 3. 设置流程变量。如果流程变量前端传空,需要从历史实例中获取,原因:前端表单如果在当前节点无可编辑的字段时 variables 一定会为空 |
| 597 | // 场景一:A 节点发起,B 节点表单无可编辑字段,审批通过时,C 节点需要流程变量获取下一个执行节点,但因为 B 节点无可编辑的字段,variables 为空,流程可能出现问题。 | 613 | // 场景一:A 节点发起,B 节点表单无可编辑字段,审批通过时,C 节点需要流程变量获取下一个执行节点,但因为 B 节点无可编辑的字段,variables 为空,流程可能出现问题。 |
| @@ -817,7 +833,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -817,7 +833,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 817 | // 2.2 添加流程评论 | 833 | // 2.2 添加流程评论 |
| 818 | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), | 834 | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), |
| 819 | BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); | 835 | BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason())); |
| 820 | - // 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过 | 836 | + // 2.3 添加附件 |
| 837 | + if (CollUtil.isNotEmpty(reqVO.getAttachments())) { | ||
| 838 | + reqVO.getAttachments().forEach(attachment -> taskService.createAttachment(BpmAttachmentTypeEnum.TASK_ATTACHMENT.getType(), | ||
| 839 | + task.getId(), task.getProcessInstanceId(), FileUtil.getName(URLUtil.getPath(attachment)), null, attachment)); | ||
| 840 | + } | ||
| 841 | + | ||
| 842 | + // 2.4 如果当前任务时被加签的,则加它的根任务也标记成未通过 | ||
| 821 | // 疑问:为什么要标记未通过呢? | 843 | // 疑问:为什么要标记未通过呢? |
| 822 | // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 | 844 | // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 |
| 823 | if (task.getParentTaskId() != null) { | 845 | if (task.getParentTaskId() != null) { |
| @@ -921,7 +943,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -921,7 +943,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 921 | * @param reqVO 前端参数封装 | 943 | * @param reqVO 前端参数封装 |
| 922 | */ | 944 | */ |
| 923 | public void returnTask(Long userId, BpmnModel bpmnModel, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { | 945 | public void returnTask(Long userId, BpmnModel bpmnModel, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { |
| 924 | - // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveExecutionsToSingleActivityId 回撤 | 946 | + // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤 |
| 925 | // 1.1 获取所有正常进行的任务节点 Key | 947 | // 1.1 获取所有正常进行的任务节点 Key |
| 926 | List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list(); | 948 | List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list(); |
| 927 | List<String> runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey); | 949 | List<String> runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey); |
| @@ -929,16 +951,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -929,16 +951,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 929 | // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F | 951 | // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F |
| 930 | List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); | 952 | List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); |
| 931 | List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); | 953 | List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); |
| 932 | - List<String> runExecutionIds = new ArrayList<>(); | ||
| 933 | // 2. 给当前要被退回的 task 数组,设置退回意见 | 954 | // 2. 给当前要被退回的 task 数组,设置退回意见 |
| 934 | taskList.forEach(task -> { | 955 | taskList.forEach(task -> { |
| 935 | // 需要排除掉,不需要设置退回意见的任务 | 956 | // 需要排除掉,不需要设置退回意见的任务 |
| 936 | if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { | 957 | if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { |
| 937 | return; | 958 | return; |
| 938 | } | 959 | } |
| 939 | - if (task.getExecutionId() != null) { | ||
| 940 | - runExecutionIds.add(task.getExecutionId()); | ||
| 941 | - } | ||
| 942 | // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务 | 960 | // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务 |
| 943 | if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记 | 961 | if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记 |
| 944 | // 2.1.1 添加评论 | 962 | // 2.1.1 添加评论 |
| @@ -955,21 +973,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { | @@ -955,21 +973,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { | ||
| 955 | Set<String> needSimulateTaskDefinitionKeys = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement); | 973 | Set<String> needSimulateTaskDefinitionKeys = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement); |
| 956 | 974 | ||
| 957 | // 4. 执行驳回 | 975 | // 4. 执行驳回 |
| 958 | - // 4.1 校验是否有可回撤的 execution,避免 moveExecutionsToSingleActivityId 传入空集合时 Flowable 内部报错 | ||
| 959 | - if (CollUtil.isEmpty(runExecutionIds)) { | ||
| 960 | - throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR); | ||
| 961 | - } | ||
| 962 | - // 4.2 执行驳回 | ||
| 963 | // ① 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId。原因:当多实例任务回退的时候有问题。 | 976 | // ① 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId。原因:当多实例任务回退的时候有问题。 |
| 964 | // 相关 issue: https://github.com/flowable/flowable-engine/issues/3944 | 977 | // 相关 issue: https://github.com/flowable/flowable-engine/issues/3944 |
| 965 | // ② flowable 7.2.0 版本后,继续使用 moveActivityIdsToSingleActivityId 方法。原因:flowable 7.2.0 版本修复了该问题。 | 978 | // ② flowable 7.2.0 版本后,继续使用 moveActivityIdsToSingleActivityId 方法。原因:flowable 7.2.0 版本修复了该问题。 |
| 966 | // 相关 issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/1018 | 979 | // 相关 issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/1018 |
| 967 | // ③ moveActivityIdsToSingleActivityId 使用遇到问题, 相关 issue https://gitee.com/zhijiantianya/yudao-cloud/issues/IJM8MS | 980 | // ③ moveActivityIdsToSingleActivityId 使用遇到问题, 相关 issue https://gitee.com/zhijiantianya/yudao-cloud/issues/IJM8MS |
| 968 | // 改成 moveExecutionsToSingleActivityId 好像并没有遇到 ② 提到的超时提醒失效的问题。暂时先改回 moveExecutionsToSingleActivityId | 981 | // 改成 moveExecutionsToSingleActivityId 好像并没有遇到 ② 提到的超时提醒失效的问题。暂时先改回 moveExecutionsToSingleActivityId |
| 969 | - // 目前还有的相关问题 https://t.zsxq.com/z4d9i。 估计需要升级 flowable 8 版本试试 | 982 | + // ④ moveExecutionsToSingleActivityId 回退多实例的时候不会去删除多实例根, 应改成 moveActivityIdsToSingleActivityId |
| 983 | + // flowable 8.0.0 修复上面相关问题, 还修复了并行分支回退的问题 https://t.zsxq.com/z4d9i。 | ||
| 970 | runtimeService.createChangeActivityStateBuilder() | 984 | runtimeService.createChangeActivityStateBuilder() |
| 971 | .processInstanceId(currentTask.getProcessInstanceId()) | 985 | .processInstanceId(currentTask.getProcessInstanceId()) |
| 972 | - .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) | 986 | + .moveActivityIdsToSingleActivityId(returnTaskKeyList, reqVO.getTargetTaskDefinitionKey()) |
| 973 | // 设置需要预测的任务 ids 的流程变量,用于辅助预测 | 987 | // 设置需要预测的任务 ids 的流程变量,用于辅助预测 |
| 974 | .processVariable(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskDefinitionKeys) | 988 | .processVariable(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskDefinitionKeys) |
| 975 | // 设置流程变量节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 | 989 | // 设置流程变量节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 |