diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..631d03f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,25 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+target
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+*.iml
+.idea
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 7c6a984..c4e125d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
4.1.2
2.3
0.9.1
+
@@ -236,6 +237,18 @@
${jwt.version}
+
+
+ org.activiti
+ activiti-spring-boot-starter
+ 7.1.0.M6
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.24
+
diff --git a/src/main/java/com/zilber/boot/activiti/controller/BpmnController.java b/src/main/java/com/zilber/boot/activiti/controller/BpmnController.java
new file mode 100644
index 0000000..04058b1
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/controller/BpmnController.java
@@ -0,0 +1,191 @@
+package com.zilber.boot.activiti.controller;
+
+
+import com.github.pagehelper.PageInfo;
+import com.zilber.boot.activiti.dto.DeploymentDTO;
+import com.zilber.boot.activiti.dto.InstanceDTO;
+import com.zilber.boot.activiti.dto.PageQueryDTO;
+import com.zilber.boot.activiti.dto.TaskQueryDTO;
+import com.zilber.boot.activiti.service.*;
+import com.zilber.boot.activiti.vo.DefinitionVO;
+import com.zilber.boot.activiti.vo.HistoricTaskVO;
+import com.zilber.boot.activiti.vo.InstanceVO;
+import com.zilber.boot.activiti.vo.TaskVO;
+import com.zilber.boot.utils.AjaxResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.List;
+
+@Validated
+@RestController
+@RequestMapping("/bpmn")
+@Api(tags = "工作流接口")
+@Slf4j
+public class BpmnController {
+
+ @Resource
+ private DefinitionService definitionService;
+
+ @Resource
+ private InstanceService instanceService;
+
+ @Resource
+ private TaskService taskService;
+
+ @Resource
+ private HistoryService historyService;
+
+ @PostMapping("/definition/upload")
+ @ApiOperation("上传流程图")
+ public AjaxResult uploadAndDeployment(
+ @RequestParam @ApiParam("名称") String name,
+ @RequestParam @ApiParam("上传的文件") MultipartFile file) {
+ definitionService.uploadAndDeployment(name, file);
+ return AjaxResult.success();
+ }
+
+ @PostMapping("/definition")
+ @ApiOperation("保存流程图")
+ public AjaxResult saveAndDeployment(@RequestBody @Valid DeploymentDTO dto) {
+ definitionService.saveAndDeployment(dto.getName(), dto.getXml());
+ return AjaxResult.success();
+ }
+
+ @GetMapping("/definition/page")
+ @ApiOperation("查询流程定义列表")
+ public AjaxResult getDefinitionPage(@Valid PageQueryDTO dto) {
+ PageInfo page = new PageInfo<>(definitionService.getDefinitionPage(dto));
+ return AjaxResult.success(page);
+ }
+
+ @GetMapping("/definition/list/by/{key}")
+ @ApiOperation("查询指定Key的流程定义所有列表")
+ public AjaxResult getVersionList(
+ @PathVariable @NotBlank String key) {
+ return AjaxResult.success(definitionService.getVersionList(key));
+ }
+
+ @GetMapping("/definition/by/key/{key}")
+ @ApiOperation("查询流程图,XML字符串格式")
+ public AjaxResult getDeploymentXMLByKey(
+ @PathVariable @ApiParam("流程定义Key") @NotBlank String key) {
+ String xmlString = definitionService.getDeploymentXML(key);
+ HashMap map = new HashMap<>();
+ map.put("xml", xmlString);
+ return AjaxResult.success(map);
+ }
+
+ @GetMapping("/definition/by/{id}")
+ @ApiOperation("查询流程图,XML字符串格式")
+ public AjaxResult getDeploymentXML(
+ @PathVariable @ApiParam("流程部署ID") @NotBlank String id,
+ @RequestParam @ApiParam("资源文件名") @NotBlank String name) {
+ String xmlString = definitionService.getDeploymentXML(id, name);
+ HashMap map = new HashMap<>();
+ map.put("xml", xmlString);
+ return AjaxResult.success(map);
+ }
+
+ @GetMapping("/definition/file/{id}")
+ @ApiOperation("查询流程图,XML文件格式")
+ public void getDeploymentFile(
+ HttpServletResponse response,
+ @PathVariable @ApiParam("流程部署ID") @NotBlank String id,
+ @RequestParam @ApiParam("资源文件名") @NotBlank String name) {
+ InputStream is = definitionService.getDeploymentStream(id, name);
+ OutputStream os;
+ try {
+ os = response.getOutputStream();
+ IOUtils.copy(is, os);
+
+ is.close();
+ os.close();
+ } catch (IOException e) {
+ log.error("查看流程文件失败", e);
+ }
+ }
+
+ @DeleteMapping("/definition/by/{id}")
+ @ApiOperation("删除流程定义")
+ public AjaxResult deleteDefinitionById(
+ @PathVariable @ApiParam("流程部署ID") @NotBlank String id) {
+ definitionService.deleteById(id);
+ return AjaxResult.success();
+ }
+
+ @GetMapping("/instance/page")
+ @ApiOperation("查询流程实例列表")
+ public AjaxResult getInstancePage(@Valid PageQueryDTO dto) {
+ PageInfo page = new PageInfo<>(instanceService.getInstancePage(dto));
+ return AjaxResult.success(page);
+ }
+
+ @PostMapping("/instance")
+ @ApiOperation("启动流程实例")
+ public AjaxResult startInstance(@RequestBody @Valid InstanceDTO dto) {
+ instanceService.start(dto);
+ return AjaxResult.success();
+ }
+
+ @PutMapping("/instance/suspend/by/{id}")
+ @ApiOperation("挂起流程实例")
+ public AjaxResult suspendInstance(@PathVariable @ApiParam("流程实例ID") @NotBlank String id) {
+ instanceService.suspend(id);
+ return AjaxResult.success();
+ }
+
+ @PutMapping("/instance/resume/by/{id}")
+ @ApiOperation("激活/重启流程实例")
+ public AjaxResult resumeInstance(@PathVariable @ApiParam("流程实例ID") @NotBlank String id) {
+ instanceService.resume(id);
+ return AjaxResult.success();
+ }
+
+ @DeleteMapping("/instance/by/{id}")
+ @ApiOperation("取消流程实例")
+ public AjaxResult deleteInstanceById(@PathVariable @ApiParam("流程实例ID") @NotBlank String id) {
+ instanceService.cancelById(id);
+ return AjaxResult.success();
+ }
+
+ @GetMapping("/instance/variables/by/{id}")
+ @ApiOperation("查询流程参数")
+ public AjaxResult instanceVariables(@PathVariable @ApiParam("流程实例ID") @NotBlank String id) {
+ return AjaxResult.success(instanceService.getVariables(id));
+ }
+
+ @GetMapping("/task/page")
+ @ApiOperation("查看待办任务")
+ public AjaxResult taskPage(@Valid TaskQueryDTO dto) {
+ PageInfo page = new PageInfo<>(taskService.taskPage(dto));
+ return AjaxResult.success(page);
+ }
+
+ @GetMapping("/historic/task/page")
+ @ApiOperation("查看历史任务")
+ public AjaxResult historicTaskPage(@Valid TaskQueryDTO dto) {
+ PageInfo page = new PageInfo<>(historyService.historicTaskPage(dto));
+ return AjaxResult.success(page);
+ }
+
+ @GetMapping("/historic/task/list")
+ @ApiOperation("任务实例历史")
+ public List getHistoryListByInstanceId(
+ @RequestParam @ApiParam("流程实例ID") @NotBlank String instanceId) {
+ return historyService.getListOfInstance(instanceId);
+ }
+}
diff --git a/src/main/java/com/zilber/boot/activiti/dto/DeploymentDTO.java b/src/main/java/com/zilber/boot/activiti/dto/DeploymentDTO.java
new file mode 100644
index 0000000..0ca7217
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/dto/DeploymentDTO.java
@@ -0,0 +1,23 @@
+package com.zilber.boot.activiti.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+@Getter
+@Setter
+@ApiModel("流程部署提交")
+public class DeploymentDTO {
+
+ @NotBlank(message = "名称不能为空")
+ @ApiModelProperty(value = "名称", required = true)
+ private String name;
+
+ @NotBlank(message = "bpmn文件xml不能为空")
+ @ApiModelProperty(value = "bpmn文件xml", required = true)
+ private String xml;
+
+}
diff --git a/src/main/java/com/zilber/boot/activiti/dto/InstanceDTO.java b/src/main/java/com/zilber/boot/activiti/dto/InstanceDTO.java
new file mode 100644
index 0000000..115b9d4
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/dto/InstanceDTO.java
@@ -0,0 +1,33 @@
+package com.zilber.boot.activiti.dto;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@ApiModel("启动流程实例")
+public class InstanceDTO {
+
+ @NotBlank
+ @ApiModelProperty(value = "流程定义Key")
+ @Length(max = 32)
+ private String procDefKey;
+
+ @Length(max = 24)
+ @ApiModelProperty(value = "发起人")
+ private String createBy;
+
+ @ApiModelProperty(value = "流程变量")
+ private Map variables;
+
+}
diff --git a/src/main/java/com/zilber/boot/activiti/dto/PageQueryDTO.java b/src/main/java/com/zilber/boot/activiti/dto/PageQueryDTO.java
new file mode 100644
index 0000000..82f309f
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/dto/PageQueryDTO.java
@@ -0,0 +1,23 @@
+package com.zilber.boot.activiti.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Positive;
+import javax.validation.constraints.PositiveOrZero;
+import java.io.Serializable;
+
+@Getter
+@Setter
+public class PageQueryDTO implements Serializable {
+
+ @ApiModelProperty("每页记录数")
+ @Positive(message = "分页数量必须为正整数")
+ private Integer pageSize = 20;
+
+ @ApiModelProperty("当前页数")
+ @PositiveOrZero(message = "分页数不正确")
+ private Integer pageNo = 0;
+}
diff --git a/src/main/java/com/zilber/boot/activiti/dto/TaskCompleteDTO.java b/src/main/java/com/zilber/boot/activiti/dto/TaskCompleteDTO.java
new file mode 100644
index 0000000..94ff33c
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/dto/TaskCompleteDTO.java
@@ -0,0 +1,28 @@
+package com.zilber.boot.activiti.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+@Getter
+@Setter
+@ApiModel("完成流程任务")
+public class TaskCompleteDTO {
+
+ @NotNull(message = "请选择审批结果")
+ @ApiModelProperty("审批状态:1-通过 0-拒绝(其他可以根据业务自行添加)")
+ private Integer status;
+
+ @Length(max = 512)
+ @ApiModelProperty("意见")
+ private String remark;
+
+ @ApiModelProperty(value = "流程变量")
+ private Map variables;
+
+}
diff --git a/src/main/java/com/zilber/boot/activiti/dto/TaskQueryDTO.java b/src/main/java/com/zilber/boot/activiti/dto/TaskQueryDTO.java
new file mode 100644
index 0000000..8ca6f30
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/dto/TaskQueryDTO.java
@@ -0,0 +1,22 @@
+package com.zilber.boot.activiti.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@ApiModel("任务查询")
+public class TaskQueryDTO extends PageQueryDTO {
+
+ @ApiModelProperty("流程定义Key")
+ private String procDefKey;
+
+ @ApiModelProperty("任务执行人")
+ private String assignee;
+
+ @ApiModelProperty("流程实例ID")
+ private String procInstId;
+
+}
diff --git a/src/main/java/com/zilber/boot/activiti/service/DefinitionService.java b/src/main/java/com/zilber/boot/activiti/service/DefinitionService.java
new file mode 100644
index 0000000..9ed78b6
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/service/DefinitionService.java
@@ -0,0 +1,164 @@
+package com.zilber.boot.activiti.service;
+
+import com.zilber.boot.activiti.dto.PageQueryDTO;
+import com.zilber.boot.activiti.vo.DefinitionVO;
+import com.zilber.boot.exception.ServiceException;
+import org.activiti.engine.RepositoryService;
+import org.activiti.engine.repository.ProcessDefinition;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.zip.ZipInputStream;
+
+@Service
+public class DefinitionService {
+
+ @Autowired
+ private RepositoryService repositoryService;
+
+ /**
+ * 上传并部署流程定义
+ *
+ * @param name 流程定义名称
+ * @param file 上传的文件
+ */
+ public void uploadAndDeployment(String name, MultipartFile file) {
+ try {
+ String filename = file.getOriginalFilename();
+ String extension = FilenameUtils.getExtension(filename);
+ InputStream fileInputStream = file.getInputStream();
+ if ("zip".equals(extension)) {
+ ZipInputStream zip = new ZipInputStream(fileInputStream);
+ repositoryService.createDeployment()
+ .addZipInputStream(zip)
+ .name(name)
+ .deploy();
+ } else {
+ repositoryService.createDeployment()
+ .addInputStream(filename, fileInputStream)
+ .name(name)
+ .deploy();
+ }
+ } catch (Exception e) {
+ throw new ServiceException(e.getMessage());
+ }
+ }
+
+ public List getDefinitionPage(PageQueryDTO dto) {
+ List list = repositoryService.createProcessDefinitionQuery()
+ .latestVersion().listPage(dto.getPageNo() * dto.getPageSize(), dto.getPageSize());
+ list.sort((y, x) -> x.getVersion() - y.getVersion());
+
+ return list.stream().map(processDefinition -> {
+ DefinitionVO vo = new DefinitionVO();
+ BeanUtils.copyProperties(processDefinition, vo);
+ return vo;
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * 根据流程定义Key获取历史版本
+ *
+ * @param key 流程定义Key
+ * @return 历史版本列表
+ */
+ public List getVersionList(String key) {
+ List list = repositoryService.createProcessDefinitionQuery()
+ .processDefinitionKey(key)
+ .orderByProcessDefinitionVersion().desc()
+ .list();
+ return list.stream().map(processDefinition -> {
+ DefinitionVO vo = new DefinitionVO();
+ BeanUtils.copyProperties(processDefinition, vo);
+ return vo;
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * 在线绘制流程图保存
+ *
+ * @param name 流程名称
+ * @param xml xml数据
+ */
+ public void saveAndDeployment(String name, String xml) {
+ repositoryService.createDeployment()
+ .addString("bpmnjs.bpmn", xml)
+ .name(name)
+ .deploy();
+ }
+
+ /**
+ * 获取流程图(流)
+ *
+ * @param deploymentId 流程部署ID
+ * @param resourceName 资源文件名称
+ * @return 数据流
+ */
+ public InputStream getDeploymentStream(String deploymentId, String resourceName) {
+ return repositoryService.getResourceAsStream(deploymentId, resourceName);
+ }
+
+ /**
+ * 获取流程图xml
+ *
+ * @param definitionKey 流程定义Key
+ * @return xml字符串
+ */
+ public String getDeploymentXML(String definitionKey) {
+ ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
+ .processDefinitionKey(definitionKey)
+ .latestVersion()
+ .singleResult();
+ return getDeploymentXML(processDefinition.getDeploymentId(), processDefinition.getResourceName());
+ }
+
+ /**
+ * 获取流程图xml
+ *
+ * @param deploymentId 流程部署ID
+ * @param resourceName 资源文件名称
+ * @return xml字符串
+ */
+ public String getDeploymentXML(String deploymentId, String resourceName) {
+ try {
+ InputStream inputStream = repositoryService.getResourceAsStream(deploymentId, resourceName);
+ StringWriter writer = new StringWriter();
+ IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8.name());
+ return writer.toString();
+ } catch (IOException e) {
+ throw new ServiceException(e.getMessage());
+ }
+ }
+
+ /**
+ * 获取流程定义
+ *
+ * @param processDefinitionId 流程定义ID
+ * @return
+ */
+ public ProcessDefinition getById(String processDefinitionId) {
+ return repositoryService.createProcessDefinitionQuery()
+ .processDefinitionId(processDefinitionId)
+ .singleResult();
+ }
+
+ /**
+ * 根据流程定义id删除流程定义
+ *
+ * @param id 流程定义id
+ */
+ public void deleteById(String id) {
+ repositoryService.deleteDeployment(id);
+ }
+}
diff --git a/src/main/java/com/zilber/boot/activiti/service/HistoryService.java b/src/main/java/com/zilber/boot/activiti/service/HistoryService.java
new file mode 100644
index 0000000..c0c57ed
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/service/HistoryService.java
@@ -0,0 +1,54 @@
+package com.zilber.boot.activiti.service;
+
+
+import com.zilber.boot.activiti.dto.TaskQueryDTO;
+import com.zilber.boot.activiti.vo.HistoricTaskVO;
+import org.activiti.engine.history.HistoricTaskInstance;
+import org.activiti.engine.history.HistoricTaskInstanceQuery;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import java.util.stream.Collectors;
+
+
+@Service(value = "HistoryService_")
+public class HistoryService {
+
+ @Resource
+ private org.activiti.engine.HistoryService historyService;
+
+ public List historicTaskPage(TaskQueryDTO dto) {
+ HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
+ .finished()
+ .orderByHistoricTaskInstanceEndTime()
+ .desc();
+ if (!StringUtils.isEmpty(dto.getAssignee())) {
+ taskInstanceQuery.taskAssignee(dto.getAssignee());
+ }
+ if (!StringUtils.isEmpty(dto.getProcDefKey())) {
+ taskInstanceQuery.processDefinitionKey(dto.getProcDefKey());
+ }
+ if (!StringUtils.isEmpty(dto.getProcInstId())) {
+ taskInstanceQuery.processInstanceId(dto.getProcInstId());
+ }
+ List tasks = taskInstanceQuery.listPage(dto.getPageNo() * dto.getPageSize(), dto.getPageSize());
+ return HistoricTaskVO.merge(tasks);
+
+ }
+
+ public List getListOfInstance(String instanceId) {
+ List list = historyService.createHistoricTaskInstanceQuery()
+ .orderByHistoricTaskInstanceStartTime()
+ .asc()
+ .processInstanceId(instanceId)
+ .list();
+ return list.stream()
+ .map(HistoricTaskVO::merge)
+ .collect(Collectors.toList());
+
+ }
+}
diff --git a/src/main/java/com/zilber/boot/activiti/service/InstanceService.java b/src/main/java/com/zilber/boot/activiti/service/InstanceService.java
new file mode 100644
index 0000000..4d7a2d0
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/service/InstanceService.java
@@ -0,0 +1,167 @@
+package com.zilber.boot.activiti.service;
+
+
+import com.zilber.boot.activiti.dto.InstanceDTO;
+import com.zilber.boot.activiti.dto.PageQueryDTO;
+import com.zilber.boot.activiti.vo.InstanceProgressVO;
+import com.zilber.boot.activiti.vo.InstanceVO;
+import org.activiti.engine.RuntimeService;
+import org.activiti.engine.TaskService;
+import org.activiti.engine.impl.persistence.entity.EntityManager;
+import org.activiti.engine.runtime.ProcessInstance;
+import org.activiti.engine.runtime.ProcessInstanceQuery;
+import org.activiti.engine.task.Task;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class InstanceService {
+
+ @Autowired
+ private RuntimeService runtimeService;
+
+ @Autowired
+ private TaskService taskService;
+
+ public ProcessInstance getById(String processInstanceId) {
+ return runtimeService.createProcessInstanceQuery()
+ .processInstanceId(processInstanceId)
+ .singleResult();
+ }
+
+ public List getInstancePage(PageQueryDTO dto) {
+ ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery();
+ return InstanceVO.merge(query.listPage(dto.getPageNo() * dto.getPageSize(), dto.getPageSize()));
+ }
+
+ /**
+ * 启动流程实例并完成第一个任务
+ *
+ * @param dto 启动流程实例必要参数
+ */
+ public void start(InstanceDTO dto) {
+ Map variables = dto.getVariables();
+ if (variables == null) {
+ variables = new HashMap<>();
+ }
+
+ // 设置默认的流程变量
+ variables.put("assignee0", dto.getCreateBy());
+ //variables.put("procDefKey", dto.getProcDefKey());
+
+ // 启动流程实例
+ ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
+ dto.getProcDefKey(), /*dto.getBusinessId() +*/ "", variables);
+
+ // 执行第一个任务
+ Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
+ if (task != null) {
+ if (task.getAssignee() == null) {
+ taskService.claim(task.getId(), dto.getCreateBy());
+ }
+ taskService.complete(task.getId());
+ }
+
+ }
+
+ /**
+ * 查询指定流程进度
+ *
+ * @param instanceId 流程实例ID
+ * @return 进度列表
+ */
+ public List getInstanceProgress(String instanceId) {
+ String sql = "select\n" +
+ " t1.ACT_ID_ as actId,\n" +
+ " t1.TASK_ID_ as taskId,\n" +
+ " t1.ACT_NAME_ as actName,\n" +
+ " t1.ACT_TYPE_ as actType,\n" +
+ " t1.PROC_INST_ID_ as procInstId,\n" +
+ " t1.DURATION_ as duration,\n" +
+ " t1.ACT_INST_STATE_ as state,\n" +
+ " t1.START_TIME_ as startTime,\n" +
+ " t1.END_TIME_ as endTime,\n" +
+ " t1.ASSIGNEE_ as assignee,\n" +
+ " t2.MESSAGE_ as remark,\n" +
+ " t3.TEXT_ as operators\n" +
+ "from ACT_HI_ACTINST as t1\n" +
+ " left join ACT_HI_COMMENT as t2 on t1.TASK_ID_ = t2.TASK_ID_\n" +
+ " left join ACT_HI_VARINST as t3 on t1.ID_ = t3.ACT_INST_ID_ and t3.NAME_ = ?2\n" +
+ "where t1.PROC_INST_ID_ = ?1\n" +
+ " " +
+ "and t1.ACT_TYPE_ = 'userTask'\n" +
+ "order by t1.START_TIME_ desc, NOT ISNULL(t1.END_TIME_), t1" +
+ ".END_TIME_ desc";
+
+ /*Query nativeQuery = entityManager.createNativeQuery(sql);
+ nativeQuery.setParameter(1, instanceId);
+ nativeQuery.setParameter(2, CamundaConstants.OPERATORS);
+ return nativeQuery
+ .unwrap(NativeQueryImpl.class)
+ .setResultTransformer(Transformers.aliasToBean(InstanceProgressVO.class))
+ .list();*/
+ return null;
+ }
+
+ /**
+ * 挂起流程实例
+ *
+ * @param id 流程实例id
+ */
+ public void suspend(String id) {
+ runtimeService.suspendProcessInstanceById(id);
+ }
+
+ /**
+ * 激活流程实例
+ *
+ * @param id 流程实例id
+ */
+ public void resume(String id) {
+ runtimeService.activateProcessInstanceById(id);
+ }
+
+ /**
+ * 结束流程实例(审批失败)
+ *
+ * @param id 流程实例id
+ * @param reason 失败原因
+ */
+ public void failEndById(String id, String reason) {
+ ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+ .processInstanceId(id).singleResult();
+ if (processInstance != null) {
+ // 审批失败,状态为 INTERNALLY_TERMINATED
+ runtimeService.deleteProcessInstance(processInstance.getId(), reason);
+ }
+ }
+
+ /**
+ * 取消流程实例
+ *
+ * @param id 流程实例id
+ */
+ public void cancelById(String id) {
+ ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
+ .processInstanceId(id).singleResult();
+ if (processInstance != null) {
+ // 手动取消,状态为 EXTERNALLY_TERMINATED
+ runtimeService.deleteProcessInstance(processInstance.getId(), "手动取消");
+ }
+ }
+
+ /**
+ * 获取流程变量
+ *
+ * @param id 流程实例id
+ * @return 流程变量map
+ */
+ public Map getVariables(String id) {
+ return runtimeService.getVariables(id);
+ }
+
+}
diff --git a/src/main/java/com/zilber/boot/activiti/service/TaskService.java b/src/main/java/com/zilber/boot/activiti/service/TaskService.java
new file mode 100644
index 0000000..edb444a
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/service/TaskService.java
@@ -0,0 +1,123 @@
+package com.zilber.boot.activiti.service;
+
+import com.zilber.boot.activiti.dto.TaskCompleteDTO;
+import com.zilber.boot.activiti.dto.TaskQueryDTO;
+import com.zilber.boot.activiti.vo.TaskVO;
+import com.zilber.boot.exception.ServiceException;
+import org.activiti.engine.task.Task;
+import org.activiti.engine.task.TaskQuery;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service(value = "TaskService_")
+public class TaskService {
+
+ @Resource
+ private org.activiti.engine.TaskService taskService;
+
+ @Resource
+ private InstanceService instanceService;
+
+ public List taskPage(TaskQueryDTO dto) {
+ TaskQuery taskQuery = taskService.createTaskQuery()
+ .active()
+ .orderByTaskCreateTime()
+ .desc();
+ if (!StringUtils.isEmpty(dto.getAssignee())) {
+ taskQuery.taskAssignee(dto.getAssignee());
+ }
+ if (!StringUtils.isEmpty(dto.getProcDefKey())) {
+ taskQuery.processDefinitionKey(dto.getProcDefKey());
+ }
+ if (!StringUtils.isEmpty(dto.getProcInstId())) {
+ taskQuery.processInstanceId(dto.getProcInstId());
+ }
+ List tasks = taskQuery.listPage(dto.getPageNo() * dto.getPageSize(), dto.getPageSize());
+ return TaskVO.merge(tasks);
+ }
+
+ public void complete(String userCode, String taskId, TaskCompleteDTO dto) {
+ Task task = taskService.createTaskQuery()
+ .taskAssignee(userCode)
+ .taskId(taskId)
+ .singleResult();
+
+ if (task == null) {
+ throw new ServiceException("没有任务");
+ }
+
+ if (task.getAssignee() == null) {
+ taskService.claim(taskId, userCode);
+ }
+ if (!StringUtils.isEmpty(dto.getRemark())) {
+ //taskService.createComment(taskId, task.getProcessInstanceId(), dto.getRemark());
+ }
+ Map variables = dto.getVariables();
+ if (dto.getVariables() == null) {
+ variables = new HashMap<>();
+ }
+ if (dto.getStatus() == 0) {
+ // TODO: 多实例并行任务,需要按照业务处理,是否一个人审批不通过,就都不通过
+ taskService.setVariable(taskId, "status", 0);
+ instanceService.failEndById(task.getProcessInstanceId(), dto.getRemark());
+ } else {
+ variables.put("status", dto.getStatus());
+ taskService.complete(taskId, variables);
+ }
+ }
+
+ /*public List getSimpleTasks(List businessIds, DefinitionKey definitionKey) {
+ String sql = "SELECT\n" +
+ " t2.ID_ as taskId,\n" +
+ " t2.NAME_ as taskName,\n" +
+ " t2.DESCRIPTION_ as taskDescription,\n" +
+ " t2.TASK_DEF_KEY_ as taskDefinitionKey,\n" +
+ " t2.ASSIGNEE_ as assignee,\n" +
+ " t2.CREATE_TIME_ as taskTime,\n" +
+ " t2.PRIORITY_ as priority,\n" +
+ " t2.SUSPENSION_STATE_ as suspensionState,\n" +
+ " t1.business_id as businessId\n" +
+ "FROM bpm_business as t1\n" +
+ " LEFT JOIN ACT_RU_TASK as t2 on t1.proc_inst_id = t2.PROC_INST_ID_\n" +
+ "WHERE t1.business_id in (?1) and t1.proc_def_key = ?2";
+
+ Query nativeQuery = entityManager.createNativeQuery(sql);
+ nativeQuery.setParameter(1, businessIds);
+ nativeQuery.setParameter(2, definitionKey.getValue());
+ return nativeQuery
+ .unwrap(NativeQueryImpl.class)
+ .setResultTransformer(Transformers.aliasToBean(io.izn.iec.camunda.vo.SimpleTaskVO.class))
+ .list();
+ }
+
+ public List getSimpleTasks(List businessIds, List definitionKey) {
+ List collect = definitionKey.stream().map(DefinitionKey::getValue).collect(Collectors.toList());
+ String sql = "SELECT\n" +
+ " t2.ID_ as taskId,\n" +
+ " t2.NAME_ as taskName,\n" +
+ " t2.DESCRIPTION_ as taskDescription,\n" +
+ " t2.TASK_DEF_KEY_ as taskDefinitionKey,\n" +
+ " t2.ASSIGNEE_ as assignee,\n" +
+ " t2.CREATE_TIME_ as taskTime,\n" +
+ " t2.PRIORITY_ as priority,\n" +
+ " t2.SUSPENSION_STATE_ as suspensionState,\n" +
+ " t1.business_id as businessId\n" +
+ "FROM bpm_business as t1\n" +
+ " LEFT JOIN ACT_RU_TASK as t2 on t1.proc_inst_id = t2.PROC_INST_ID_\n" +
+ "WHERE t1.business_id in (?1) and t1.proc_def_key in (?2)";
+
+ Query nativeQuery = entityManager.createNativeQuery(sql);
+ nativeQuery.setParameter(1, businessIds);
+ nativeQuery.setParameter(2, collect);
+ return nativeQuery
+ .unwrap(NativeQueryImpl.class)
+ .setResultTransformer(Transformers.aliasToBean(io.izn.iec.camunda.vo.SimpleTaskVO.class))
+ .list();
+ }*/
+}
diff --git a/src/main/java/com/zilber/boot/activiti/vo/DefinitionVO.java b/src/main/java/com/zilber/boot/activiti/vo/DefinitionVO.java
new file mode 100644
index 0000000..c6b2252
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/vo/DefinitionVO.java
@@ -0,0 +1,34 @@
+package com.zilber.boot.activiti.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@ApiModel("流程定义")
+public class DefinitionVO {
+
+ @ApiModelProperty("流程定义ID")
+ private String id;
+
+ @ApiModelProperty("流程定义名称")
+ private String name;
+
+ @ApiModelProperty("流程定义描述")
+ private String description;
+
+ @ApiModelProperty("流程定义Key")
+ private String key;
+
+ @ApiModelProperty("流程定义资源名称")
+ private String resourceName;
+
+ @ApiModelProperty("流程部署ID")
+ private String deploymentId;
+
+ @ApiModelProperty("版本号")
+ private Integer version;
+
+}
diff --git a/src/main/java/com/zilber/boot/activiti/vo/HistoricTaskVO.java b/src/main/java/com/zilber/boot/activiti/vo/HistoricTaskVO.java
new file mode 100644
index 0000000..d8c85d7
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/vo/HistoricTaskVO.java
@@ -0,0 +1,74 @@
+package com.zilber.boot.activiti.vo;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import org.activiti.engine.history.HistoricTaskInstance;
+import org.springframework.beans.BeanUtils;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Getter
+@Setter
+@ApiModel("历史任务")
+public class HistoricTaskVO {
+
+ @ApiModelProperty("任务ID")
+ private String taskId;
+
+ @ApiModelProperty("任务名称")
+ private String taskName;
+
+ @ApiModelProperty("任务描述")
+ private String taskDescription;
+
+ @ApiModelProperty("任务定义Key")
+ private String taskDefinitionKey;
+
+ @ApiModelProperty("执行人")
+ private String assignee;
+
+ @ApiModelProperty("任务开始时间")
+ private Date startTime;
+
+ @ApiModelProperty("任务结束时间")
+ private Date endTime;
+
+ @ApiModelProperty("任务优先级")
+ private Integer priority;
+
+ @ApiModelProperty("任务执行时长")
+ private Long duration;
+
+ @ApiModelProperty("任务删除原因")
+ private String deleteReason;
+
+ public static List merge(List tasks) {
+ return tasks.stream()
+ .map(HistoricTaskVO::merge)
+ .collect(Collectors.toList());
+ }
+
+ public static HistoricTaskVO merge(HistoricTaskInstance task) {
+ HistoricTaskVO vo = new HistoricTaskVO();
+ vo.setTaskId(task.getId());
+ vo.setTaskName(task.getName());
+ vo.setTaskDescription(task.getDescription());
+ vo.setTaskDefinitionKey(task.getTaskDefinitionKey());
+ vo.setAssignee(task.getAssignee());
+ vo.setStartTime(task.getStartTime());
+ vo.setEndTime(task.getEndTime());
+ vo.setPriority(task.getPriority());
+ vo.setDuration(task.getDurationInMillis());
+ vo.setDeleteReason(task.getDeleteReason());
+ return vo;
+ }
+
+
+}
diff --git a/src/main/java/com/zilber/boot/activiti/vo/InstanceProgressVO.java b/src/main/java/com/zilber/boot/activiti/vo/InstanceProgressVO.java
new file mode 100644
index 0000000..24a8539
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/vo/InstanceProgressVO.java
@@ -0,0 +1,54 @@
+package com.zilber.boot.activiti.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.util.Date;
+
+@Getter
+@Setter
+@ApiModel("审批进度")
+public class InstanceProgressVO implements Serializable {
+
+ @ApiModelProperty("活动ID")
+ private String actId;
+
+ @ApiModelProperty("任务ID")
+ private String taskId;
+
+ @ApiModelProperty("活动名称")
+ private String actName;
+
+ // 'userTask', 'startEvent', 'noneEndEvent'
+ @ApiModelProperty("活动类型")
+ private String actType;
+
+ @ApiModelProperty("流程实例ID")
+ private String procInstId;
+
+ @ApiModelProperty("任务执行时长")
+ private BigInteger duration;
+
+ // ActivityInstanceState
+ @ApiModelProperty("活动状态:0-default 1-scopeComplete 2-canceled 3-starting 4-ending")
+ private Integer state;
+
+ @ApiModelProperty("开始时间")
+ private Date startTime;
+
+ @ApiModelProperty("结束时间")
+ private Date endTime;
+
+ @ApiModelProperty("执行人")
+ private String assignee;
+
+ @ApiModelProperty("审批意见")
+ private String remark;
+
+ @ApiModelProperty("额外操作项")
+ private String operators;
+}
diff --git a/src/main/java/com/zilber/boot/activiti/vo/InstanceVO.java b/src/main/java/com/zilber/boot/activiti/vo/InstanceVO.java
new file mode 100644
index 0000000..c26b206
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/vo/InstanceVO.java
@@ -0,0 +1,48 @@
+package com.zilber.boot.activiti.vo;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import org.activiti.engine.runtime.ProcessInstance;
+
+import java.util.List;
+
+import java.util.stream.Collectors;
+
+@Getter
+@Setter
+@ApiModel("流程实例")
+public class InstanceVO {
+
+ @ApiModelProperty("是否挂起:1-正常 2-挂起")
+ private Integer suspensionState;
+
+ @ApiModelProperty("流程定义Key")
+ private String procDefKey;
+
+ @ApiModelProperty("流程定义ID")
+ private String procDefId;
+
+ @ApiModelProperty("流程实例ID")
+ private String procInstId;
+
+ @ApiModelProperty("备注")
+ private String remark;
+
+ @ApiModelProperty("发起人")
+ private String createBy;
+
+ public static List merge(List instances) {
+ return instances.stream().map(instance -> {
+ InstanceVO vo = new InstanceVO();
+ vo.setSuspensionState(instance.isSuspended() ? 2 : 1);
+ vo.setProcDefKey(instance.getProcessDefinitionKey());
+ vo.setProcDefId(instance.getProcessDefinitionId());
+ vo.setProcInstId(instance.getProcessInstanceId());
+ vo.setCreateBy(instance.getStartUserId());
+ return vo;
+ }).collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/com/zilber/boot/activiti/vo/SimpleTaskVO.java b/src/main/java/com/zilber/boot/activiti/vo/SimpleTaskVO.java
new file mode 100644
index 0000000..385a129
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/vo/SimpleTaskVO.java
@@ -0,0 +1,43 @@
+package com.zilber.boot.activiti.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Getter
+@Setter
+@ApiModel("流程任务(简单数据结构)")
+public class SimpleTaskVO implements Serializable {
+
+ @ApiModelProperty("任务ID")
+ private String taskId;
+
+ @ApiModelProperty("任务名称")
+ private String taskName;
+
+ @ApiModelProperty("任务描述")
+ private String taskDescription;
+
+ @ApiModelProperty("任务定义Key")
+ private String taskDefinitionKey;
+
+ @ApiModelProperty("执行人")
+ private String assignee;
+
+ @ApiModelProperty("任务开始时间")
+ private Date taskTime;
+
+ @ApiModelProperty("任务优先级")
+ private Integer priority;
+
+ @ApiModelProperty("是否为挂起状态:1-否 2-是")
+ private Integer suspensionState;
+
+ @ApiModelProperty("业务ID")
+ private Integer businessId;
+
+}
diff --git a/src/main/java/com/zilber/boot/activiti/vo/TaskVO.java b/src/main/java/com/zilber/boot/activiti/vo/TaskVO.java
new file mode 100644
index 0000000..838488a
--- /dev/null
+++ b/src/main/java/com/zilber/boot/activiti/vo/TaskVO.java
@@ -0,0 +1,62 @@
+package com.zilber.boot.activiti.vo;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import org.activiti.engine.task.Task;
+import org.springframework.beans.BeanUtils;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Getter
+@Setter
+@ApiModel("流程任务")
+public class TaskVO {
+
+ @ApiModelProperty("任务ID")
+ private String taskId;
+
+ @ApiModelProperty("任务名称")
+ private String taskName;
+
+ @ApiModelProperty("任务描述")
+ private String taskDescription;
+
+ @ApiModelProperty("任务定义Key")
+ private String taskDefinitionKey;
+
+ @ApiModelProperty("执行人")
+ private String assignee;
+
+ @ApiModelProperty("任务开始时间")
+ private Date taskTime;
+
+ @ApiModelProperty("任务优先级")
+ private Integer priority;
+
+ @ApiModelProperty("是否为挂起状态:1-否 2-是")
+ private Integer suspensionState;
+
+ public static List merge(List tasks) {
+ return tasks.stream().map(task -> {
+ TaskVO vo = new TaskVO();
+ vo.setTaskId(task.getId());
+ vo.setTaskName(task.getName());
+ vo.setTaskDescription(task.getDescription());
+ vo.setTaskDefinitionKey(task.getTaskDefinitionKey());
+ vo.setAssignee(task.getAssignee());
+ vo.setTaskTime(task.getCreateTime());
+ vo.setPriority(task.getPriority());
+ vo.setSuspensionState(task.isSuspended() ? 2 : 1);
+ return vo;
+ }).collect(Collectors.toList());
+ }
+
+
+}
diff --git a/src/main/java/com/zilber/boot/framework/config/SecurityConfig.java b/src/main/java/com/zilber/boot/framework/config/SecurityConfig.java
index 08667b6..5b8a35a 100644
--- a/src/main/java/com/zilber/boot/framework/config/SecurityConfig.java
+++ b/src/main/java/com/zilber/boot/framework/config/SecurityConfig.java
@@ -112,7 +112,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
.antMatchers("/login", "/register", "/captchaImage").anonymous()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js","/**/*.jpg","/**/*.png","/zilbervue/admin/", "/profile/**").permitAll()
- .antMatchers("/swagger-ui.html","/swagger-ui", "/v3","/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
+ .antMatchers("/bpmn/**","/swagger-ui.html","/swagger-ui", "/v3","/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
diff --git a/src/main/resources/application-druid.yml b/src/main/resources/application-druid.yml
index 3cae6f8..c96e804 100644
--- a/src/main/resources/application-druid.yml
+++ b/src/main/resources/application-druid.yml
@@ -6,9 +6,9 @@ spring:
druid:
# 主库数据源
master:
- url: jdbc:mysql://localhost:3306/zilberboot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+ url: jdbc:mysql://39.100.74.100:3306/zilberboot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
- password: root
+ password: root@123456
# 从库数据源
slave:
# 从数据源开关/默认关闭
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 475747b..6410efe 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -68,13 +68,13 @@ spring:
# redis 配置
redis:
# 地址
- host: localhost
+ host: 39.100.74.100
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
- password:
+ password: redis@ly1234
# 连接超时时间
timeout: 10s
lettuce:
@@ -87,6 +87,11 @@ spring:
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
+ activiti:
+ history-level: full
+ db-history-used: true
+ check-process-definitions: false
+ deployment-mode: never-fail
# token配置
token:
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
deleted file mode 100644
index cf2c596..0000000
--- a/src/main/resources/banner.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-Application Version: ${zilberboot.version}
-Spring Boot Version: ${spring-boot.version}
-////////////////////////////////////////////////////////////////////
-// _ooOoo_ //
-// o8888888o //
-// 88" . "88 //
-// (| ^_^ |) //
-// O\ = /O //
-// ____/`---'\____ //
-// .' \\| |// `. //
-// / \\||| : |||// \ //
-// / _||||| -:- |||||- \ //
-// | | \\\ - /// | | //
-// | \_| ''\---/'' | | //
-// \ .-\__ `-` ___/-. / //
-// ___`. .' /--.--\ `. . ___ //
-// ."" '< `.___\_<|>_/___.' >'"". //
-// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
-// \ \ `-. \_ __\ /__ _/ .-` / / //
-// ========`-.____`-.___\_____/___.-`____.-'======== //
-// `=---=' //
-// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
-// 佛祖保佑 永不宕机 永无BUG //
-////////////////////////////////////////////////////////////////////
\ No newline at end of file
diff --git a/src/main/resources/bpmn/leave.bpmn20.xml b/src/main/resources/bpmn/leave.bpmn20.xml
new file mode 100644
index 0000000..b26af30
--- /dev/null
+++ b/src/main/resources/bpmn/leave.bpmn20.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+