!1276 fix: 发起流程报错,返回的VO缺少字段,icon为null时,copyTo转换异常问题
Merge pull request !1276 from SamllNorth_Lee/feature/bpm
This commit is contained in:
commit
44083c96c3
|
@ -44,6 +44,11 @@ public class BpmModelMetaInfoVO {
|
|||
private Integer formType;
|
||||
@Schema(description = "表单编号", example = "1024")
|
||||
private Long formId; // formType 为 NORMAL 使用,必须非空
|
||||
|
||||
@Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String formConf;
|
||||
@Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<String> formFields;
|
||||
@Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
|
||||
private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
|
||||
@Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
|
||||
|
|
|
@ -21,6 +21,9 @@ public class BpmModelSaveReqVO extends BpmModelMetaInfoVO {
|
|||
@NotEmpty(message = "流程名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "表单名字", example = "请假表单")
|
||||
private String formName;
|
||||
|
||||
@Schema(description = "流程分类", example = "1")
|
||||
private String category;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 流程定义 Response VO")
|
||||
@Data
|
||||
|
@ -19,7 +20,10 @@ public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
|
|||
@Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
|
||||
@Schema(description = "表单名字", example = "请假表单")
|
||||
private String formName;
|
||||
|
||||
@Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "youdao")
|
||||
private String key;
|
||||
|
||||
@Schema(description = "流程分类", example = "1")
|
||||
|
@ -27,14 +31,11 @@ public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
|
|||
@Schema(description = "流程分类名字", example = "请假")
|
||||
private String categoryName;
|
||||
|
||||
@Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
|
||||
private String modelId;
|
||||
|
||||
@Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
|
||||
|
||||
@Schema(description = "表单名字", example = "请假表单")
|
||||
private String formName;
|
||||
@Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
|
||||
private String modelId;
|
||||
|
||||
@Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer suspensionState; // 参见 SuspensionState 枚举
|
||||
|
|
|
@ -907,9 +907,16 @@ public class BpmnModelUtils {
|
|||
* @return 符合条件的路径
|
||||
*/
|
||||
private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) {
|
||||
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
|
||||
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
|
||||
&& (evalConditionExpress(variables, flow.getConditionExpression())));
|
||||
// TODO 表单无可编辑字段时variables为空,流程走向会出现问题,比如流程审批过程中无需要修改的字段值,
|
||||
SequenceFlow matchSequenceFlow;
|
||||
if (CollUtil.isNotEmpty(variables)){
|
||||
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
|
||||
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
|
||||
&& (evalConditionExpress(variables, flow.getConditionExpression())));
|
||||
}else {
|
||||
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
|
||||
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
|
||||
}
|
||||
if (matchSequenceFlow == null) {
|
||||
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
|
||||
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
|
||||
|
|
|
@ -40,9 +40,7 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
@ -209,11 +207,11 @@ public class BpmModelServiceImpl implements BpmModelService {
|
|||
public void deployModel(Long userId, String id) {
|
||||
// 1.1 校验流程模型存在
|
||||
Model model = validateModelManager(id, userId);
|
||||
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
|
||||
// 1.2 校验流程图
|
||||
byte[] bpmnBytes = getModelBpmnXML(model.getId());
|
||||
validateBpmnXml(bpmnBytes);
|
||||
validateBpmnXml(bpmnBytes, metaInfo.getType());
|
||||
// 1.3 校验表单已配
|
||||
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
|
||||
BpmFormDO form = validateFormConfig(metaInfo);
|
||||
// 1.4 校验任务分配规则已配置
|
||||
taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
|
||||
|
@ -233,7 +231,7 @@ public class BpmModelServiceImpl implements BpmModelService {
|
|||
repositoryService.saveModel(model);
|
||||
}
|
||||
|
||||
private void validateBpmnXml(byte[] bpmnBytes) {
|
||||
private void validateBpmnXml(byte[] bpmnBytes, int type) {
|
||||
BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
|
||||
if (bpmnModel == null) {
|
||||
throw exception(MODEL_NOT_EXISTS);
|
||||
|
@ -243,24 +241,23 @@ public class BpmModelServiceImpl implements BpmModelService {
|
|||
if (startEvent == null) {
|
||||
throw exception(MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS);
|
||||
}
|
||||
// 2. 校验第一个用户任务的规则类型是否为“审批人自选”,如果是则抛出异常。原因是,流程发起后,直接进入第一个用户任务,会出现无审批人的情况
|
||||
List<SequenceFlow> outgoingFlows = startEvent.getOutgoingFlows();
|
||||
// TODO @小北:可能极端情况下,startEvent 后面接了个 serviceTask,接着才是 userTask。。。
|
||||
// TODO @小北:simple 因为第一个任务是发起人,可能要找第二个任务???
|
||||
if (CollUtil.isNotEmpty(outgoingFlows)) {
|
||||
FlowElement targetFlowElement = outgoingFlows.get(0).getTargetFlowElement();
|
||||
Integer candidateStrategy = parseCandidateStrategy(targetFlowElement);
|
||||
if (Objects.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
|
||||
throw exception(MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR, targetFlowElement.getName());
|
||||
}
|
||||
}
|
||||
// 3. 校验 UserTask 的 name 都配置了
|
||||
// 2. 校验 UserTask 的 name 都配置了
|
||||
List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
|
||||
userTasks.forEach(userTask -> {
|
||||
if (StrUtil.isEmpty(userTask.getName())) {
|
||||
throw exception(MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS, userTask.getId());
|
||||
}
|
||||
});
|
||||
// 3. 校验第一个用户任务节点的规则类型是否为“审批人自选”
|
||||
Map<Integer, UserTask> userTaskMap = new HashMap<>();
|
||||
// BPMN 设计器,校验第一个用户任务节点
|
||||
userTaskMap.put(BpmModelTypeEnum.BPMN.getType(), userTasks.get(0));
|
||||
// SIMPLE 设计器,第一个节点固定为发起人所以校验第二个用户任务节点
|
||||
userTaskMap.put(BpmModelTypeEnum.SIMPLE.getType(), userTasks.get(1));
|
||||
Integer candidateStrategy = parseCandidateStrategy(userTaskMap.get(type));
|
||||
if (Objects.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
|
||||
throw exception(MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR, userTaskMap.get(type).getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -221,8 +221,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
|
|||
processDefinitionInfo,
|
||||
processVariables, activities);
|
||||
// 3.3 如果是发起动作,activityId 为开始节点,不校验审批人自选节点
|
||||
// TODO @小北:ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID) 够啦,不用判空
|
||||
if (ObjUtil.isNotNull(reqVO.getActivityId()) && ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
|
||||
if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
|
||||
simulateActivityNodes.removeIf(node ->
|
||||
BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
|
||||
}
|
||||
|
|
|
@ -558,18 +558,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||
taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
|
||||
BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
|
||||
// 2.3 调用 BPM complete 去完成任务
|
||||
// 其中,variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
|
||||
if (CollUtil.isNotEmpty(reqVO.getVariables())) {
|
||||
// 校验并处理 APPROVE_USER_SELECT 当前审批人,选择下一节点审批人的逻辑
|
||||
Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
|
||||
bpmnModel, reqVO.getNextAssignees(), instance);
|
||||
// 完成任务
|
||||
runtimeService.setVariables(task.getProcessInstanceId(), variables);
|
||||
taskService.complete(task.getId(), variables, true);
|
||||
} else {
|
||||
// 完成任务
|
||||
taskService.complete(task.getId());
|
||||
}
|
||||
// 校验并处理 APPROVE_USER_SELECT 当前审批人,选择下一节点审批人的逻辑
|
||||
Map<String, Object> variables = validateAndSetNextAssignees(task.getTaskDefinitionKey(), reqVO.getVariables(),
|
||||
bpmnModel, reqVO.getNextAssignees(), instance);
|
||||
// 完成任务
|
||||
runtimeService.setVariables(task.getProcessInstanceId(), variables);
|
||||
taskService.complete(task.getId(), variables, true);
|
||||
|
||||
// 【加签专属】处理加签任务
|
||||
handleParentTaskIfSign(task.getParentTaskId());
|
||||
|
@ -590,38 +584,31 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||
*/
|
||||
private Map<String, Object> validateAndSetNextAssignees(String taskDefinitionKey, Map<String, Object> variables, BpmnModel bpmnModel,
|
||||
Map<String, List<Long>> nextAssignees, ProcessInstance processInstance) {
|
||||
// 下一个节点参数为空,不做处理,表示流程正常流转,无需选择下一个节点的审判人
|
||||
// TODO @小北:会出现漏选,其实需要的情况哇?
|
||||
if (CollUtil.isEmpty(nextAssignees)) {
|
||||
return variables;
|
||||
}
|
||||
// 1. 获取下一个将要执行的节点集合
|
||||
FlowElement flowElement = bpmnModel.getFlowElement(taskDefinitionKey);
|
||||
List<FlowNode> nextFlowNodes = getNextFlowNodes(flowElement, bpmnModel, variables);
|
||||
|
||||
// 2. 循环下一个将要执行的节点集合
|
||||
Map<String, List<Long>> processVariables = new HashMap<>();
|
||||
Map<String, List<Long>> processVariables;
|
||||
for (FlowNode nextFlowNode : nextFlowNodes) {
|
||||
if (!nextAssignees.containsKey(nextFlowNode.getId())) {
|
||||
throw exception(TASK_TARGET_NODE_NOT_EXISTS, nextFlowNode.getName());
|
||||
}
|
||||
List<Long> assignees = nextAssignees.get(nextFlowNode.getId());
|
||||
// 2.1 情况一:如果节点中的审批人策略为 发起人自选
|
||||
// 获取任务节点中的审批人策略
|
||||
Integer candidateStrategy = parseCandidateStrategy(nextFlowNode);
|
||||
// 2.1 情况一:如果节点中的审批人策略为 发起人自选
|
||||
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())) {
|
||||
processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
|
||||
if (processVariables == null) {
|
||||
processVariables = new HashMap<>();
|
||||
}
|
||||
List<Long> startUserSelectAssignee = processVariables.get(nextFlowNode.getId());
|
||||
// 特殊:如果当前节点已经存在审批人,则不允许覆盖
|
||||
if (CollUtil.isNotEmpty(startUserSelectAssignee)) {
|
||||
continue;
|
||||
}
|
||||
// 如果节点存在,但未配置审批人
|
||||
List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
|
||||
if (CollUtil.isEmpty(assignees)) {
|
||||
throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
|
||||
}
|
||||
processVariables = FlowableUtils.getStartUserSelectAssignees(processInstance.getProcessVariables());
|
||||
if (processVariables == null){
|
||||
processVariables = new HashMap<>();
|
||||
}else {
|
||||
List<Long> startUserSelectAssignee = processVariables.get(nextFlowNode.getId());
|
||||
// 特殊:如果当前节点已经存在审批人,则不允许覆盖
|
||||
if (CollUtil.isNotEmpty(startUserSelectAssignee)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 校验通过的全部节点和审批人
|
||||
processVariables.put(nextFlowNode.getId(), assignees);
|
||||
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, processVariables);
|
||||
|
@ -629,12 +616,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||
// 2.2 情况二:如果节点中的审批人策略为 审批人,在审批时选择下一个节点的审批人,并且该节点的审批人为空
|
||||
if (ObjUtil.equals(candidateStrategy, BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy())) {
|
||||
// 如果节点存在,但未配置审批人
|
||||
List<Long> assignees = nextAssignees != null ? nextAssignees.get(nextFlowNode.getId()) : null;
|
||||
if (CollUtil.isEmpty(assignees)) {
|
||||
throw exception(PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG, nextFlowNode.getName());
|
||||
}
|
||||
processVariables = FlowableUtils.getApproveUserSelectAssignees(processInstance.getProcessVariables());
|
||||
if (processVariables == null){
|
||||
processVariables = new HashMap<>();
|
||||
}else {
|
||||
List<Long> approveUserSelectAssignee = processVariables.get(nextFlowNode.getId());
|
||||
// 特殊:如果当前节点已经存在审批人,则不允许覆盖
|
||||
if (CollUtil.isNotEmpty(approveUserSelectAssignee)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 校验通过的全部节点和审批人
|
||||
processVariables.put(nextFlowNode.getId(), assignees);
|
||||
// TODO @小北:是不是要类似情况一的做法,通过 PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES 拿一下?因为如果 task1 是审批人自选,task2 是审批人自选,看着会覆盖?
|
||||
variables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES, processVariables);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue