BPM:支持多表单,每个流程任务都可以绑定流程表单

This commit is contained in:
YunaiV 2024-03-20 12:50:51 +08:00
parent ed83b912e4
commit 29a0fbfc43
11 changed files with 117 additions and 41 deletions

View File

@ -20,7 +20,7 @@ public class BpmProcessInstanceCreateReqDTO {
@NotEmpty(message = "流程定义的标识不能为空")
private String processDefinitionKey;
/**
* 变量实例
* 变量实例动态表单
*/
private Map<String, Object> variables;

View File

@ -6,6 +6,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@ -46,6 +48,8 @@ public class BpmTaskController {
private BpmTaskService taskService;
@Resource
private BpmProcessInstanceService processInstanceService;
@Resource
private BpmFormService formService;
@Resource
private AdminUserApi adminUserApi;
@ -98,7 +102,11 @@ public class BpmTaskController {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance, userMap, deptMap));
// 获得 Form Map
Map<Long, BpmFormDO> formMap = formService.getFormMap(
convertSet(taskList, task -> Long.parseLong(task.getFormKey())));
return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance,
formMap, userMap, deptMap));
}
@PutMapping("/approve")

View File

@ -15,7 +15,7 @@ public class BpmProcessInstanceCreateReqVO {
@NotEmpty(message = "流程定义编号不能为空")
private String processDefinitionId;
@Schema(description = "变量实例")
@Schema(description = "变量实例(动态表单)")
private Map<String, Object> variables;
// TODO @haiassignees 复数

View File

@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.Collection;
import java.util.Map;
@Schema(description = "管理后台 - 通过流程任务的 Request VO")
@Data
@ -21,4 +22,7 @@ public class BpmTaskApproveReqVO {
@Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2")
private Collection<Long> copyUserIds;
@Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, Object> variables;
}

View File

@ -6,6 +6,7 @@ import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - 流程任务 Response VO")
@Data
@ -49,7 +50,6 @@ public class BpmTaskRespVO {
@Schema(description = "所属流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8888")
private String processInstanceId;
/**
* 所属流程实例
*/
@ -57,10 +57,20 @@ public class BpmTaskRespVO {
@Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String parentTaskId;
@Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask")
private List<BpmTaskRespVO> children;
@Schema(description = "表单编号", example = "1024")
private Long formId;
@Schema(description = "表单名字", example = "请假表单")
private String formName;
@Schema(description = "表单的配置-JSON 字符串")
private String formConf;
@Schema(description = "表单项的数组")
private List<String> formFields;
@Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, Object> formVariables;
@Data
@Schema(description = "流程实例")
public static class ProcessInstance {

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@ -56,7 +57,9 @@ public interface BpmModelConvert {
byte[] bpmnBytes) {
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null);
modelVO.setBpmnXml(new String(bpmnBytes));
if (ArrayUtil.isNotEmpty(bpmnBytes)) {
modelVO.setBpmnXml(new String(bpmnBytes));
}
return modelVO;
}

View File

@ -2,15 +2,14 @@ package cn.iocoder.yudao.module.bpm.convert.task;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@ -19,7 +18,6 @@ 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.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
@ -32,7 +30,6 @@ import java.util.Map;
*
* @author 芋道源码
*/
@Mapper(uses = DateUtils.class)
public interface BpmProcessInstanceConvert {
BpmProcessInstanceConvert INSTANCE = Mappers.getMapper(BpmProcessInstanceConvert.class);
@ -44,7 +41,7 @@ public interface BpmProcessInstanceConvert {
PageResult<BpmProcessInstancePageItemRespVO> vpPageResult = BeanUtils.toBean(pageResult, BpmProcessInstancePageItemRespVO.class);
for (int i = 0; i < pageResult.getList().size(); i++) {
BpmProcessInstancePageItemRespVO respVO = vpPageResult.getList().get(i);
respVO.setStatus((Integer) pageResult.getList().get(i).getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS));
respVO.setStatus(FlowableUtils.getProcessInstanceStatus(pageResult.getList().get(i)));
MapUtils.findAndThen(processDefinitionMap, respVO.getProcessDefinitionId(),
processDefinition -> respVO.setCategory(processDefinition.getCategory()));
MapUtils.findAndThen(categoryMap, respVO.getCategory(), category -> respVO.setCategoryName(category.getName()));
@ -57,8 +54,8 @@ public interface BpmProcessInstanceConvert {
ProcessDefinition processDefinition, BpmProcessDefinitionInfoDO processDefinitionExt,
String bpmnXml, AdminUserRespDTO startUser, DeptRespDTO dept) {
BpmProcessInstanceRespVO respVO = convert2(processInstance);
respVO.setStatus((Integer) processInstance.getProcessVariables().get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS));
respVO.setFormVariables(processInstance.getProcessVariables()); // TODO 芋艿真的这么搞么formVariable 要不要换个 key 之类的
respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance));
respVO.setFormVariables(FlowableUtils.filterProcessInstanceFormVariable(processInstance.getProcessVariables()));
// definition
respVO.setProcessDefinition(convert2(processDefinition));
copyTo(processDefinitionExt, respVO.getProcessDefinition());

View File

@ -1,14 +1,15 @@
package cn.iocoder.yudao.module.bpm.convert.task;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmConstants;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@ -17,7 +18,6 @@ import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.Date;
@ -25,13 +25,13 @@ import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
/**
* Bpm 任务 Convert
*
* @author 芋道源码
*/
@Mapper(uses = DateUtils.class)
public interface BpmTaskConvert {
BpmTaskConvert INSTANCE = Mappers.getMapper(BpmTaskConvert.class);
@ -45,8 +45,7 @@ public interface BpmTaskConvert {
return;
}
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class,
processInstanceVO -> processInstanceVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class))));
taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
});
}
@ -55,14 +54,13 @@ public interface BpmTaskConvert {
Map<Long, AdminUserRespDTO> userMap) {
List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(pageResult.getList(), task -> {
BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
taskVO.setStatus((Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS));
taskVO.setReason((String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON));
taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
// 流程实例
HistoricProcessInstance processInstance = processInstanceMap.get(taskVO.getProcessInstanceId());
if (processInstance != null) {
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class,
processInstanceVO -> processInstanceVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class))));
taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
}
return taskVO;
});
@ -71,32 +69,32 @@ public interface BpmTaskConvert {
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 -> {
BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
taskVO.setStatus((Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS));
taskVO.setReason((String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON));
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,
processInstanceVO -> processInstanceVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class))));
taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
// 表单信息
BpmFormDO form = MapUtil.get(formMap, Long.parseLong(task.getFormKey()), BpmFormDO.class);
if (form != null) {
taskVO.setFormId(form.getId()).setFormName(form.getName()).setFormConf(form.getConf())
.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));
DeptRespDTO dept = deptMap.get(assignUser.getDeptId());
if (dept != null) {
taskVO.getAssigneeUser().setDeptName(dept.getName());
}
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));
DeptRespDTO dept = deptMap.get(ownerUser.getDeptId());
if (dept != null) {
taskVO.getOwnerUser().setDeptName(dept.getName());
}
findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
}
return taskVO;
});
@ -126,10 +124,7 @@ public interface BpmTaskConvert {
AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
if (ownerUser != null) {
taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class));
DeptRespDTO dept = deptMap.get(ownerUser.getDeptId());
if (dept != null) {
taskVO.getOwnerUser().setDeptName(dept.getName());
}
findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
}
}));
}

View File

@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
@ -185,7 +186,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
// 3.3 调用 BPM complete 去完成任务
taskService.complete(task.getId(), instance.getProcessVariables());
// 其中variables 是存储动态表单到 local 任务级别过滤一下避免 ProcessInstance 系统级的变量被占用
Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
taskService.complete(task.getId(), variables, true);
// 加签专属处理加签任务
handleParentTaskIfSign(task.getParentTaskId());

View File

@ -4,8 +4,14 @@ 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.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.TaskInfo;
import java.util.HashMap;
import java.util.Map;
/**
* Flowable 相关的工具方法
@ -34,6 +40,56 @@ public class FlowableUtils {
return activityId + "_assignee";
}
// ========== ProcessInstance 相关的工具方法 ==========
public static Integer getProcessInstanceStatus(ProcessInstance processInstance) {
return getProcessInstanceStatus(processInstance.getProcessVariables());
}
public static Integer getProcessInstanceStatus(HistoricProcessInstance processInstance) {
return getProcessInstanceStatus(processInstance.getProcessVariables());
}
// TODO 芋艿需要再搞搞
private static Integer getProcessInstanceStatus(Map<String, Object> processVariables) {
return (Integer) processVariables.get("PROCESS_STATUS");
}
public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) {
Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables());
filterProcessInstanceFormVariable(formVariables);
return formVariables;
}
public static Map<String, Object> filterProcessInstanceFormVariable(Map<String, Object> processVariables) {
processVariables.remove("PROCESS_STATUS");
return processVariables;
}
// ========== Task 相关的工具方法 ==========
// TODO 芋艿需要再搞搞
public static Integer getTaskStatus(TaskInfo task) {
return (Integer) task.getTaskLocalVariables().get("TASK_STATUS");
}
public static String getTaskReason(TaskInfo task) {
return (String) task.getTaskLocalVariables().get("TASK_REASON");
}
public static Map<String, Object> getTaskFormVariable(TaskInfo task) {
Map<String, Object> formVariables = new HashMap<>(task.getTaskLocalVariables());
filterTaskFormVariable(formVariables);
return formVariables;
}
public static Map<String, Object> filterTaskFormVariable(Map<String, Object> taskLocalVariables) {
taskLocalVariables.remove("TASK_STATUS");
taskLocalVariables.remove("TASK_REASON");
return taskLocalVariables;
}
// ========== Expression 相关的工具方法 ==========
public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {