feat(REQ-3004) - 集成表单引擎测试

This commit is contained in:
wangli 2024-11-08 18:37:06 +08:00
parent 2c0630f888
commit 424da03bd2
17 changed files with 641 additions and 133 deletions

View File

@ -8,6 +8,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
@ -63,6 +64,16 @@ public interface ProcessTaskApi {
@PostMapping("/api/process/task/approve")
CommonResponse<Boolean> approveTask(@Validated @RequestBody BpmnTaskAuditDTO dto);
/**
* 同意时并提交表单数据
*
* @param dto
* @return
*/
@Operation(summary = "同意时并提交表单")
@PostMapping("/api/process/task/form/approve")
CommonResponse<Boolean> approveTaskWithForm(@Validated @RequestBody BpmnTaskAuditWithFormDTO dto);
/**
* 批量同意
*
@ -75,6 +86,7 @@ public interface ProcessTaskApi {
/**
* 获取当前节点可回退节点选项列表
*
* @param taskId 当前任务id
* @return 可以回退节点列表
*/
@ -84,6 +96,7 @@ public interface ProcessTaskApi {
/**
* 回退到指定节点
*
* @param dto
* @return
*/

View File

@ -0,0 +1,32 @@
package cn.axzo.workflow.common.model.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 搜索
*
* @author wangli
* @date 2024/11/08
*/
@Data
public class BpmnFormRelationSearchDTO {
/**
* 业务标识
*/
@ApiModelProperty(value = "业务标识")
private String key;
/**
* 审批模型定义 ID
*/
@ApiModelProperty(value = "审批模型定义 ID")
private String bpmnDefinitionId;
/**
* 表单定义部署 ID
*/
@ApiModelProperty(value = "表单定义部署 ID")
private String formDeploymentId;
}

View File

@ -0,0 +1,31 @@
package cn.axzo.workflow.common.model.request.bpmn.task;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Map;
/**
* 审批同意时携带表单
*
* @author wangli
* @since 2024-11-08 11:36
*/
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "审批任务携带表单数据的入参模型")
public class BpmnTaskAuditWithFormDTO extends BpmnTaskAuditDTO {
/**
* 表单数据
*/
@ApiModelProperty(value = "表单数据")
private Map<String, Object> formVariables;
/**
* 暂对不对
*/
private String outcome;
}

View File

@ -28,7 +28,6 @@ import com.alibaba.cloud.nacos.NacosServiceManager;
import com.google.common.collect.Lists;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.common.engine.impl.history.HistoryLevel;
import org.flowable.form.spring.SpringFormEngineConfiguration;
import org.flowable.job.service.JobProcessor;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
@ -46,8 +45,6 @@ import java.time.Duration;
import java.util.List;
import java.util.Map;
import static org.flowable.common.engine.impl.AbstractEngineConfiguration.DB_SCHEMA_UPDATE_TRUE;
/**
* Flowable 引擎相关全局配置
*

View File

@ -0,0 +1,184 @@
package cn.axzo.workflow.core.engine.cmd;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import com.alibaba.fastjson.JSON;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.form.api.FormDefinition;
import org.flowable.form.api.FormEngineConfigurationApi;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.addComment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.batchAddAttachment;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.validTask;
/**
* 自定义(同步)审批通过任务的命令器实现
*
* @author wangli
* @since 2024/1/4 15:50
*/
public class CustomApproveTaskWithFormCmd extends AbstractCommand<Void> implements Serializable {
private static final Logger log = LoggerFactory.getLogger(CustomApproveTaskWithFormCmd.class);
private final String taskId;
/**
* advice 统一均为审批节点的审批意见一般为用户在同意驳回时填写的内容
* 有值时在日志中一般出现在 operationDesc 的下方
*/
private final String advice;
/**
* operationDesc 统一为审批节点的动作描述例如某某转交加签某某等
* 在日志中一般出现在节点名称的下方
* 不传,默认"已通过"
*/
private String operationDesc;
/**
* 附件, 可为空
*/
private final List<AttachmentDTO> attachmentList;
private final BpmnTaskDelegateAssigner approver;
/**
* 下级节点的审批,可为空
*/
private final BpmnTaskDelegateAssigner nextApprover;
/**
* 指定节点类型
*/
private List<BpmnFlowNodeType> nodeTypes;
/**
* 表单数据
*/
private Map<String, Object> formVariables;
@Override
public String paramToJsonString() {
Map<String, Object> params = new HashMap<>();
params.put("taskId", taskId);
params.put("advice", advice);
params.put("operationDesc", operationDesc);
params.put("attachmentList", JSON.toJSONString(attachmentList));
params.put("approver", JSON.toJSONString(approver));
params.put("nextApprover", JSON.toJSONString(nextApprover));
params.put("nodeTypes", JSON.toJSONString(nodeTypes));
params.put("formVariables", JSON.toJSONString(formVariables));
return JSON.toJSONString(params);
}
public CustomApproveTaskWithFormCmd(BpmnTaskAuditWithFormDTO dto) {
this(dto, null);
if (Objects.nonNull(dto.getOperationDesc())) {
this.operationDesc = dto.getOperationDesc();
}
}
public CustomApproveTaskWithFormCmd(BpmnTaskAuditWithFormDTO dto, String operationDesc) {
this.taskId = dto.getTaskId();
this.advice = dto.getAdvice();
this.attachmentList = dto.getAttachmentList();
this.approver = dto.getApprover();
this.nextApprover = dto.getNextApprover();
this.nodeTypes = dto.getNodeTypes();
this.formVariables = dto.getFormVariables();
// 这里的不能直接使用字符串的比较因为外部可能传入空字符串比如发起人的通过时就是传入的空字符串
if (Objects.nonNull(operationDesc)) {
this.operationDesc = operationDesc;
} else {
this.operationDesc = "(已通过)";
}
}
@Override
public Void execute(CommandContext commandContext) {
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
HistoricTaskInstanceQuery taskQuery =
processEngineConfiguration.getHistoryService().createHistoricTaskInstanceQuery();
TaskService taskService = processEngineConfiguration.getTaskService();
HistoricTaskInstance historicTaskInstance = taskQuery.taskId(taskId).singleResult();
TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(taskId).singleResult();
validTask(historicTaskInstance, task, approver, nodeTypes);
// TODO 所有的跟 Task 相关的动作都可以在这里进行扩展用于扩展八大按钮标准动作以外的一些逻辑但这里需要结合 Spring 能力需设计好扩展点否则无法进行扩展
// 其他动态也应该在类似的地方预留扩展点
if (StringUtils.hasLength(advice)) {
Authentication.setAuthenticatedUserId(approver.buildAssigneeId());
addComment(commandContext, task, COMMENT_TYPE_ADVICE, advice);
Authentication.setAuthenticatedUserId(null);
}
batchAddAttachment(commandContext, task.getProcessInstanceId(), task, attachmentList, approver);
Authentication.setAuthenticatedUserId(Objects.nonNull(approver) ? approver.buildAssigneeId() : null);
addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, operationDesc);
Authentication.setAuthenticatedUserId(null);
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
if (Objects.nonNull(nextApprover)) {
// 主动设置下级审批人
runtimeService.setVariable(task.getProcessInstanceId(), INTERNAL_SPECIFY_NEXT_APPROVER,
nextApprover);
}
task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + taskId, APPROVED.getStatus());
executeSynchronous(task, taskService, runtimeService, commandContext);
return null;
}
private void executeSynchronous(Task task, TaskService taskService, RuntimeService runtimeService, CommandContext commandContext) {
ExtAxBpmnFormRelationService bpmnFormRelationService = SpringContextUtils.getBean(ExtAxBpmnFormRelationService.class);
ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(task.getProcessDefinitionId());
if (Objects.nonNull(relation)) {
FormEngineConfigurationApi formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration(commandContext);
FormRepositoryService formRepositoryService = formEngineConfiguration.getFormRepositoryService();
FormDefinition formDefinition = formRepositoryService.createFormDefinitionQuery()
.formDefinitionKey(relation.getKey())
.deploymentId(relation.getFormDeploymentId())
.singleResult();
Authentication.setAuthenticatedUserId(approver.buildAssigneeId());
taskService.completeTaskWithForm(taskId, formDefinition.getId(), null, formVariables);
Authentication.setAuthenticatedUserId(null);
} else {
if (StringUtils.hasText(task.getExecutionId())) {
// 正常完成流程任务审批通过
taskService.complete(task.getId(), runtimeService.getVariables(task.getExecutionId()));
} else {
// 特殊的完成单独创建的任务
taskService.complete(task.getId());
}
}
}
}

