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 | 46 | <jedis-mock.version>1.1.12</jedis-mock.version> |
| 47 | 47 | <mockito-inline.version>5.2.0</mockito-inline.version> |
| 48 | 48 | <!-- Bpm 工作流相关 --> |
| 49 | - <flowable.version>7.2.0</flowable.version> | |
| 49 | + <flowable.version>8.0.0</flowable.version> | |
| 50 | 50 | <!-- 工具类相关 --> |
| 51 | 51 | <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version> |
| 52 | 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 | 107 | @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") |
| 108 | 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 | 21 | @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") |
| 22 | 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 | 27 | @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) |
| 25 | 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 | 5 | |
| 6 | 6 | import jakarta.validation.constraints.NotEmpty; |
| 7 | 7 | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 8 | 10 | @Schema(description = "管理后台 - 不通过流程任务的 Request VO") |
| 9 | 11 | @Data |
| 10 | 12 | public class BpmTaskRejectReqVO { |
| ... | ... | @@ -16,4 +18,7 @@ public class BpmTaskRejectReqVO { |
| 16 | 18 | @Schema(description = "审批意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "不错不错!") |
| 17 | 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 | 9 | import cn.iocoder.yudao.framework.common.util.date.DateUtils; |
| 10 | 10 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| 11 | 11 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 12 | +import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; | |
| 12 | 13 | import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO; |
| 13 | 14 | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; |
| 14 | 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 | 22 | import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert; |
| 22 | 23 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; |
| 23 | 24 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 24 | -import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; | |
| 25 | 25 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| 26 | 26 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| 27 | 27 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; |
| ... | ... | @@ -34,6 +34,7 @@ import org.flowable.bpmn.model.BpmnModel; |
| 34 | 34 | import org.flowable.engine.history.HistoricProcessInstance; |
| 35 | 35 | import org.flowable.engine.repository.ProcessDefinition; |
| 36 | 36 | import org.flowable.engine.runtime.ProcessInstance; |
| 37 | +import org.flowable.engine.task.Attachment; | |
| 37 | 38 | import org.flowable.task.api.Task; |
| 38 | 39 | import org.flowable.task.api.history.HistoricTaskInstance; |
| 39 | 40 | import org.mapstruct.Mapper; |
| ... | ... | @@ -82,7 +83,7 @@ public interface BpmProcessInstanceConvert { |
| 82 | 83 | if (CollUtil.isNotEmpty(respVO.getTasks())) { |
| 83 | 84 | respVO.getTasks().forEach(task -> { |
| 84 | 85 | AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee()); |
| 85 | - if (assigneeUser!= null) { | |
| 86 | + if (assigneeUser != null) { | |
| 86 | 87 | task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class)); |
| 87 | 88 | MapUtils.findAndThen(deptMap, assigneeUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName())); |
| 88 | 89 | } |
| ... | ... | @@ -137,10 +138,10 @@ public interface BpmProcessInstanceConvert { |
| 137 | 138 | |
| 138 | 139 | default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) { |
| 139 | 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 | 147 | default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, |
| ... | ... | @@ -183,8 +184,8 @@ public interface BpmProcessInstanceConvert { |
| 183 | 184 | } |
| 184 | 185 | |
| 185 | 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 | 189 | if (userId == null) { |
| 189 | 190 | return null; |
| 190 | 191 | } |
| ... | ... | @@ -200,13 +201,14 @@ public interface BpmProcessInstanceConvert { |
| 200 | 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 | 205 | if (task == null) { |
| 205 | 206 | return null; |
| 206 | 207 | } |
| 207 | 208 | return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class) |
| 208 | 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 | 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 | 25 | import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants; |
| 26 | 26 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; |
| 27 | 27 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum; |
| 28 | +import cn.iocoder.yudao.module.bpm.enums.task.BpmAttachmentTypeEnum; | |
| 28 | 29 | import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; |
| 29 | 30 | import cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum; |
| 30 | 31 | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| ... | ... | @@ -57,6 +58,7 @@ import org.flowable.engine.repository.ProcessDefinition; |
| 57 | 58 | import org.flowable.engine.runtime.Execution; |
| 58 | 59 | import org.flowable.engine.runtime.ProcessInstance; |
| 59 | 60 | import org.flowable.engine.runtime.ProcessInstanceBuilder; |
| 61 | +import org.flowable.engine.task.Attachment; | |
| 60 | 62 | import org.flowable.task.api.Task; |
| 61 | 63 | import org.flowable.task.api.history.HistoricTaskInstance; |
| 62 | 64 | import org.springframework.context.annotation.Lazy; |
| ... | ... | @@ -404,8 +406,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService |
| 404 | 406 | // 遍历 tasks 列表,只处理已结束的 UserTask |
| 405 | 407 | // 为什么不通过 activities 呢?因为,加签场景下,它只存在于 tasks,没有 activities,导致如果遍历 activities 的话,它无法成为一个节点 |
| 406 | 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 | 413 | List<ActivityNode> approvalNodes = convertList(endTasks, task -> { |
| 408 | 414 | FlowElement flowNode = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey()); |
| 415 | + List<Attachment> taskAttachments = taskAttachmentMap.get(task.getId()); | |
| 409 | 416 | ActivityNode activityNode = new ActivityNode().setId(task.getTaskDefinitionKey()).setName(task.getName()) |
| 410 | 417 | .setNodeType(START_USER_NODE_ID.equals(task.getTaskDefinitionKey()) |
| 411 | 418 | ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() |
| ... | ... | @@ -414,7 +421,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService |
| 414 | 421 | .setStatus(getEndActivityNodeStatus(task)) |
| 415 | 422 | .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) |
| 416 | 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 | 426 | if (BpmTaskStatusEnum.isCancelStatus(activityNode.getStatus())) { |
| 420 | 427 | return null; |
| ... | ... | @@ -528,14 +535,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService |
| 528 | 535 | if (task == null) { |
| 529 | 536 | continue; |
| 530 | 537 | } |
| 531 | - activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)); | |
| 538 | + activityNode.getTasks().add(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task, null)); | |
| 532 | 539 | // 加签子任务,需要过滤掉已经完成的加签子任务 |
| 533 | 540 | List<HistoricTaskInstance> childrenTasks = filterList( |
| 534 | 541 | taskService.getAllChildrenTaskListByParentTaskId(activity.getTaskId(), tasks), |
| 535 | 542 | childTask -> childTask.getEndTime() == null); |
| 536 | 543 | if (CollUtil.isNotEmpty(childrenTasks)) { |
| 537 | 544 | activityNode.getTasks().addAll( |
| 538 | - convertList(childrenTasks, BpmProcessInstanceConvert.INSTANCE::buildApprovalTaskInfo)); | |
| 545 | + convertList(childrenTasks, item->BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(item, null))); | |
| 539 | 546 | } |
| 540 | 547 | } |
| 541 | 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 | 4 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 5 | 5 | import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; |
| 6 | 6 | import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum; |
| 7 | +import cn.iocoder.yudao.module.bpm.enums.task.BpmAttachmentTypeEnum; | |
| 7 | 8 | import jakarta.validation.Valid; |
| 8 | 9 | import org.flowable.bpmn.model.UserTask; |
| 9 | 10 | import org.flowable.engine.history.HistoricActivityInstance; |
| 11 | +import org.flowable.engine.task.Attachment; | |
| 10 | 12 | import org.flowable.task.api.Task; |
| 11 | 13 | import org.flowable.task.api.TaskInfo; |
| 12 | 14 | import org.flowable.task.api.history.HistoricTaskInstance; |
| ... | ... | @@ -14,6 +16,7 @@ import org.flowable.task.api.history.HistoricTaskInstance; |
| 14 | 16 | import java.util.Collection; |
| 15 | 17 | import java.util.List; |
| 16 | 18 | import java.util.Map; |
| 19 | +import java.util.Set; | |
| 17 | 20 | |
| 18 | 21 | /** |
| 19 | 22 | * 流程任务实例 Service 接口 |
| ... | ... | @@ -184,6 +187,17 @@ public interface BpmTaskService { |
| 184 | 187 | */ |
| 185 | 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 | 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 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.convert.Convert; |
| 5 | +import cn.hutool.core.io.FileUtil; | |
| 5 | 6 | import cn.hutool.core.lang.Assert; |
| 6 | 7 | import cn.hutool.core.util.*; |
| 7 | 8 | import cn.hutool.extra.spring.SpringUtil; |
| ... | ... | @@ -19,10 +20,7 @@ import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert; |
| 19 | 20 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO; |
| 20 | 21 | import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO; |
| 21 | 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 | 24 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; |
| 27 | 25 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; |
| 28 | 26 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; |
| ... | ... | @@ -49,6 +47,7 @@ import org.flowable.engine.history.HistoricActivityInstance; |
| 49 | 47 | import org.flowable.engine.runtime.ActivityInstance; |
| 50 | 48 | import org.flowable.engine.runtime.Execution; |
| 51 | 49 | import org.flowable.engine.runtime.ProcessInstance; |
| 50 | +import org.flowable.engine.task.Attachment; | |
| 52 | 51 | import org.flowable.task.api.DelegationState; |
| 53 | 52 | import org.flowable.task.api.Task; |
| 54 | 53 | import org.flowable.task.api.TaskInfo; |
| ... | ... | @@ -510,6 +509,18 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 510 | 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 | 603 | // 2.2 添加评论 |
| 593 | 604 | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(), |
| 594 | 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 | 612 | // 3. 设置流程变量。如果流程变量前端传空,需要从历史实例中获取,原因:前端表单如果在当前节点无可编辑的字段时 variables 一定会为空 |
| 597 | 613 | // 场景一:A 节点发起,B 节点表单无可编辑字段,审批通过时,C 节点需要流程变量获取下一个执行节点,但因为 B 节点无可编辑的字段,variables 为空,流程可能出现问题。 |
| ... | ... | @@ -817,7 +833,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 817 | 833 | // 2.2 添加流程评论 |
| 818 | 834 | taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.REJECT.getType(), |
| 819 | 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 | 844 | // 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。 |
| 823 | 845 | if (task.getParentTaskId() != null) { |
| ... | ... | @@ -921,7 +943,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 921 | 943 | * @param reqVO 前端参数封装 |
| 922 | 944 | */ |
| 923 | 945 | public void returnTask(Long userId, BpmnModel bpmnModel, Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) { |
| 924 | - // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveExecutionsToSingleActivityId 回撤 | |
| 946 | + // 1. 获得所有需要回撤的任务 taskDefinitionKey,用于稍后的 moveActivityIdsToSingleActivityId 回撤 | |
| 925 | 947 | // 1.1 获取所有正常进行的任务节点 Key |
| 926 | 948 | List<Task> taskList = taskService.createTaskQuery().processInstanceId(currentTask.getProcessInstanceId()).list(); |
| 927 | 949 | List<String> runTaskKeyList = convertList(taskList, Task::getTaskDefinitionKey); |
| ... | ... | @@ -929,16 +951,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 929 | 951 | // 为什么不直接使用 runTaskKeyList 呢?因为可能存在多个审批分支,例如说:A -> B -> C 和 D -> F,而只要 C 撤回到 A,需要排除掉 F |
| 930 | 952 | List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null); |
| 931 | 953 | List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId); |
| 932 | - List<String> runExecutionIds = new ArrayList<>(); | |
| 933 | 954 | // 2. 给当前要被退回的 task 数组,设置退回意见 |
| 934 | 955 | taskList.forEach(task -> { |
| 935 | 956 | // 需要排除掉,不需要设置退回意见的任务 |
| 936 | 957 | if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { |
| 937 | 958 | return; |
| 938 | 959 | } |
| 939 | - if (task.getExecutionId() != null) { | |
| 940 | - runExecutionIds.add(task.getExecutionId()); | |
| 941 | - } | |
| 942 | 960 | // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务 |
| 943 | 961 | if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记 |
| 944 | 962 | // 2.1.1 添加评论 |
| ... | ... | @@ -955,21 +973,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { |
| 955 | 973 | Set<String> needSimulateTaskDefinitionKeys = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement); |
| 956 | 974 | |
| 957 | 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 | 976 | // ① 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId。原因:当多实例任务回退的时候有问题。 |
| 964 | 977 | // 相关 issue: https://github.com/flowable/flowable-engine/issues/3944 |
| 965 | 978 | // ② flowable 7.2.0 版本后,继续使用 moveActivityIdsToSingleActivityId 方法。原因:flowable 7.2.0 版本修复了该问题。 |
| 966 | 979 | // 相关 issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/1018 |
| 967 | 980 | // ③ moveActivityIdsToSingleActivityId 使用遇到问题, 相关 issue https://gitee.com/zhijiantianya/yudao-cloud/issues/IJM8MS |
| 968 | 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 | 984 | runtimeService.createChangeActivityStateBuilder() |
| 971 | 985 | .processInstanceId(currentTask.getProcessInstanceId()) |
| 972 | - .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) | |
| 986 | + .moveActivityIdsToSingleActivityId(returnTaskKeyList, reqVO.getTargetTaskDefinitionKey()) | |
| 973 | 987 | // 设置需要预测的任务 ids 的流程变量,用于辅助预测 |
| 974 | 988 | .processVariable(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskDefinitionKeys) |
| 975 | 989 | // 设置流程变量节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 | ... | ... |