View File

@ -0,0 +1,151 @@
package cn.axzo.workflow.core.engine.cmd;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.common.engine.api.scope.ScopeTypes;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.cmd.NeedsActiveTaskCmd;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.impl.util.TaskHelper;
import org.flowable.form.api.FormFieldHandler;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.api.FormService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import java.util.Map;
/**
* 重写 org.flowable.engine.impl.cmd.CompleteTaskWithFormCmd
*
* @author wangli
* @since 2024-11-08 14:46
*/
public class CustomCompleteTaskWithFormCmd extends NeedsActiveTaskCmd<Void> {
private static final long serialVersionUID = 1L;
protected String formDefinitionId;
protected String outcome;
protected Map<String, Object> formVariables;
protected Map<String, Object> variables;
protected Map<String, Object> variablesLocal;
protected Map<String, Object> transientVariables;
protected Map<String, Object> transientVariablesLocal;
public CustomCompleteTaskWithFormCmd(String taskId, String formDefinitionId, String outcome, Map<String, Object> formVariables) {
super(taskId);
this.formDefinitionId = formDefinitionId;
this.outcome = outcome;
this.formVariables = formVariables;
}
public CustomCompleteTaskWithFormCmd(String taskId, String formDefinitionId, String outcome, Map<String, Object> formVariables, Map<String, Object> variables) {
super(taskId);
this.formDefinitionId = formDefinitionId;
this.outcome = outcome;
this.formVariables = formVariables;
this.variables = variables;
}
public CustomCompleteTaskWithFormCmd(String taskId, String formDefinitionId, String outcome,
Map<String, Object> formVariables, Map<String, Object> variables, boolean localScope) {
this(taskId, formDefinitionId, outcome, formVariables, variables);
if (localScope) {
this.variablesLocal = variables;
} else {
this.variables = variables;
}
}
public CustomCompleteTaskWithFormCmd(String taskId, String formDefinitionId, String outcome,
Map<String, Object> formVariables, Map<String, Object> variables, Map<String, Object> transientVariables) {
this(taskId, formDefinitionId, outcome, formVariables, variables);
this.transientVariables = transientVariables;
}
public CustomCompleteTaskWithFormCmd(String taskId, String formDefinitionId, String outcome, Map<String, Object> formVariables, Map<String,
Object> variables, Map<String, Object> variablesLocal, Map<String, Object> transientVariables, Map<String, Object> transientVariablesLocal) {
this(taskId, formDefinitionId, outcome, formVariables, variables);
this.variablesLocal = variablesLocal;
this.transientVariables = transientVariables;
this.transientVariablesLocal = transientVariablesLocal;
}
@Override
protected Void execute(CommandContext commandContext, TaskEntity task) {
if (StringUtils.isNotEmpty(task.getScopeId()) && ScopeTypes.CMMN.equals(task.getScopeType())) {
throw new FlowableException("The task instance is created by the cmmn engine and should be completed via the cmmn engine API");
}
FormService formService = CommandContextUtil.getFormService();
if (formService == null) {
throw new FlowableIllegalArgumentException("Form engine is not initialized");
}
FormRepositoryService formRepositoryService = CommandContextUtil.getFormRepositoryService();
FormInfo formInfo = formRepositoryService.getFormModelById(formDefinitionId);
Map<String, Object> formVariables;
boolean local = variablesLocal != null && !variablesLocal.isEmpty();
if (local) {
formVariables = variablesLocal;
} else {
formVariables = variables;
}
Map<String, Object> taskVariables = null;
if (formInfo != null) {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(commandContext);
FormFieldHandler formFieldHandler = processEngineConfiguration.getFormFieldHandler();
if (isFormFieldValidationEnabled(task, processEngineConfiguration, task.getProcessDefinitionId(), task.getTaskDefinitionKey())) {
formService.validateFormFields(formInfo, formVariables);
}
// Extract raw variables and complete the task
taskVariables = formService.getVariablesFromFormSubmission(formInfo, formVariables, outcome);
// The taskVariables are the variables that should be used when completing the task
// the actual variables should instead be used when saving the form instances
if (task.getProcessInstanceId() != null) {
formService.saveFormInstance(formVariables, formInfo, task.getId(), task.getProcessInstanceId(),
task.getProcessDefinitionId(), task.getTenantId(), outcome);
} else {
formService.saveFormInstanceWithScopeId(formVariables, formInfo, task.getId(), task.getScopeId(), task.getScopeType(),
task.getScopeDefinitionId(), task.getTenantId(), outcome);
}
formFieldHandler.handleFormFieldsOnSubmit(formInfo, task.getId(), task.getProcessInstanceId(), null, null, taskVariables, task.getTenantId());
}
// Only one set of variables can be used as form submission.
// When variablesLocal are present then they have precedence and those are used for the completion
if (local) {
TaskHelper.completeTask(task, variables, taskVariables, transientVariables, transientVariablesLocal, commandContext);
} else {
TaskHelper.completeTask(task, taskVariables, variablesLocal, transientVariables, transientVariablesLocal, commandContext);
}
return null;
}
protected boolean isFormFieldValidationEnabled(TaskEntity task, ProcessEngineConfigurationImpl processEngineConfiguration, String processDefinitionId,
String taskDefinitionKey) {
if (processEngineConfiguration.isFormFieldValidationEnabled()) {
UserTask userTask = (UserTask) ProcessDefinitionUtil.getBpmnModel(processDefinitionId).getFlowElement(taskDefinitionKey);
String formFieldValidationExpression = userTask.getValidateFormFields();
return TaskHelper.isFormFieldValidationEnabled(task, processEngineConfiguration, formFieldValidationExpression);
}
return false;
}
@Override
protected String getSuspendedTaskException() {
return "Cannot complete a suspended task";
}
}

View File

@ -1,6 +1,7 @@
package cn.axzo.workflow.core.repository.entity;
import cn.axzo.framework.data.mybatisplus.model.BaseEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -21,6 +22,7 @@ public class ExtAxBpmnFormRelation extends BaseEntity<ExtAxBpmnFormRelation> {
/**
* 业务标识
*/
@TableField(value = "`key`")
private String key;
/**
* bpmn 模型 ID

View File

@ -5,6 +5,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
@ -42,6 +43,7 @@ public interface BpmnProcessTaskService {
*/
void approveTask(BpmnTaskAuditDTO taskAuditDTO);
void approveTaskWithForm(BpmnTaskAuditWithFormDTO taskAuditDto);
/**
* 回退
*/

View File

@ -1,6 +1,10 @@
package cn.axzo.workflow.core.service;
import cn.axzo.workflow.common.model.dto.BpmnFormRelationCreateDTO;
import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import java.util.List;
/**
* ext_ax_bpmn_form_relation
@ -11,4 +15,8 @@ import cn.axzo.workflow.common.model.dto.BpmnFormRelationCreateDTO;
public interface ExtAxBpmnFormRelationService {
Long insert(BpmnFormRelationCreateDTO dto);
ExtAxBpmnFormRelation queryByBpmnDefinitionId(String bpmnDefinitionId);
List<ExtAxBpmnFormRelation> genericQuery(BpmnFormRelationSearchDTO dto);
}

View File

@ -44,10 +44,12 @@ import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCarbonCopyUserSelectorCmd;
import cn.axzo.workflow.core.engine.cmd.CustomForecastUserTaskAssigneeCmd;
import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessInstanceService;
import cn.axzo.workflow.core.service.CategoryService;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import cn.axzo.workflow.core.service.converter.BpmnHistoricProcessInstanceConverter;
@ -213,6 +215,8 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
private BpmnProcessInstanceService bpmnProcessInstanceService;
@Resource
private ExtAxProcessLogService processLogService;
@Resource
private ExtAxBpmnFormRelationService bpmnFormRelationService;
@Override
public HistoricProcessInstance getProcessInstanceByBusinessKey(String businessKey, @Nullable String tenantId,
@ -366,12 +370,13 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
.variables(dto.getVariables())
.name(name)
.overrideProcessDefinitionTenantId(dto.getTenantId());
if (!CollectionUtils.isEmpty(dto.getStartFormVariables())) {
instanceBuilder.startFormVariables(dto.getStartFormVariables());
}
if (org.springframework.util.StringUtils.hasText(dto.getOutcome())) {
instanceBuilder.outcome(dto.getOutcome());
ExtAxBpmnFormRelation relation = bpmnFormRelationService.queryByBpmnDefinitionId(definition.getId());
if (Objects.nonNull(relation)) {
// 如果模型没有绑定表单则强制情况表单相关属性避免报错
instanceBuilder.startFormVariables(null).outcome(null);
}
ProcessInstance instance;
if (dto.getAsync()) {
// 异步开始

View File

@ -4,12 +4,14 @@ import cn.axzo.framework.domain.ServiceException;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
@ -26,10 +28,10 @@ import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstance
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskWithFormCmd;
import cn.axzo.workflow.core.engine.cmd.CustomBackTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomBackTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCommentTaskCmd;
@ -47,6 +49,7 @@ import cn.axzo.workflow.core.engine.event.MessagePushEventType;
import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessTaskService;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter;
import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter;
@ -77,7 +80,9 @@ import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Attachment;
import org.flowable.engine.task.Comment;
import org.flowable.form.api.FormDefinition;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
@ -111,6 +116,13 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_TASK_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.FIND_TASK_BY_PERSON_ID_ERROR;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.REACHED_BACKED_MAXIMUM_NUM;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_REMIND_ERROR_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_COMMENT_EXT;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
@ -131,16 +143,9 @@ import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETE
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.valueOfStatus;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_TASK_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.FIND_TASK_BY_PERSON_ID_ERROR;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.REACHED_BACKED_MAXIMUM_NUM;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_REMIND_ERROR_NOT_EXISTS;
import static cn.axzo.workflow.core.common.utils.BpmnCollectionUtils.convertSet;
import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.countSql;
import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.sqlConnectors;
import static cn.axzo.workflow.core.common.utils.BpmnCollectionUtils.convertSet;
@Service
@Slf4j
@ -177,6 +182,10 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
private BpmnProcessTaskService bpmnProcessTaskService;
@Resource
private BpmnProcessDefinitionService bpmnProcessModelService;
@Resource
private FormRepositoryService formRepositoryService;
@Resource
private ExtAxBpmnFormRelationService bpmnFormRelationService;
@Override
public BpmPageResult<BpmnTaskTodoPageItemVO> getTodoTaskPage(BpmnTaskPageSearchDTO dto) {
@ -184,8 +193,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
Long resultTotalCount;
if (CollectionUtils.isEmpty(dto.getResults())) {
HistoricTaskInstanceQuery query =
historyService.createHistoricTaskInstanceQuery().unfinished().taskAssignee(dto.getUserId()) // 分配给自己
.orderByTaskCreateTime().desc();
historyService.createHistoricTaskInstanceQuery().unfinished().taskAssignee(dto.getUserId()) // 分配给自己
.orderByTaskCreateTime().desc();
populateQuery(dto, query);
tasks = query.listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
resultTotalCount = query.count();
@ -204,7 +213,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
// 获得 ProcessInstance Map
Map<String, ProcessInstance> processInstanceMap =
processInstanceService.getProcessInstanceMap(processInstanceIds);
processInstanceService.getProcessInstanceMap(processInstanceIds);
List<BpmnTaskTodoPageItemVO> vos = todoPageItemConverter.toVos(tasks, processInstanceMap);
return new BpmPageResult<>(vos, resultTotalCount);
@ -218,8 +227,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
if (CollectionUtils.isEmpty(dto.getResults())) {
// 查询已办任务
HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().finished() // 已完成
.taskAssignee(dto.getUserId()) // 分配给自己
.orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
.taskAssignee(dto.getUserId()) // 分配给自己
.orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
populateQuery(dto, query);
// 执行查询
tasks = query.listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
@ -236,7 +245,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
}
Set<String> processInstanceIds = convertSet(tasks, HistoricTaskInstance::getProcessInstanceId);
Map<String, HistoricProcessInstance> historicProcessInstanceMap =
processInstanceService.getHistoricProcessInstanceMap(processInstanceIds);
processInstanceService.getHistoricProcessInstanceMap(processInstanceIds);
List<BpmnTaskDonePageItemVO> vos = donePageItemConverter.toVos(tasks, historicProcessInstanceMap);
return new BpmPageResult<>(vos, resultTotalCount);
}
@ -260,7 +269,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
NativeHistoricTaskInstanceQuery nativeQuery = historyService.createNativeHistoricTaskInstanceQuery();
String tableName = managementService.getTableName(HistoricTaskInstance.class);
baseQuerySql.append("SELECT a.* FROM ").append(tableName).append(" a JOIN " + "ACT_HI_PROCINST b").append(" " +
"ON b.PROC_INST_ID_ = a.PROC_INST_ID_");
"ON b.PROC_INST_ID_ = a.PROC_INST_ID_");
if (Objects.nonNull(dto.getTenantId())) {
baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" b.TENANT_ID_ = #{tenantId}").append(sqlConnectors(baseQuerySql)).append(" a.TENANT_ID_ = #{tenantId}");
@ -279,7 +288,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
}
if (StringUtils.hasLength(dto.getCategories())) {
List<String> categories =
Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList());
Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList());
baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" a.CATEGORY_ in (");
for (int i = 0; i < categories.size(); i++) {
baseQuerySql.append("#{category").append(i).append("}");
@ -320,7 +329,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
}
if (StringUtils.hasLength(dto.getCategories())) {
List<String> categories =
Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList());
Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList());
query.processCategoryIn(categories);
}
}
@ -336,6 +345,13 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void approveTaskWithForm(BpmnTaskAuditWithFormDTO dto) {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
commandExecutor.execute(new CustomApproveTaskWithFormCmd(dto));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void backTask(BpmnTaskBackAuditDTO dto) {
@ -348,17 +364,17 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId());
}
Optional<String> instOpt = backOptionalNodes.stream()
.map(BpmnOptionalNodeDTO::getProcessInstanceId)
.filter(StringUtils::hasText)
.findAny();
.map(BpmnOptionalNodeDTO::getProcessInstanceId)
.filter(StringUtils::hasText)
.findAny();
if (instOpt.isPresent()) {
ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO();
searchDTO.setProcessInstanceId(instOpt.get());
List<ExtAxHiTaskInst> extAxHiTaskInsts = extAxHiTaskInstService.queryList(searchDTO);
if (CollectionUtils.isNotEmpty(extAxHiTaskInsts)) {
long backOpeCount = extAxHiTaskInsts.stream()
.filter(ext -> BACKED.getStatus().equals(ext.getStatus()))
.count();
.filter(ext -> BACKED.getStatus().equals(ext.getStatus()))
.count();
if (backOpeCount > MAX_BACKED_OPERATE_COUNT) {
throw new WorkflowEngineException(REACHED_BACKED_MAXIMUM_NUM, String.valueOf(MAX_BACKED_OPERATE_COUNT));
}
@ -387,8 +403,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
}
List<BpmnHistoricTaskInstanceVO> tasks = this.getHistoricTaskListByProcessInstanceId(processInstanceId, null);
tasks = tasks.stream()
.filter(t -> t.getNodeType() == NODE_STARTER || t.getNodeType() == NODE_TASK || t.getNodeType() == NODE_BUSINESS)
.collect(Collectors.toList());
.filter(t -> t.getNodeType() == NODE_STARTER || t.getNodeType() == NODE_TASK || t.getNodeType() == NODE_BUSINESS)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(tasks)) {
return Collections.emptyList();
}
@ -408,15 +424,15 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
for (Pair<String, List<BpmnHistoricTaskInstanceVO>> pair : executedList) {
List<BpmnHistoricTaskInstanceVO> taskInstanceVOList = pair.getRight();
Optional<BpmnHistoricTaskInstanceVO> backTaskOpt = taskInstanceVOList
.stream()
.filter(t -> t.getResult() != null)
.filter(t -> t.getResult() == BpmnProcessInstanceResultEnum.BACKED)
.findFirst();
.stream()
.filter(t -> t.getResult() != null)
.filter(t -> t.getResult() == BpmnProcessInstanceResultEnum.BACKED)
.findFirst();
if (backTaskOpt.isPresent()) {
String deleteReason = backTaskOpt.get().getDeleteReason();
String changeParentActivityTo = deleteReason
.replace("Change parent activity to ", "")
.replace("Change activity to ", "");
.replace("Change parent activity to ", "")
.replace("Change activity to ", "");
if (org.springframework.util.CollectionUtils.isEmpty(valuableList)) {
throw new ServiceException("状态异常,首个节点进行了退回操作");
}
@ -436,25 +452,25 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
Map<String, FlowElement> flowElementMap = flowElements.stream().collect(Collectors.toMap(BaseElement::getId, f -> f));
AtomicInteger index = new AtomicInteger(0);
List<BpmnOptionalNodeDTO> resultList = valuableList
.stream()
.filter(pair -> !task.getTaskDefinitionKey().equals(pair.getLeft())) //排除当前节点
.map(pair -> flowElementMap.get(pair.getLeft()))
.filter(flowElement -> {
BpmnFlowNodeType currentNodeType = BpmnMetaParserHelper.getNodeType(flowElement).orElse(NODE_EMPTY);
return currentNodeType == NODE_TASK || currentNodeType == NODE_BUSINESS;
})
.filter(flowElement -> !NODE_STARTER.getType().equals(flowElement.getId()))
.map(flowElement -> BpmnOptionalNodeDTO
.builder()
.processInstanceId(task.getProcessInstanceId())
.processDefinitionId(task.getProcessDefinitionId())
.processActivityId(flowElement.getId())
.processActivityName(flowElement.getName())
.processNodeDesc(flowElement.getName())
.nodeType(BpmnMetaParserHelper.getNodeType(flowElement).orElse(NODE_EMPTY))
.ordinal(index.incrementAndGet())
.build())
.collect(Collectors.toList());
.stream()
.filter(pair -> !task.getTaskDefinitionKey().equals(pair.getLeft())) //排除当前节点
.map(pair -> flowElementMap.get(pair.getLeft()))
.filter(flowElement -> {
BpmnFlowNodeType currentNodeType = BpmnMetaParserHelper.getNodeType(flowElement).orElse(NODE_EMPTY);
return currentNodeType == NODE_TASK || currentNodeType == NODE_BUSINESS;
})
.filter(flowElement -> !NODE_STARTER.getType().equals(flowElement.getId()))
.map(flowElement -> BpmnOptionalNodeDTO
.builder()
.processInstanceId(task.getProcessInstanceId())
.processDefinitionId(task.getProcessDefinitionId())
.processActivityId(flowElement.getId())
.processActivityName(flowElement.getName())
.processNodeDesc(flowElement.getName())
.nodeType(BpmnMetaParserHelper.getNodeType(flowElement).orElse(NODE_EMPTY))
.ordinal(index.incrementAndGet())
.build())
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(resultList)) {
BpmnOptionalNodeDTO bpmnOptionalNodeDTO = resultList.get(resultList.size() - 1);
bpmnOptionalNodeDTO.setProcessNodeDesc(bpmnOptionalNodeDTO.getProcessActivityName() + "(上一步)");
@ -488,13 +504,13 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
public List<BpmnHistoricTaskInstanceVO> getHistoricTaskListByProcessInstanceId(String processInstanceId,
String tenantId) {
HistoricTaskInstanceQuery query =
historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId);
historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId);
if (StringUtils.hasLength(tenantId)) {
query.taskTenantId(tenantId);
}
HistoricProcessInstanceQuery instanceQuery =
historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId);
historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId);
// .includeProcessVariables();
if (StringUtils.hasLength(tenantId)) {
instanceQuery.processInstanceTenantId(tenantId);
@ -505,35 +521,35 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
}
List<HistoricTaskInstance> taskInstances = query.orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
.list();
.list();
taskInstances.forEach(task -> ((HistoricTaskInstanceEntity) task).setCreateTime(((HistoricTaskInstanceEntity) task).getLastUpdateTime()));
taskInstances.sort(Comparator.comparing(p -> ((HistoricTaskInstanceEntity) p).getLastUpdateTime()));
Map<String, HistoricVariableInstance> variableInstanceMap =
// 不能使用框架提供的历史变量 API 查询, BUG
historyService.createNativeHistoricVariableInstanceQuery()
.sql("select * from ACT_HI_VARINST t where t.proc_inst_id_= #{processInstanceId}")
.parameter("processInstanceId", processInstanceId)
.list().stream()
.collect(Collectors.toMap(HistoricVariableInstance::getVariableName, Function.identity(),
(s, t) -> s));
// 不能使用框架提供的历史变量 API 查询, BUG
historyService.createNativeHistoricVariableInstanceQuery()
.sql("select * from ACT_HI_VARINST t where t.proc_inst_id_= #{processInstanceId}")
.parameter("processInstanceId", processInstanceId)
.list().stream()
.collect(Collectors.toMap(HistoricVariableInstance::getVariableName, Function.identity(),
(s, t) -> s));
HistoricVariableInstance instanceVersion = variableInstanceMap.getOrDefault(WORKFLOW_ENGINE_VERSION, null);
// 过滤了多实例或签自动完成的任务
List<BpmnHistoricTaskInstanceVO> vos = historicTaskInstanceConverter.toVosSkipSystemOperation(taskInstances,
Objects.isNull(instanceVersion) ? null :
((HistoricVariableInstanceEntityImpl) instanceVersion).getTextValue());
Objects.isNull(instanceVersion) ? null :
((HistoricVariableInstanceEntityImpl) instanceVersion).getTextValue());
Map<String, List<Comment>> commentByTaskIdMap =
taskService.getProcessInstanceComments(processInstanceId).stream()
.collect(Collectors.groupingBy(Comment::getTaskId));
taskService.getProcessInstanceComments(processInstanceId).stream()
.collect(Collectors.groupingBy(Comment::getTaskId));
Map<String, List<Attachment>> attachmentByTaskIdMap =
taskService.getProcessInstanceAttachments(processInstanceId).stream()
.collect(Collectors.groupingBy(Attachment::getTaskId));
taskService.getProcessInstanceAttachments(processInstanceId).stream()
.collect(Collectors.groupingBy(Attachment::getTaskId));
ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO();
searchDTO.setProcessInstanceId(processInstanceId);
Map<String, ExtAxHiTaskInst> extTaskInstMap = extAxHiTaskInstService.queryList(searchDTO).stream()
.collect(Collectors.toMap(ExtAxHiTaskInst::getTaskId, Function.identity(), (s, t) -> s));
.collect(Collectors.toMap(ExtAxHiTaskInst::getTaskId, Function.identity(), (s, t) -> s));
BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
List<BpmnHistoricTaskInstanceVO> resultList = new ArrayList<>();
@ -548,29 +564,29 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
// 处理 advice
taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId()))
.filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_ADVICE)).findFirst()
.ifPresent(i -> vo.setAdvice(i.getFullMessage()));
.filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_ADVICE)).findFirst()
.ifPresent(i -> vo.setAdvice(i.getFullMessage()));
// 处理 operationDesc
taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId()))
.filter(i -> Objects.equals(COMMENT_TYPE_OPERATION_DESC, i.getType()))
.max(Comparator.comparing(Comment::getTime))
.ifPresent(i -> vo.setOperationDesc(i.getFullMessage()));
.filter(i -> Objects.equals(COMMENT_TYPE_OPERATION_DESC, i.getType()))
.max(Comparator.comparing(Comment::getTime))
.ifPresent(i -> vo.setOperationDesc(i.getFullMessage()));
// 处理 CommentExt
taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId()))
.filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_COMMENT_EXT)).findFirst()
.ifPresent(i -> vo.setCommentExt(i.getFullMessage()));
.filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_COMMENT_EXT)).findFirst()
.ifPresent(i -> vo.setCommentExt(i.getFullMessage()));
List<Attachment> attachments = attachmentByTaskIdMap.getOrDefault(vo.getTaskId(), Collections.emptyList());
vo.setAttachments(attachmentConverter.toVos(attachments));
HistoricVariableInstance assginerSnapshot =
variableInstanceMap.getOrDefault(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + vo.getTaskId(),
null);
variableInstanceMap.getOrDefault(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + vo.getTaskId(),
null);
if (Objects.isNull(assginerSnapshot)) {
assginerSnapshot =
variableInstanceMap.getOrDefault(OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + vo.getTaskId(), null);
variableInstanceMap.getOrDefault(OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + vo.getTaskId(), null);
}
BpmnTaskDelegateAssigner assigner = null;
@ -584,15 +600,15 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
vo.setNodeType(nodeType);
if (Objects.equals(NODE_CARBON_COPY, nodeType)) {
HistoricVariableInstance carbonUsers =
variableInstanceMap.getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + vo.getTaskDefinitionKey(), null);
variableInstanceMap.getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + vo.getTaskDefinitionKey(), null);
vo.setForecastAssignees(Objects.isNull(carbonUsers) ? Collections.emptyList() :
(List<BpmnTaskDelegateAssigner>) carbonUsers.getValue());
(List<BpmnTaskDelegateAssigner>) carbonUsers.getValue());
} else {
vo.setForecastAssignees(Collections.emptyList());
}
});
BpmnMetaParserHelper.getApprovalMethod(bpmnModel.getFlowElement(vo.getTaskDefinitionKey()))
.ifPresent(vo::setApprovalMethod);
.ifPresent(vo::setApprovalMethod);
}
return resultList;
}
@ -606,11 +622,11 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
*/
@Override
public List<BpmnHistoricTaskInstanceGroupVO> getHistoricTaskListGroupByProcessInstanceId(String processInstanceId
, String tenantId) {
, String tenantId) {
List<BpmnHistoricTaskInstanceVO> vos = getHistoricTaskListByProcessInstanceId(processInstanceId, tenantId);
Map<String, List<BpmnHistoricTaskInstanceVO>> voMapByTaskDefKey =
vos.stream().collect(Collectors.groupingBy(BpmnHistoricTaskInstanceVO::getTaskDefinitionKey));
vos.stream().collect(Collectors.groupingBy(BpmnHistoricTaskInstanceVO::getTaskDefinitionKey));
List<BpmnHistoricTaskInstanceGroupVO> groupVos = new ArrayList<>();
for (Map.Entry<String, List<BpmnHistoricTaskInstanceVO>> entry : voMapByTaskDefKey.entrySet()) {
@ -648,9 +664,9 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
}
List<BpmnTaskInstanceVO> vos = bpmnTaskConverter.toVos(query.list());
List<String> snapshotTaskIds =
vos.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).collect(Collectors.toList());
vos.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).collect(Collectors.toList());
Map<String, VariableInstance> instanceMap = runtimeService.getVariableInstances(processInstanceId,
snapshotTaskIds);
snapshotTaskIds);
vos.forEach(i -> i.setAssigner(BpmnTaskDelegateAssigner.toObjectCompatible(instanceMap.get(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).getValue())));
return vos;
}
@ -684,7 +700,7 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
commandExecutor.execute(new CustomTransferUserTaskAsyncCmd(dto));
} else {
commandExecutor.execute(new CustomTransferUserTaskCmd(dto.getTaskId(),
dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssigner(), dto.getAdditionalOpeDesc()));
dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssigner(), dto.getAdditionalOpeDesc()));
}
}
@ -726,8 +742,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
public void commentTask(BpmnTaskCommentDTO dto) {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
commandExecutor.execute(new CustomCommentTaskCmd(dto.getProcessInstanceId(),
dto.getOperator(), dto.getComment(), dto.getCommentExt(), dto.getAttachmentList(),
extAxHiTaskInstService));
dto.getOperator(), dto.getComment(), dto.getCommentExt(), dto.getAttachmentList(),
extAxHiTaskInstService));
}
@Override
@ -735,8 +751,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
public void attachmentTask(BpmnTaskAttachmentDTO dto, String assignee) {
Authentication.setAuthenticatedUserId(assignee);
Attachment attachment = taskService.createAttachment(dto.getType(), dto.getTaskId(),
dto.getProcessInstanceId(), dto.getName(), dto.getDescription(),
dto.getUrl());
dto.getProcessInstanceId(), dto.getName(), dto.getDescription(),
dto.getUrl());
taskService.saveAttachment(attachment);
Authentication.setAuthenticatedUserId(null);
}
@ -750,8 +766,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
commandExecutor.execute(new CustomCountersignUserTaskAsyncCmd(dto));
} else {
commandExecutor.execute(new CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum.valueOfType(dto.getCountersignType()), dto.getTaskId(),
dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(),
extAxHiTaskInstService));
dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(),
extAxHiTaskInstService));
}
}
@ -772,22 +788,22 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();
ProcessInstance processInstance =
runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult();
runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult();
Optional<BpmnNoticeConf> noticeConfig =
BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId()));
BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId()));
List<BpmnTaskDelegateAssigner> assigners =
(List<BpmnTaskDelegateAssigner>) runtimeService.getVariable(dto.getProcessInstanceId(),
INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + dto.getTaskDefinitionKey());
(List<BpmnTaskDelegateAssigner>) runtimeService.getVariable(dto.getProcessInstanceId(),
INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + dto.getTaskDefinitionKey());
// 过滤出未审批的任何,用选择的方式去发送消息
dto.getRemindTypes().forEach(type -> {
list.forEach(task -> {
assigners.stream().filter(i -> Objects.equals(task.getAssignee(), i.buildAssigneeId())).findAny().ifPresent(assigner -> {
MessagePushEventImpl event = MessagePushEventBuilder.createEvent(MessagePushEventType.valueOf(type),
Lists.newArrayList(assigner), noticeConfig.orElse(null),
processInstance.getProcessInstanceId(),
processInstance.getProcessDefinitionKey(),
processInstance.getTenantId(), task.getId());
Lists.newArrayList(assigner), noticeConfig.orElse(null),
processInstance.getProcessInstanceId(),
processInstance.getProcessDefinitionKey(),
processInstance.getTenantId(), task.getId());
event.setProcessInstanceId(processInstance.getProcessInstanceId());
event.setTenantId(processInstance.getTenantId());
event.setTaskId(task.getId());
@ -802,8 +818,8 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
public String createRobotTask(BpmnRobotTaskCreateDTO dto) {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
return commandExecutor.execute(new CustomCreateDummyTaskCmd(dto.getProcessInstanceId(),
dto.getRobotNode().getFlowNodeName(), dto.getRobotNode().getOperationDesc(), dto.getApprover(),
extAxHiTaskInstService));
dto.getRobotNode().getFlowNodeName(), dto.getRobotNode().getOperationDesc(), dto.getApprover(),
extAxHiTaskInstService));
}
@Override
@ -811,17 +827,17 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
public void completeRobotTask(BpmnRobotTaskCompleteDTO dto) {
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
commandExecutor.execute(new CustomCompleteDummyTaskCmd(dto.getProcessInstanceId(), dto.getTaskId(),
Objects.isNull(dto.getRobotNode()) ? null : dto.getRobotNode().getFlowNodeName(),
Objects.isNull(dto.getRobotNode()) ? null : dto.getRobotNode().getOperationDesc(),
extAxHiTaskInstService));
Objects.isNull(dto.getRobotNode()) ? null : dto.getRobotNode().getFlowNodeName(),
Objects.isNull(dto.getRobotNode()) ? null : dto.getRobotNode().getOperationDesc(),
extAxHiTaskInstService));
}
@Override
public String findTaskIdByInstanceIdAndPersonId(String processInstanceId, String personId) {
List<Task> list = taskService.createTaskQuery().processInstanceId(processInstanceId)
.taskAssigneeLike("%" + personId)
.active()
.list();
.taskAssigneeLike("%" + personId)
.active()
.list();
if (CollectionUtils.isEmpty(list) || list.size() > 1) {
throw new WorkflowEngineException(FIND_TASK_BY_PERSON_ID_ERROR, processInstanceId, personId);
}
@ -831,9 +847,9 @@ public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {
@Override
public Map<String, String> findTaskIdByInstanceIdsAndPersonId(List<String> processInstanceIds, String personId) {
List<Task> tasks = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds)
.taskAssigneeLike("%" + personId)
.active()
.list();
.taskAssigneeLike("%" + personId)
.active()
.list();
if (CollectionUtils.isEmpty(tasks)) {
return new HashMap<>();
}

View File

@ -1,15 +1,17 @@
package cn.axzo.workflow.core.service.impl;
import cn.axzo.workflow.common.model.dto.BpmnFormRelationCreateDTO;
import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.repository.mapper.ExtAxBpmnFormRelationMapper;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.List;
/**
* bpmn 模型与表单模型的关联关系
@ -33,4 +35,23 @@ public class ExtAxBpmnFormRelationServiceImpl implements ExtAxBpmnFormRelationSe
return relation.getId();
}
@Override
public ExtAxBpmnFormRelation queryByBpmnDefinitionId(String bpmnDefinitionId) {
BpmnFormRelationSearchDTO searchDTO = new BpmnFormRelationSearchDTO();
searchDTO.setBpmnDefinitionId(bpmnDefinitionId);
return bpmnFormRelationMapper.selectOne(buildQueryWrapper(searchDTO));
}
@Override
public List<ExtAxBpmnFormRelation> genericQuery(BpmnFormRelationSearchDTO dto) {
return bpmnFormRelationMapper.selectList(buildQueryWrapper(dto));
}
LambdaQueryWrapper<ExtAxBpmnFormRelation> buildQueryWrapper(BpmnFormRelationSearchDTO dto) {
return new LambdaQueryWrapper<ExtAxBpmnFormRelation>()
.eq(StringUtils.hasText(dto.getKey()), ExtAxBpmnFormRelation::getKey, dto.getKey())
.eq(StringUtils.hasText(dto.getBpmnDefinitionId()), ExtAxBpmnFormRelation::getBpmnDefinitionId, dto.getBpmnDefinitionId())
.eq(StringUtils.hasText(dto.getFormDeploymentId()), ExtAxBpmnFormRelation::getFormDeploymentId, dto.getFormDeploymentId())
;
}
}

View File

@ -7,9 +7,12 @@ import cn.axzo.workflow.form.service.FormDefinitionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.impl.util.IoUtil;
import org.flowable.engine.FormService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.form.api.FormDefinition;
import org.flowable.form.api.FormDefinitionQuery;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormRepositoryService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@ -36,6 +39,10 @@ public class FormDefinitionServiceImpl implements FormDefinitionService {
private FormRepositoryService formRepositoryService;
@Resource
private RepositoryService repositoryService;
@Resource
private FormService formService;
@Resource
private RuntimeService runtimeService;
@Override
public FormDefinitionVO getDraft(FromDefinitionSearchDTO dto) {
@ -69,4 +76,5 @@ public class FormDefinitionServiceImpl implements FormDefinitionService {
return JSON.parseObject(new String(bytes, StandardCharsets.UTF_8), FormDefinitionVO.class);
}
}

View File

@ -13,11 +13,15 @@ import cn.azxo.framework.common.model.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.util.IoUtil;
import org.flowable.engine.FormService;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.form.StartFormData;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormRepositoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
@ -27,6 +31,7 @@ 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.io.InputStream;
import java.util.Date;
import java.util.List;
@ -63,6 +68,10 @@ public class TestController {
private ProcessInstanceApi processInstanceApi;
@Autowired
private BpmnProcessInstanceService bpmnProcessInstanceService;
@Resource
private FormService formService;
@Resource
private FormRepositoryService formRepositoryService;
// @Autowired
// private WorkflowCoreService workflowCoreService;
// @Autowired
@ -254,4 +263,13 @@ public class TestController {
}
return ShellUtil.grepRealTimeLog(profile, keyword);
}
@GetMapping("form")
public CommonResponse<FormInfo> getForm(@RequestParam String definitionId, @RequestParam(required = false) String processInstanceId) {
// FormInfo startFormModel = bpmnProcessInstanceService.getStartFormModel(definitionId, processInstanceId);
StartFormData startFormData = formService.getStartFormData(definitionId);
FormInfo formModelByKey = formRepositoryService.getFormModelByKey("we");
FormInfo startFormModel = runtimeService.getStartFormModel(definitionId, processInstanceId);
return CommonResponse.success(null);
}
}

View File

@ -8,6 +8,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
@ -111,6 +112,23 @@ public class BpmnProcessTaskController extends BasicPopulateAvatarController imp
return success(true);
}
@PostMapping("/approveWithForm")
public CommonResponse<Boolean> approveTaskWithForm(@Validated @RequestBody BpmnTaskAuditWithFormDTO dto) {
log.info("同意 approveTask===>>>参数:{}", JSON.toJSONString(dto));
List<AttachmentDTO> tempAttachments = ListUtils.defaultIfNull(dto.getAttachmentList(), new ArrayList<>());
if (StringUtils.hasText(dto.getSignatureUrl())) {
AttachmentDTO signature = new AttachmentDTO();
signature.setType(AttachmentTypeEnum.signature);
signature.setName(dto.getApprover().getAssignerName() + "的签名");
signature.setUrl(dto.getSignatureUrl());
tempAttachments.add(signature);
dto.setAttachmentList(tempAttachments);
}
populateUsersAvatar(dto.getApprover());
bpmnProcessTaskService.approveTaskWithForm(dto);
return success(true);
}
/**
* 批量同意
*/

View File

@ -48,6 +48,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
@ -117,7 +118,7 @@ public interface WorkflowCoreService {
@Operation(summary = "创建审批流程, MQ 触发规则:1. 当前流程实例会依次触发 process-instance-created 和 process-instance-started 事件,2. 第一个审批任务会依次触发 process-task-assigned 和 process-task-created 事件")
@PostMapping("/api/process/instance/create")
@InvokeMode(SYNC)
String createProcessInstance(@Validated @RequestBody BpmnProcessInstanceCreateDTO dto);
String createProcessInstance(@Validated @RequestBody BpmnProcessInstanceCreateWithFormDTO dto);
/**
* 发起人主动撤回审核
@ -215,6 +216,16 @@ public interface WorkflowCoreService {
@PostMapping("/api/process/task/approve")
Boolean approveTask(@Validated @RequestBody BpmnTaskAuditDTO dto);
/**
* 同意时并提交表单数据
*
* @param dto
* @return
*/
@Operation(summary = "同意时并提交表单")
@PostMapping("/api/process/task/form/approve")
Boolean approveTaskWithForm(@Validated @RequestBody BpmnTaskAuditWithFormDTO dto);
/**
* 批量同意
*
@ -227,6 +238,7 @@ public interface WorkflowCoreService {
/**
* 获取当前节点可回退节点选项列表
*
* @param taskId 当前任务id
* @return 可以回退节点列表
*/
@ -236,6 +248,7 @@ public interface WorkflowCoreService {
/**
* 回退到指定节点
*
* @param dto
* @return
*/

View File

@ -71,6 +71,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
@ -341,18 +342,6 @@ public interface WorkflowManageService {
@InvokeMode(SYNC)
BpmPageResult<ProcessInstanceDocumentVO> searchInstanceInEs(@Validated @RequestBody InstanceSearchReqDTO dto);
/**
* 创建审批流程并带上表单
*
* @param dto
* @return
*/
@Operation(summary = "创建审批流程并带上表单")
@PostMapping("/api/process/instance/form/create")
@Manageable
@InvokeMode(SYNC)
String createProcessInstanceWith(@Validated @RequestBody BpmnProcessInstanceCreateWithFormDTO dto);
/**
* 查询所有的审批流
*