Merge remote-tracking branch 'origin/master' into feature/bpm-back
This commit is contained in:
53
yudao-module-bpm/yudao-module-bpm-biz-flowable/pom.xml
Normal file
53
yudao-module-bpm/yudao-module-bpm-biz-flowable/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-bpm</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-module-bpm-biz-flowable</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>
|
||||
bpm-flowable 模块,基于 Flowable 6 实现工作流
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-module-bpm-base</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工作流相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-flowable</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--junit4-->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.196</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* bpm API 实现类,定义暴露给其它模块的 API
|
||||
*/
|
||||
package cn.iocoder.yudao.module.bpm.api;
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.yudao.module.bpm.api.task;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* Flowable 流程实例 Api 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @author jason
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi {
|
||||
|
||||
@Resource
|
||||
private BpmProcessInstanceService processInstanceService;
|
||||
|
||||
@Override
|
||||
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) {
|
||||
return processInstanceService.createProcessInstance(userId, reqDTO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.io.IoUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
|
||||
import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Api(tags = "管理后台 - 流程模型")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/model")
|
||||
@Validated
|
||||
public class BpmModelController {
|
||||
|
||||
@Resource
|
||||
private BpmModelService modelService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@ApiOperation(value = "获得模型分页")
|
||||
public CommonResult<PageResult<BpmModelPageItemRespVO>> getModelPage(BpmModelPageReqVO pageVO) {
|
||||
return success(modelService.getModelPage(pageVO));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@ApiOperation("获得模型")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:query')")
|
||||
public CommonResult<BpmModelRespVO> getModel(@RequestParam("id") String id) {
|
||||
BpmModelRespVO model = modelService.getModel(id);
|
||||
return success(model);
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@ApiOperation(value = "新建模型")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:create')")
|
||||
public CommonResult<String> createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) {
|
||||
return success(modelService.createModel(createRetVO, null));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@ApiOperation(value = "修改模型")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
|
||||
public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) {
|
||||
modelService.updateModel(modelVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/import")
|
||||
@ApiOperation(value = "导入模型")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:import')")
|
||||
public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException {
|
||||
BpmModelCreateReqVO createReqVO = BpmModelConvert.INSTANCE.convert(importReqVO);
|
||||
// 读取文件
|
||||
String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false);
|
||||
return success(modelService.createModel(createReqVO, bpmnXml));
|
||||
}
|
||||
|
||||
@PostMapping("/deploy")
|
||||
@ApiOperation(value = "部署模型")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:deploy')")
|
||||
public CommonResult<Boolean> deployModel(@RequestParam("id") String id) {
|
||||
modelService.deployModel(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update-state")
|
||||
@ApiOperation(value = "修改模型的状态", notes = "实际更新的部署的流程定义的状态")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
|
||||
public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
|
||||
modelService.updateModelState(reqVO.getId(), reqVO.getState());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@ApiOperation("删除模型")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:model:delete')")
|
||||
public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) {
|
||||
modelService.deleteModel(id);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Api(tags = "管理后台 - 流程定义")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/process-definition")
|
||||
@Validated
|
||||
public class BpmProcessDefinitionController {
|
||||
|
||||
@Resource
|
||||
private BpmProcessDefinitionService bpmDefinitionService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@ApiOperation(value = "获得流程定义分页")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
|
||||
public CommonResult<PageResult<BpmProcessDefinitionPageItemRespVO>> getProcessDefinitionPage(
|
||||
BpmProcessDefinitionPageReqVO pageReqVO) {
|
||||
return success(bpmDefinitionService.getProcessDefinitionPage(pageReqVO));
|
||||
}
|
||||
|
||||
@GetMapping ("/list")
|
||||
@ApiOperation(value = "获得流程定义列表")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
|
||||
public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList(
|
||||
BpmProcessDefinitionListReqVO listReqVO) {
|
||||
return success(bpmDefinitionService.getProcessDefinitionList(listReqVO));
|
||||
}
|
||||
|
||||
@GetMapping ("/get-bpmn-xml")
|
||||
@ApiOperation(value = "获得流程定义的 BPMN XML")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
|
||||
public CommonResult<String> getProcessDefinitionBpmnXML(@RequestParam("id") String id) {
|
||||
String bpmnXML = bpmDefinitionService.getProcessDefinitionBpmnXML(id);
|
||||
return success(bpmnXML);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Api(tags = "管理后台 - 任务分配规则")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/task-assign-rule")
|
||||
@Validated
|
||||
public class BpmTaskAssignRuleController {
|
||||
|
||||
@Resource
|
||||
private BpmTaskAssignRuleService taskAssignRuleService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiOperation(value = "获得任务分配规则列表")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "modelId", value = "模型编号", example = "1024", dataTypeClass = String.class),
|
||||
@ApiImplicitParam(name = "processDefinitionId", value = "流程定义的编号", example = "2048", dataTypeClass = String.class)
|
||||
})
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:query')")
|
||||
public CommonResult<List<BpmTaskAssignRuleRespVO>> getTaskAssignRuleList(
|
||||
@RequestParam(value = "modelId", required = false) String modelId,
|
||||
@RequestParam(value = "processDefinitionId", required = false) String processDefinitionId) {
|
||||
return success(taskAssignRuleService.getTaskAssignRuleList(modelId, processDefinitionId));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@ApiOperation(value = "创建任务分配规则")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:create')")
|
||||
public CommonResult<Long> createTaskAssignRule(@Valid @RequestBody BpmTaskAssignRuleCreateReqVO reqVO) {
|
||||
return success(taskAssignRuleService.createTaskAssignRule(reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@ApiOperation(value = "更新任务分配规则")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:update')")
|
||||
public CommonResult<Boolean> updateTaskAssignRule(@Valid @RequestBody BpmTaskAssignRuleUpdateReqVO reqVO) {
|
||||
taskAssignRuleService.updateTaskAssignRule(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Api(tags = "管理后台 - 流程活动实例")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/activity")
|
||||
@Validated
|
||||
public class BpmActivityController {
|
||||
|
||||
@Resource
|
||||
private BpmActivityService activityService;
|
||||
|
||||
@GetMapping("/list")
|
||||
@ApiOperation(value = "生成指定流程实例的高亮流程图",
|
||||
notes = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成")
|
||||
@ApiImplicitParam(name = "processInstanceId", value = "流程实例的编号", required = true, dataTypeClass = String.class)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
|
||||
public CommonResult<List<BpmActivityRespVO>> getActivityList(
|
||||
@RequestParam("processInstanceId") String processInstanceId) {
|
||||
return success(activityService.getActivityListByProcessInstanceId(processInstanceId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Api(tags = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请”
|
||||
@RestController
|
||||
@RequestMapping("/bpm/process-instance")
|
||||
@Validated
|
||||
public class BpmProcessInstanceController {
|
||||
@Resource
|
||||
private BpmProcessInstanceService processInstanceService;
|
||||
|
||||
@GetMapping("/my-page")
|
||||
@ApiOperation(value = "获得我的实例分页列表", notes = "在【我的流程】菜单中,进行调用")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
|
||||
public CommonResult<PageResult<BpmProcessInstancePageItemRespVO>> getMyProcessInstancePage(
|
||||
@Valid BpmProcessInstanceMyPageReqVO pageReqVO) {
|
||||
return success(processInstanceService.getMyProcessInstancePage(getLoginUserId(), pageReqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@ApiOperation("新建流程实例")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
|
||||
public CommonResult<String> createProcessInstance(@Valid @RequestBody BpmProcessInstanceCreateReqVO createReqVO) {
|
||||
return success(processInstanceService.createProcessInstance(getLoginUserId(), createReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@ApiOperation(value = "获得指定流程实例", notes = "在【流程详细】界面中,进行调用")
|
||||
@ApiImplicitParam(name = "id", value = "流程实例的编号", required = true, dataTypeClass = String.class)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
|
||||
public CommonResult<BpmProcessInstanceRespVO> getProcessInstance(@RequestParam("id") String id) {
|
||||
return success(processInstanceService.getProcessInstanceVO(id));
|
||||
}
|
||||
|
||||
@DeleteMapping("/cancel")
|
||||
@ApiOperation(value = "取消流程实例", notes = "撤回发起的流程")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")
|
||||
public CommonResult<Boolean> cancelProcessInstance(@Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) {
|
||||
processInstanceService.cancelProcessInstance(getLoginUserId(), cancelReqVO);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Api(tags = "管理后台 - 流程任务实例")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/task")
|
||||
@Validated
|
||||
public class BpmTaskController {
|
||||
|
||||
@Resource
|
||||
private BpmTaskService taskService;
|
||||
|
||||
@GetMapping("todo-page")
|
||||
@ApiOperation("获取 Todo 待办任务分页")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
|
||||
public CommonResult<PageResult<BpmTaskTodoPageItemRespVO>> getTodoTaskPage(@Valid BpmTaskTodoPageReqVO pageVO) {
|
||||
return success(taskService.getTodoTaskPage(getLoginUserId(), pageVO));
|
||||
}
|
||||
|
||||
@GetMapping("done-page")
|
||||
@ApiOperation("获取 Done 已办任务分页")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
|
||||
public CommonResult<PageResult<BpmTaskDonePageItemRespVO>> getDoneTaskPage(@Valid BpmTaskDonePageReqVO pageVO) {
|
||||
return success(taskService.getDoneTaskPage(getLoginUserId(), pageVO));
|
||||
}
|
||||
|
||||
@GetMapping("/list-by-process-instance-id")
|
||||
@ApiOperation(value = "获得指定流程实例的任务列表", notes = "包括完成的、未完成的")
|
||||
@ApiImplicitParam(name = "processInstanceId", value = "流程实例的编号", required = true, dataTypeClass = String.class)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
|
||||
public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
|
||||
@RequestParam("processInstanceId") String processInstanceId) {
|
||||
return success(taskService.getTaskListByProcessInstanceId(processInstanceId));
|
||||
}
|
||||
|
||||
@PutMapping("/approve")
|
||||
@ApiOperation("通过任务")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
|
||||
public CommonResult<Boolean> approveTask(@Valid @RequestBody BpmTaskApproveReqVO reqVO) {
|
||||
taskService.approveTask(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/reject")
|
||||
@ApiOperation("不通过任务")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
|
||||
public CommonResult<Boolean> rejectTask(@Valid @RequestBody BpmTaskRejectReqVO reqVO) {
|
||||
taskService.rejectTask(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/update-assignee")
|
||||
@ApiOperation(value = "更新任务的负责人", notes = "用于【流程详情】的【转派】按钮")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
|
||||
public CommonResult<Boolean> updateTaskAssignee(@Valid @RequestBody BpmTaskUpdateAssigneeReqVO reqVO) {
|
||||
taskService.updateTaskAssignee(getLoginUserId(), reqVO);
|
||||
return success(true);
|
||||
}
|
||||
@PutMapping("/back")
|
||||
@ApiOperation(value = "回退")
|
||||
// @PreAuthorize("@ss.hasPermission('bpm:task:back')")
|
||||
public CommonResult<Boolean> backTask(@Valid @RequestBody BpmTaskUpdateAssigneeReqVO reqVO) {
|
||||
//先硬编码到 回退到第一个审批节点
|
||||
String destinationTaskDefKey = "task01";
|
||||
taskService.backTask(reqVO.getId(),destinationTaskDefKey);
|
||||
return success(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.bpm.controller.app;
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 提供 RESTful API 给前端:
|
||||
* 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目
|
||||
* 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分
|
||||
*/
|
||||
package cn.iocoder.yudao.module.bpm.controller;
|
||||
@@ -0,0 +1,140 @@
|
||||
package cn.iocoder.yudao.module.bpm.convert.definition;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
|
||||
import org.flowable.common.engine.impl.db.SuspensionState;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.Model;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 流程模型 Convert
|
||||
*
|
||||
* @author yunlongn
|
||||
*/
|
||||
@Mapper
|
||||
public interface BpmModelConvert {
|
||||
|
||||
BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class);
|
||||
|
||||
default List<BpmModelPageItemRespVO> convertList(List<Model> list, Map<Long, BpmFormDO> formMap,
|
||||
Map<String, Deployment> deploymentMap,
|
||||
Map<String, ProcessDefinition> processDefinitionMap) {
|
||||
return CollectionUtils.convertList(list, model -> {
|
||||
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
|
||||
BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
|
||||
Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null;
|
||||
ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null;
|
||||
return convert(model, form, deployment, processDefinition);
|
||||
});
|
||||
}
|
||||
|
||||
default BpmModelPageItemRespVO convert(Model model, BpmFormDO form, Deployment deployment, ProcessDefinition processDefinition) {
|
||||
BpmModelPageItemRespVO modelRespVO = new BpmModelPageItemRespVO();
|
||||
modelRespVO.setId(model.getId());
|
||||
modelRespVO.setCreateTime(model.getCreateTime());
|
||||
// 通用 copy
|
||||
copyTo(model, modelRespVO);
|
||||
// Form
|
||||
if (form != null) {
|
||||
modelRespVO.setFormId(form.getId());
|
||||
modelRespVO.setFormName(form.getName());
|
||||
}
|
||||
// ProcessDefinition
|
||||
modelRespVO.setProcessDefinition(this.convert(processDefinition));
|
||||
if (modelRespVO.getProcessDefinition() != null) {
|
||||
modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ?
|
||||
SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
|
||||
modelRespVO.getProcessDefinition().setDeploymentTime(deployment.getDeploymentTime());
|
||||
}
|
||||
return modelRespVO;
|
||||
}
|
||||
|
||||
default BpmModelRespVO convert(Model model) {
|
||||
BpmModelRespVO modelRespVO = new BpmModelRespVO();
|
||||
modelRespVO.setId(model.getId());
|
||||
modelRespVO.setCreateTime(model.getCreateTime());
|
||||
// 通用 copy
|
||||
copyTo(model, modelRespVO);
|
||||
return modelRespVO;
|
||||
}
|
||||
|
||||
default void copyTo(Model model, BpmModelBaseVO to) {
|
||||
to.setName(model.getName());
|
||||
to.setKey(model.getKey());
|
||||
to.setCategory(model.getCategory());
|
||||
// metaInfo
|
||||
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
|
||||
copyTo(metaInfo, to);
|
||||
}
|
||||
|
||||
BpmModelCreateReqVO convert(BpmModeImportReqVO bean);
|
||||
|
||||
default BpmProcessDefinitionCreateReqDTO convert2(Model model, BpmFormDO form) {
|
||||
BpmProcessDefinitionCreateReqDTO createReqDTO = new BpmProcessDefinitionCreateReqDTO();
|
||||
createReqDTO.setModelId(model.getId());
|
||||
createReqDTO.setName(model.getName());
|
||||
createReqDTO.setKey(model.getKey());
|
||||
createReqDTO.setCategory(model.getCategory());
|
||||
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
|
||||
// metaInfo
|
||||
copyTo(metaInfo, createReqDTO);
|
||||
// form
|
||||
if (form != null) {
|
||||
createReqDTO.setFormConf(form.getConf());
|
||||
createReqDTO.setFormFields(form.getFields());
|
||||
}
|
||||
return createReqDTO;
|
||||
}
|
||||
|
||||
void copyTo(BpmModelMetaInfoRespDTO from, @MappingTarget BpmProcessDefinitionCreateReqDTO to);
|
||||
|
||||
void copyTo(BpmModelMetaInfoRespDTO from, @MappingTarget BpmModelBaseVO to);
|
||||
|
||||
BpmModelPageItemRespVO.ProcessDefinition convert(ProcessDefinition bean);
|
||||
|
||||
default void copy(Model model, BpmModelCreateReqVO bean) {
|
||||
model.setName(bean.getName());
|
||||
model.setKey(bean.getKey());
|
||||
model.setMetaInfo(buildMetaInfoStr(null, bean.getDescription(), null, null,
|
||||
null, null));
|
||||
}
|
||||
|
||||
default void copy(Model model, BpmModelUpdateReqVO bean) {
|
||||
model.setName(bean.getName());
|
||||
model.setCategory(bean.getCategory());
|
||||
model.setMetaInfo(buildMetaInfoStr(JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class),
|
||||
bean.getDescription(), bean.getFormType(), bean.getFormId(),
|
||||
bean.getFormCustomCreatePath(), bean.getFormCustomViewPath()));
|
||||
}
|
||||
|
||||
default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, String description, Integer formType,
|
||||
Long formId, String formCustomCreatePath, String formCustomViewPath) {
|
||||
if (metaInfo == null) {
|
||||
metaInfo = new BpmModelMetaInfoRespDTO();
|
||||
}
|
||||
// 只有非空,才进行设置,避免更新时的覆盖
|
||||
if (StrUtil.isNotEmpty(description)) {
|
||||
metaInfo.setDescription(description);
|
||||
}
|
||||
if (Objects.nonNull(formType)) {
|
||||
metaInfo.setFormType(formType);
|
||||
metaInfo.setFormId(formId);
|
||||
metaInfo.setFormCustomCreatePath(formCustomCreatePath);
|
||||
metaInfo.setFormCustomViewPath(formCustomViewPath);
|
||||
}
|
||||
return JsonUtils.toJsonString(metaInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package cn.iocoder.yudao.module.bpm.convert.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
|
||||
import org.flowable.common.engine.impl.db.SuspensionState;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.Named;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bpm 流程定义的 Convert
|
||||
*
|
||||
* @author yunlong.li
|
||||
*/
|
||||
@Mapper
|
||||
public interface BpmProcessDefinitionConvert {
|
||||
|
||||
BpmProcessDefinitionConvert INSTANCE = Mappers.getMapper(BpmProcessDefinitionConvert.class);
|
||||
|
||||
BpmProcessDefinitionPageItemRespVO convert(ProcessDefinition bean);
|
||||
|
||||
BpmProcessDefinitionExtDO convert2(BpmProcessDefinitionCreateReqDTO bean);
|
||||
|
||||
default List<BpmProcessDefinitionPageItemRespVO> convertList(List<ProcessDefinition> list, Map<String, Deployment> deploymentMap,
|
||||
Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap, Map<Long, BpmFormDO> formMap) {
|
||||
return CollectionUtils.convertList(list, definition -> {
|
||||
Deployment deployment = definition.getDeploymentId() != null ? deploymentMap.get(definition.getDeploymentId()) : null;
|
||||
BpmProcessDefinitionExtDO definitionDO = processDefinitionDOMap.get(definition.getId());
|
||||
BpmFormDO form = definitionDO != null ? formMap.get(definitionDO.getFormId()) : null;
|
||||
return convert(definition, deployment, definitionDO, form);
|
||||
});
|
||||
}
|
||||
|
||||
default List<BpmProcessDefinitionRespVO> convertList3(List<ProcessDefinition> list,
|
||||
Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap) {
|
||||
return CollectionUtils.convertList(list, processDefinition -> {
|
||||
BpmProcessDefinitionRespVO respVO = convert3(processDefinition);
|
||||
BpmProcessDefinitionExtDO processDefinitionExtDO = processDefinitionDOMap.get(processDefinition.getId());
|
||||
// 复制通用属性
|
||||
copyTo(processDefinitionExtDO, respVO);
|
||||
return respVO;
|
||||
});
|
||||
}
|
||||
|
||||
@Mapping(source = "suspended", target = "suspensionState", qualifiedByName = "convertSuspendedToSuspensionState")
|
||||
BpmProcessDefinitionRespVO convert3(ProcessDefinition bean);
|
||||
|
||||
@Named("convertSuspendedToSuspensionState")
|
||||
default Integer convertSuspendedToSuspensionState(boolean suspended) {
|
||||
return suspended ? SuspensionState.SUSPENDED.getStateCode() :
|
||||
SuspensionState.ACTIVE.getStateCode();
|
||||
}
|
||||
|
||||
default BpmProcessDefinitionPageItemRespVO convert(ProcessDefinition bean, Deployment deployment,
|
||||
BpmProcessDefinitionExtDO processDefinitionExtDO, BpmFormDO form) {
|
||||
BpmProcessDefinitionPageItemRespVO respVO = convert(bean);
|
||||
respVO.setSuspensionState(bean.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
|
||||
if (deployment != null) {
|
||||
respVO.setDeploymentTime(deployment.getDeploymentTime());
|
||||
}
|
||||
if (form != null) {
|
||||
respVO.setFormName(form.getName());
|
||||
}
|
||||
// 复制通用属性
|
||||
copyTo(processDefinitionExtDO, respVO);
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@Mapping(source = "from.id", target = "to.id", ignore = true)
|
||||
void copyTo(BpmProcessDefinitionExtDO from, @MappingTarget BpmProcessDefinitionRespVO to);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.iocoder.yudao.module.bpm.convert.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
||||
import org.flowable.bpmn.model.UserTask;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface BpmTaskAssignRuleConvert {
|
||||
BpmTaskAssignRuleConvert INSTANCE = Mappers.getMapper(BpmTaskAssignRuleConvert.class);
|
||||
|
||||
default List<BpmTaskAssignRuleRespVO> convertList(List<UserTask> tasks, List<BpmTaskAssignRuleDO> rules) {
|
||||
Map<String, BpmTaskAssignRuleDO> ruleMap = CollectionUtils.convertMap(rules, BpmTaskAssignRuleDO::getTaskDefinitionKey);
|
||||
// 以 UserTask 为主维度,原因是:流程图编辑后,一些规则实际就没用了。
|
||||
return CollectionUtils.convertList(tasks, task -> {
|
||||
BpmTaskAssignRuleRespVO respVO = convert(ruleMap.get(task.getId()));
|
||||
if (respVO == null) {
|
||||
respVO = new BpmTaskAssignRuleRespVO();
|
||||
respVO.setTaskDefinitionKey(task.getId());
|
||||
}
|
||||
respVO.setTaskDefinitionName(task.getName());
|
||||
return respVO;
|
||||
});
|
||||
}
|
||||
|
||||
BpmTaskAssignRuleRespVO convert(BpmTaskAssignRuleDO bean);
|
||||
|
||||
BpmTaskAssignRuleDO convert(BpmTaskAssignRuleCreateReqVO bean);
|
||||
|
||||
BpmTaskAssignRuleDO convert(BpmTaskAssignRuleUpdateReqVO bean);
|
||||
|
||||
List<BpmTaskAssignRuleDO> convertList2(List<BpmTaskAssignRuleRespVO> list);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 提供 POJO 类的实体转换
|
||||
*
|
||||
* 目前使用 MapStruct 框架
|
||||
*/
|
||||
package cn.iocoder.yudao.module.bpm.convert;
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.bpm.convert.task;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
|
||||
import org.flowable.engine.history.HistoricActivityInstance;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BPM 活动 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface BpmActivityConvert {
|
||||
|
||||
BpmActivityConvert INSTANCE = Mappers.getMapper(BpmActivityConvert.class);
|
||||
|
||||
List<BpmActivityRespVO> convertList(List<HistoricActivityInstance> list);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "activityId", target = "key"),
|
||||
@Mapping(source = "activityType", target = "type")
|
||||
})
|
||||
BpmActivityRespVO convert(HistoricActivityInstance bean);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package cn.iocoder.yudao.module.bpm.convert.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent;
|
||||
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
|
||||
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 流程实例 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface BpmProcessInstanceConvert {
|
||||
|
||||
BpmProcessInstanceConvert INSTANCE = Mappers.getMapper(BpmProcessInstanceConvert.class);
|
||||
|
||||
default PageResult<BpmProcessInstancePageItemRespVO> convertPage(PageResult<BpmProcessInstanceExtDO> page,
|
||||
Map<String, List<Task>> taskMap) {
|
||||
List<BpmProcessInstancePageItemRespVO> list = convertList(page.getList());
|
||||
list.forEach(respVO -> respVO.setTasks(convertList2(taskMap.get(respVO.getId()))));
|
||||
return new PageResult<>(list, page.getTotal());
|
||||
}
|
||||
|
||||
List<BpmProcessInstancePageItemRespVO> convertList(List<BpmProcessInstanceExtDO> list);
|
||||
|
||||
@Mapping(source = "processInstanceId", target = "id")
|
||||
BpmProcessInstancePageItemRespVO convert(BpmProcessInstanceExtDO bean);
|
||||
|
||||
List<BpmProcessInstancePageItemRespVO.Task> convertList2(List<Task> tasks);
|
||||
|
||||
default BpmProcessInstanceRespVO convert2(HistoricProcessInstance processInstance, BpmProcessInstanceExtDO processInstanceExt,
|
||||
ProcessDefinition processDefinition, BpmProcessDefinitionExtDO processDefinitionExt,
|
||||
String bpmnXml, AdminUserRespDTO startUser, DeptRespDTO dept) {
|
||||
BpmProcessInstanceRespVO respVO = convert2(processInstance);
|
||||
copyTo(processInstanceExt, respVO);
|
||||
// definition
|
||||
respVO.setProcessDefinition(convert2(processDefinition));
|
||||
copyTo(processDefinitionExt, respVO.getProcessDefinition());
|
||||
respVO.getProcessDefinition().setBpmnXml(bpmnXml);
|
||||
// user
|
||||
if (startUser != null) {
|
||||
respVO.setStartUser(convert2(startUser));
|
||||
if (dept != null) {
|
||||
respVO.getStartUser().setDeptName(dept.getName());
|
||||
}
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
BpmProcessInstanceRespVO convert2(HistoricProcessInstance bean);
|
||||
|
||||
@Mapping(source = "from.id", target = "to.id", ignore = true)
|
||||
void copyTo(BpmProcessInstanceExtDO from, @MappingTarget BpmProcessInstanceRespVO to);
|
||||
|
||||
BpmProcessInstanceRespVO.ProcessDefinition convert2(ProcessDefinition bean);
|
||||
|
||||
@Mapping(source = "from.id", target = "to.id", ignore = true)
|
||||
void copyTo(BpmProcessDefinitionExtDO from, @MappingTarget BpmProcessInstanceRespVO.ProcessDefinition to);
|
||||
|
||||
BpmProcessInstanceRespVO.User convert2(AdminUserRespDTO bean);
|
||||
|
||||
default BpmProcessInstanceResultEvent convert(Object source, HistoricProcessInstance instance, Integer result) {
|
||||
BpmProcessInstanceResultEvent event = new BpmProcessInstanceResultEvent(source);
|
||||
event.setId(instance.getId());
|
||||
event.setProcessDefinitionKey(instance.getProcessDefinitionKey());
|
||||
event.setBusinessKey(instance.getBusinessKey());
|
||||
event.setResult(result);
|
||||
return event;
|
||||
}
|
||||
|
||||
default BpmProcessInstanceResultEvent convert(Object source, ProcessInstance instance, Integer result) {
|
||||
BpmProcessInstanceResultEvent event = new BpmProcessInstanceResultEvent(source);
|
||||
event.setId(instance.getId());
|
||||
event.setProcessDefinitionKey(instance.getProcessDefinitionKey());
|
||||
event.setBusinessKey(instance.getBusinessKey());
|
||||
event.setResult(result);
|
||||
return event;
|
||||
}
|
||||
|
||||
default BpmMessageSendWhenProcessInstanceApproveReqDTO convert2ApprovedReq(ProcessInstance instance){
|
||||
return new BpmMessageSendWhenProcessInstanceApproveReqDTO()
|
||||
.setStartUserId(NumberUtils.parseLong(instance.getStartUserId()))
|
||||
.setProcessInstanceId(instance.getId())
|
||||
.setProcessInstanceName(instance.getName());
|
||||
}
|
||||
|
||||
default BpmMessageSendWhenProcessInstanceRejectReqDTO convert2RejectReq(ProcessInstance instance, String comment) {
|
||||
return new BpmMessageSendWhenProcessInstanceRejectReqDTO()
|
||||
.setProcessInstanceName(instance.getName())
|
||||
.setProcessInstanceId(instance.getId())
|
||||
.setComment(comment)
|
||||
.setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package cn.iocoder.yudao.module.bpm.convert.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
|
||||
import org.flowable.common.engine.impl.db.SuspensionState;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.flowable.task.api.history.HistoricTaskInstance;
|
||||
import org.mapstruct.*;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bpm 任务 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface BpmTaskConvert {
|
||||
|
||||
BpmTaskConvert INSTANCE = Mappers.getMapper(BpmTaskConvert.class);
|
||||
|
||||
default List<BpmTaskTodoPageItemRespVO> convertList1(List<Task> tasks, Map<String, ProcessInstance> processInstanceMap,
|
||||
Map<Long, AdminUserRespDTO> userMap) {
|
||||
return CollectionUtils.convertList(tasks, task -> {
|
||||
BpmTaskTodoPageItemRespVO respVO = convert1(task);
|
||||
ProcessInstance processInstance = processInstanceMap.get(task.getProcessInstanceId());
|
||||
if (processInstance != null) {
|
||||
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
|
||||
respVO.setProcessInstance(convert(processInstance, startUser));
|
||||
}
|
||||
return respVO;
|
||||
});
|
||||
}
|
||||
|
||||
@Mapping(source = "suspended", target = "suspensionState", qualifiedByName = "convertSuspendedToSuspensionState")
|
||||
BpmTaskTodoPageItemRespVO convert1(Task bean);
|
||||
|
||||
@Named("convertSuspendedToSuspensionState")
|
||||
default Integer convertSuspendedToSuspensionState(boolean suspended) {
|
||||
return suspended ? SuspensionState.SUSPENDED.getStateCode() :
|
||||
SuspensionState.ACTIVE.getStateCode();
|
||||
}
|
||||
|
||||
default List<BpmTaskDonePageItemRespVO> convertList2(List<HistoricTaskInstance> tasks, Map<String, BpmTaskExtDO> bpmTaskExtDOMap,
|
||||
Map<String, HistoricProcessInstance> historicProcessInstanceMap,
|
||||
Map<Long, AdminUserRespDTO> userMap) {
|
||||
return CollectionUtils.convertList(tasks, task -> {
|
||||
BpmTaskDonePageItemRespVO respVO = convert2(task);
|
||||
BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
|
||||
copyTo(taskExtDO, respVO);
|
||||
HistoricProcessInstance processInstance = historicProcessInstanceMap.get(task.getProcessInstanceId());
|
||||
if (processInstance != null) {
|
||||
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
|
||||
respVO.setProcessInstance(convert(processInstance, startUser));
|
||||
}
|
||||
return respVO;
|
||||
});
|
||||
}
|
||||
|
||||
BpmTaskDonePageItemRespVO convert2(HistoricTaskInstance bean);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "processInstance.id", target = "id"),
|
||||
@Mapping(source = "processInstance.name", target = "name"),
|
||||
@Mapping(source = "processInstance.startUserId", target = "startUserId"),
|
||||
@Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
|
||||
@Mapping(source = "startUser.nickname", target = "startUserNickname")
|
||||
})
|
||||
BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser);
|
||||
|
||||
default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks, Map<String, BpmTaskExtDO> bpmTaskExtDOMap,
|
||||
HistoricProcessInstance processInstance, Map<Long, AdminUserRespDTO> userMap,
|
||||
Map<Long, DeptRespDTO> deptMap) {
|
||||
return CollectionUtils.convertList(tasks, task -> {
|
||||
BpmTaskRespVO respVO = convert3(task);
|
||||
BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
|
||||
copyTo(taskExtDO, respVO);
|
||||
if (processInstance != null) {
|
||||
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
|
||||
respVO.setProcessInstance(convert(processInstance, startUser));
|
||||
}
|
||||
AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
|
||||
if (assignUser != null) {
|
||||
respVO.setAssigneeUser(convert3(assignUser));
|
||||
DeptRespDTO dept = deptMap.get(assignUser.getDeptId());
|
||||
if (dept != null) {
|
||||
respVO.getAssigneeUser().setDeptName(dept.getName());
|
||||
}
|
||||
}
|
||||
return respVO;
|
||||
});
|
||||
}
|
||||
|
||||
@Mapping(source = "taskDefinitionKey", target = "definitionKey")
|
||||
BpmTaskRespVO convert3(HistoricTaskInstance bean);
|
||||
|
||||
BpmTaskRespVO.User convert3(AdminUserRespDTO bean);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "processInstance.id", target = "id"),
|
||||
@Mapping(source = "processInstance.name", target = "name"),
|
||||
@Mapping(source = "processInstance.startUserId", target = "startUserId"),
|
||||
@Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
|
||||
@Mapping(source = "startUser.nickname", target = "startUserNickname")
|
||||
})
|
||||
BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance, AdminUserRespDTO startUser);
|
||||
|
||||
default BpmTaskExtDO convert2TaskExt(Task task){
|
||||
BpmTaskExtDO taskExtDO = new BpmTaskExtDO()
|
||||
.setTaskId(task.getId())
|
||||
.setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()))
|
||||
.setName(task.getName())
|
||||
.setProcessDefinitionId(task.getProcessDefinitionId())
|
||||
.setProcessInstanceId(task.getProcessInstanceId());
|
||||
taskExtDO.setCreateTime(task.getCreateTime());
|
||||
return taskExtDO;
|
||||
}
|
||||
|
||||
default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, Task task) {
|
||||
BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
|
||||
reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId())
|
||||
.setProcessInstanceName(processInstance.getName())
|
||||
.setStartUserId(startUser.getId())
|
||||
.setStartUserNickname(startUser.getNickname())
|
||||
.setTaskId(task.getId())
|
||||
.setTaskName(task.getName())
|
||||
.setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()));
|
||||
return reqDTO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao>
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.config;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
|
||||
import org.flowable.spring.SpringProcessEngineConfiguration;
|
||||
import org.flowable.spring.boot.EngineConfigurationConfigurer;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BPM 模块的 Flowable 配置类
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Configuration
|
||||
public class BpmFlowableConfiguration {
|
||||
|
||||
/**
|
||||
* BPM 模块的 ProcessEngineConfigurationConfigurer 实现类:
|
||||
*
|
||||
* 1. 设置各种监听器
|
||||
* 2. 设置自定义的 ActivityBehaviorFactory 实现
|
||||
*/
|
||||
@Bean
|
||||
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer(
|
||||
ObjectProvider<FlowableEventListener> listeners,
|
||||
BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
|
||||
return configuration -> {
|
||||
// 注册监听器,例如说 BpmActivitiEventListener
|
||||
configuration.setEventListeners(ListUtil.toList(listeners.iterator()));
|
||||
// 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义
|
||||
configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BpmActivityBehaviorFactory bpmActivityBehaviorFactory(BpmTaskAssignRuleService taskRuleService,
|
||||
BpmUserGroupService userGroupService,
|
||||
PermissionApi permissionApi,
|
||||
DeptApi deptApi,
|
||||
AdminUserApi adminUserApi,
|
||||
List<BpmTaskAssignScript> scripts) {
|
||||
BpmActivityBehaviorFactory bpmActivityBehaviorFactory = new BpmActivityBehaviorFactory();
|
||||
bpmActivityBehaviorFactory.setBpmTaskRuleService(taskRuleService);
|
||||
bpmActivityBehaviorFactory.setUserGroupService(userGroupService);
|
||||
bpmActivityBehaviorFactory.setAdminUserApi(adminUserApi);
|
||||
bpmActivityBehaviorFactory.setPermissionApi(permissionApi);
|
||||
bpmActivityBehaviorFactory.setDeptApi(deptApi);
|
||||
bpmActivityBehaviorFactory.setScripts(scripts);
|
||||
return bpmActivityBehaviorFactory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.flowable.bpmn.model.UserTask;
|
||||
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
|
||||
import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义的 ActivityBehaviorFactory 实现类,目的如下:
|
||||
* 1. 自定义 {@link #createUserTaskActivityBehavior(UserTask)}:实现自定义的流程任务的 assignee 负责人的分配
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
|
||||
|
||||
@Setter
|
||||
private BpmTaskAssignRuleService bpmTaskRuleService;
|
||||
@Setter
|
||||
private BpmUserGroupService userGroupService;
|
||||
|
||||
@Setter
|
||||
private PermissionApi permissionApi;
|
||||
@Setter
|
||||
private DeptApi deptApi;
|
||||
@Setter
|
||||
private AdminUserApi adminUserApi;
|
||||
@Setter
|
||||
private List<BpmTaskAssignScript> scripts;
|
||||
|
||||
@Override
|
||||
public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
|
||||
BpmUserTaskActivityBehavior userTaskActivityBehavior = new BpmUserTaskActivityBehavior(userTask);
|
||||
userTaskActivityBehavior.setBpmTaskRuleService(bpmTaskRuleService);
|
||||
userTaskActivityBehavior.setPermissionApi(permissionApi);
|
||||
userTaskActivityBehavior.setDeptApi(deptApi);
|
||||
userTaskActivityBehavior.setUserGroupService(userGroupService);
|
||||
userTaskActivityBehavior.setAdminUserApi(adminUserApi);
|
||||
userTaskActivityBehavior.setScripts(scripts);
|
||||
return userTaskActivityBehavior;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.bpmn.model.UserTask;
|
||||
import org.flowable.common.engine.api.FlowableException;
|
||||
import org.flowable.common.engine.impl.el.ExpressionManager;
|
||||
import org.flowable.engine.delegate.DelegateExecution;
|
||||
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
|
||||
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
|
||||
import org.flowable.engine.impl.util.TaskHelper;
|
||||
import org.flowable.task.service.TaskService;
|
||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_ASSIGN_SCRIPT_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
|
||||
|
||||
/**
|
||||
* 自定义的流程任务的 assignee 负责人的分配
|
||||
* 第一步,获得对应的分配规则;
|
||||
* 第二步,根据分配规则,计算出分配任务的候选人。如果找不到,则直接报业务异常,不继续执行后续的流程;
|
||||
* 第三步,随机选择一个候选人,则选择作为 assignee 负责人。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
|
||||
|
||||
@Setter
|
||||
private BpmTaskAssignRuleService bpmTaskRuleService;
|
||||
@Setter
|
||||
private BpmUserGroupService userGroupService;
|
||||
@Setter
|
||||
private DeptApi deptApi;
|
||||
@Setter
|
||||
private AdminUserApi adminUserApi;
|
||||
@Setter
|
||||
private PermissionApi permissionApi;
|
||||
|
||||
/**
|
||||
* 任务分配脚本
|
||||
*/
|
||||
private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();
|
||||
|
||||
public BpmUserTaskActivityBehavior(UserTask userTask) {
|
||||
super(userTask);
|
||||
}
|
||||
|
||||
public void setScripts(List<BpmTaskAssignScript> scripts) {
|
||||
this.scriptMap = convertMap(scripts, script -> script.getEnum().getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataPermission(enable = false) // 不需要处理数据权限, 不然会有问题,查询不到数据
|
||||
protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
|
||||
boolean isMultiInstance = hasMultiInstanceCharacteristics();
|
||||
if(isMultiInstance){
|
||||
//多实例 会签/或签,执行多次每个人 待办人都在execution里面获取
|
||||
Integer assigneeUserId = execution.getVariableLocal("user", Integer.class);
|
||||
TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
|
||||
}else {
|
||||
// 第一步,获得任务的规则
|
||||
BpmTaskAssignRuleDO rule = getTaskRule(task);
|
||||
// 第二步,获得任务的候选用户们
|
||||
Set<Long> candidateUserIds = calculateTaskCandidateUsers(task, rule);
|
||||
// 第三步,设置一个作为负责人
|
||||
Long assigneeUserId = chooseTaskAssignee(candidateUserIds);
|
||||
TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private BpmTaskAssignRuleDO getTaskRule(TaskEntity task) {
|
||||
List<BpmTaskAssignRuleDO> taskRules = bpmTaskRuleService.getTaskAssignRuleListByProcessDefinitionId(task.getProcessDefinitionId(),
|
||||
task.getTaskDefinitionKey());
|
||||
if (CollUtil.isEmpty(taskRules)) {
|
||||
throw new FlowableException(StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则",
|
||||
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey()));
|
||||
}
|
||||
if (taskRules.size() > 1) {
|
||||
throw new FlowableException(StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})",
|
||||
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), taskRules.size()));
|
||||
}
|
||||
return taskRules.get(0);
|
||||
}
|
||||
|
||||
Set<Long> calculateTaskCandidateUsers(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
||||
Set<Long> assigneeUserIds = null;
|
||||
if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
|
||||
assigneeUserIds = calculateTaskCandidateUsersByRole(task, rule);
|
||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
|
||||
assigneeUserIds = calculateTaskCandidateUsersByDeptMember(task, rule);
|
||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
|
||||
assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(task, rule);
|
||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
|
||||
assigneeUserIds = calculateTaskCandidateUsersByPost(task, rule);
|
||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
|
||||
assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
|
||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
|
||||
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
|
||||
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
|
||||
assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
|
||||
}
|
||||
|
||||
// 移除被禁用的用户
|
||||
removeDisableUsers(assigneeUserIds);
|
||||
// 如果候选人为空,抛出异常 TODO 芋艿:没候选人的策略选择。1 - 挂起;2 - 直接结束;3 - 强制一个兜底人
|
||||
if (CollUtil.isEmpty(assigneeUserIds)) {
|
||||
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]",
|
||||
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), toJsonString(rule));
|
||||
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
|
||||
}
|
||||
return assigneeUserIds;
|
||||
}
|
||||
|
||||
private Set<Long> calculateTaskCandidateUsersByRole(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
||||
return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
|
||||
}
|
||||
|
||||
private Set<Long> calculateTaskCandidateUsersByDeptMember(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
||||
List<AdminUserRespDTO> users = adminUserApi.getUsersByDeptIds(rule.getOptions());
|
||||
return convertSet(users, AdminUserRespDTO::getId);
|
||||
}
|
||||
|
||||
private Set<Long> calculateTaskCandidateUsersByDeptLeader(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
||||
List<DeptRespDTO> depts = deptApi.getDepts(rule.getOptions());
|
||||
return convertSet(depts, DeptRespDTO::getLeaderUserId);
|
||||
}
|
||||
|
||||
private Set<Long> calculateTaskCandidateUsersByPost(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
||||
List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
|
||||
return convertSet(users, AdminUserRespDTO::getId);
|
||||
}
|
||||
|
||||
private Set<Long> calculateTaskCandidateUsersByUser(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
||||
return rule.getOptions();
|
||||
}
|
||||
|
||||
private Set<Long> calculateTaskCandidateUsersByUserGroup(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
||||
List<BpmUserGroupDO> userGroups = userGroupService.getUserGroupList(rule.getOptions());
|
||||
Set<Long> userIds = new HashSet<>();
|
||||
userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds()));
|
||||
return userIds;
|
||||
}
|
||||
|
||||
private Set<Long> calculateTaskCandidateUsersByScript(TaskEntity task, BpmTaskAssignRuleDO rule) {
|
||||
// 获得对应的脚本
|
||||
List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
|
||||
rule.getOptions().forEach(id -> {
|
||||
BpmTaskAssignScript script = scriptMap.get(id);
|
||||
if (script == null) {
|
||||
throw exception(TASK_ASSIGN_SCRIPT_NOT_EXISTS, id);
|
||||
}
|
||||
scripts.add(script);
|
||||
});
|
||||
// 逐个计算任务
|
||||
Set<Long> userIds = new HashSet<>();
|
||||
scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(task)));
|
||||
return userIds;
|
||||
}
|
||||
|
||||
private Long chooseTaskAssignee(Set<Long> candidateUserIds) {
|
||||
// TODO 芋艿:未来可以优化下,改成轮询的策略
|
||||
int index = RandomUtil.randomInt(candidateUserIds.size());
|
||||
return CollUtil.get(candidateUserIds, index);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void removeDisableUsers(Set<Long> assigneeUserIds) {
|
||||
if (CollUtil.isEmpty(assigneeUserIds)) {
|
||||
return;
|
||||
}
|
||||
//TODO 芋艿 这里有数据权限的问题。默认会加上数据权限 dept_id IN (deptId). 导致查询不到数据
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
|
||||
assigneeUserIds.removeIf(id -> {
|
||||
AdminUserRespDTO user = userMap.get(id);
|
||||
return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
|
||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Bpm 任务分配的自定义 Script 脚本
|
||||
* 使用场景:
|
||||
* 1. 设置审批人为发起人
|
||||
* 2. 设置审批人为发起人的 Leader
|
||||
* 3. 甚至审批人为发起人的 Leader 的 Leader
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface BpmTaskAssignScript {
|
||||
|
||||
/**
|
||||
* 基于流程任务,获得任务的候选用户们
|
||||
*
|
||||
* @param task 任务
|
||||
* @return 候选人用户的编号数组
|
||||
*/
|
||||
Set<Long> calculateTaskCandidateUsers(TaskEntity task);
|
||||
|
||||
/**
|
||||
* 获得枚举值
|
||||
*
|
||||
* @return 枚举值
|
||||
*/
|
||||
BpmTaskRuleScriptEnum getEnum();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
/**
|
||||
* 分配给发起人的 Leader 审批的 Script 实现类
|
||||
* 目前 Leader 的定义是,
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public abstract class BpmTaskAssignLeaderAbstractScript implements BpmTaskAssignScript {
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private DeptApi deptApi;
|
||||
@Resource
|
||||
@Lazy // 解决循环依赖
|
||||
private BpmProcessInstanceService bpmProcessInstanceService;
|
||||
|
||||
protected Set<Long> calculateTaskCandidateUsers(TaskEntity task, int level) {
|
||||
Assert.isTrue(level > 0, "level 必须大于 0");
|
||||
// 获得发起人
|
||||
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
|
||||
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
|
||||
// 获得对应 leve 的部门
|
||||
DeptRespDTO dept = null;
|
||||
for (int i = 0; i < level; i++) {
|
||||
// 获得 level 对应的部门
|
||||
if (dept == null) {
|
||||
dept = getStartUserDept(startUserId);
|
||||
if (dept == null) { // 找不到发起人的部门,所以无法使用该规则
|
||||
return emptySet();
|
||||
}
|
||||
} else {
|
||||
DeptRespDTO parentDept = deptApi.getDept(dept.getParentId());
|
||||
if (parentDept == null) { // 找不到父级部门,所以只好结束寻找。原因是:例如说,级别比较高的人,所在部门层级比较少
|
||||
break;
|
||||
}
|
||||
dept = parentDept;
|
||||
}
|
||||
}
|
||||
return dept.getLeaderUserId() != null ? asSet(dept.getLeaderUserId()) : emptySet();
|
||||
}
|
||||
|
||||
private DeptRespDTO getStartUserDept(Long startUserId) {
|
||||
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
|
||||
if (startUser.getDeptId() == null) { // 找不到部门,所以无法使用该规则
|
||||
return null;
|
||||
}
|
||||
return deptApi.getDept(startUser.getDeptId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
|
||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 分配给发起人的一级 Leader 审批的 Script 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
public class BpmTaskAssignLeaderX1Script extends BpmTaskAssignLeaderAbstractScript {
|
||||
|
||||
@Override
|
||||
@DataPermission(enable = false) // 不需要处理数据权限, 不然会有问题,查询不到数据
|
||||
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
|
||||
return calculateTaskCandidateUsers(task, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BpmTaskRuleScriptEnum getEnum() {
|
||||
return BpmTaskRuleScriptEnum.LEADER_X1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
|
||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 分配给发起人的二级 Leader 审批的 Script 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
public class BpmTaskAssignLeaderX2Script extends BpmTaskAssignLeaderAbstractScript {
|
||||
|
||||
@Override
|
||||
@DataPermission(enable = false) // 不需要处理数据权限, 不然会有问题,查询不到数据
|
||||
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
|
||||
return calculateTaskCandidateUsers(task, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BpmTaskRuleScriptEnum getEnum() {
|
||||
return BpmTaskRuleScriptEnum.LEADER_X2;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 分配给发起人审批的 Script 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
public class BpmTaskAssignStartUserScript implements BpmTaskAssignScript {
|
||||
|
||||
@Resource
|
||||
@Lazy // 解决循环依赖
|
||||
private BpmProcessInstanceService bpmProcessInstanceService;
|
||||
|
||||
@Override
|
||||
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
|
||||
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
|
||||
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
|
||||
return SetUtils.asSet(startUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BpmTaskRuleScriptEnum getEnum() {
|
||||
return BpmTaskRuleScriptEnum.START_USER;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
|
||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
|
||||
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
|
||||
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
|
||||
import org.flowable.engine.delegate.event.FlowableProcessStartedEvent;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Set;
|
||||
/**
|
||||
* 监听 {@link ProcessInstance} 的开始与完成,创建与更新对应的 {@link BpmProcessInstanceExtDO} 记录
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Component
|
||||
public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private BpmProcessInstanceService processInstanceService;
|
||||
|
||||
public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
|
||||
.add(FlowableEngineEventType.PROCESS_CREATED)
|
||||
.add(FlowableEngineEventType.PROCESS_CANCELLED)
|
||||
.add(FlowableEngineEventType.PROCESS_COMPLETED)
|
||||
.build();
|
||||
|
||||
public BpmProcessInstanceEventListener(){
|
||||
super(PROCESS_INSTANCE_EVENTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processCreated(FlowableEngineEntityEvent event) {
|
||||
processInstanceService.createProcessInstanceExt((ProcessInstance)event.getEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processCancelled(FlowableCancelledEvent event) {
|
||||
processInstanceService.updateProcessInstanceExtCancel(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processCompleted(FlowableEngineEntityEvent event) {
|
||||
processInstanceService.updateProcessInstanceExtComplete((ProcessInstance)event.getEntity());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
|
||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
|
||||
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 监听 {@link org.flowable.task.api.Task} 的开始与完成,创建与更新对应的 {@link BpmTaskExtDO} 记录
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Component
|
||||
public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
|
||||
|
||||
@Resource
|
||||
@Lazy // 解决循环依赖
|
||||
private BpmTaskService taskService;
|
||||
|
||||
public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
|
||||
.add(FlowableEngineEventType.TASK_CREATED)
|
||||
.add(FlowableEngineEventType.TASK_ASSIGNED)
|
||||
.add(FlowableEngineEventType.TASK_COMPLETED)
|
||||
.build();
|
||||
|
||||
public BpmTaskEventListener(){
|
||||
super(TASK_EVENTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void taskCreated(FlowableEngineEntityEvent event) {
|
||||
taskService.createTaskExt((Task) event.getEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void taskCompleted(FlowableEngineEntityEvent event) {
|
||||
taskService.updateTaskExtComplete((Task)event.getEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void taskAssigned(FlowableEngineEntityEvent event) {
|
||||
taskService.updateTaskExtAssign((Task)event.getEntity());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 属于 bpm 模块的 framework 封装
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
package cn.iocoder.yudao.module.bpm.framework;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能,基于 activiti 7 版本实现。
|
||||
* 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等
|
||||
*
|
||||
* bpm 解释:https://baike.baidu.com/item/BPM/1933
|
||||
*
|
||||
* 1. Controller URL:以 /bpm/ 开头,避免和其它 Module 冲突
|
||||
* 2. DataObject 表名:以 bpm_ 开头,方便在数据库中区分
|
||||
*
|
||||
* 注意,由于 Bpm 模块下,容易和其它模块重名,所以类名都加载 Pay 的前缀~
|
||||
*/
|
||||
package cn.iocoder.yudao.module.bpm;
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* Flowable流程模型接口
|
||||
*
|
||||
* @author yunlongn
|
||||
*/
|
||||
public interface BpmModelService {
|
||||
/**
|
||||
* 获得流程模型分页
|
||||
*
|
||||
* @param pageVO 分页查询
|
||||
* @return 流程模型分页
|
||||
*/
|
||||
PageResult<BpmModelPageItemRespVO> getModelPage(BpmModelPageReqVO pageVO);
|
||||
|
||||
/**
|
||||
* 创建流程模型
|
||||
*
|
||||
* @param modelVO 创建信息
|
||||
* @param bpmnXml BPMN XML
|
||||
* @return 创建的流程模型的编号
|
||||
*/
|
||||
String createModel(@Valid BpmModelCreateReqVO modelVO, String bpmnXml);
|
||||
|
||||
/**
|
||||
* 获得流程模块
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 流程模型
|
||||
*/
|
||||
BpmModelRespVO getModel(String id);
|
||||
|
||||
/**
|
||||
* 修改流程模型
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateModel(@Valid BpmModelUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 将流程模型,部署成一个流程定义
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deployModel(String id);
|
||||
|
||||
/**
|
||||
* 删除模型
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteModel(String id);
|
||||
|
||||
/**
|
||||
* 修改模型的状态,实际更新的部署的流程定义的状态
|
||||
*
|
||||
* @param id 编号
|
||||
* @param state 状态
|
||||
*/
|
||||
void updateModelState(String id, Integer state);
|
||||
|
||||
/**
|
||||
* 获得流程模型编号对应的 BPMN Model
|
||||
*
|
||||
* @param id 流程模型编号
|
||||
* @return BPMN Model
|
||||
*/
|
||||
BpmnModel getBpmnModel(String id);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.definition;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
|
||||
import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.bpmn.converter.BpmnXMLConverter;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.common.engine.impl.db.SuspensionState;
|
||||
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
|
||||
import org.flowable.engine.RepositoryService;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.Model;
|
||||
import org.flowable.engine.repository.ModelQuery;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* Flowable流程模型实现
|
||||
* 主要进行 Flowable {@link Model} 的维护
|
||||
*
|
||||
* @author yunlongn
|
||||
* @author 芋道源码
|
||||
* @author jason
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class BpmModelServiceImpl implements BpmModelService {
|
||||
|
||||
@Resource
|
||||
private RepositoryService repositoryService;
|
||||
@Resource
|
||||
private BpmProcessDefinitionService processDefinitionService;
|
||||
@Resource
|
||||
private BpmFormService bpmFormService;
|
||||
@Resource
|
||||
private BpmTaskAssignRuleService taskAssignRuleService;
|
||||
|
||||
@Override
|
||||
public PageResult<BpmModelPageItemRespVO> getModelPage(BpmModelPageReqVO pageVO) {
|
||||
ModelQuery modelQuery = repositoryService.createModelQuery();
|
||||
if (StrUtil.isNotBlank(pageVO.getKey())) {
|
||||
modelQuery.modelKey(pageVO.getKey());
|
||||
}
|
||||
if (StrUtil.isNotBlank(pageVO.getName())) {
|
||||
modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配
|
||||
}
|
||||
if (StrUtil.isNotBlank(pageVO.getCategory())) {
|
||||
modelQuery.modelCategory(pageVO.getCategory());
|
||||
}
|
||||
// 执行查询
|
||||
List<Model> models = modelQuery.orderByCreateTime().desc()
|
||||
.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
|
||||
|
||||
// 获得 Form Map
|
||||
Set<Long> formIds = CollectionUtils.convertSet(models, model -> {
|
||||
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
|
||||
return metaInfo != null ? metaInfo.getFormId() : null;
|
||||
});
|
||||
Map<Long, BpmFormDO> formMap = bpmFormService.getFormMap(formIds);
|
||||
|
||||
// 获得 Deployment Map
|
||||
Set<String> deploymentIds = new HashSet<>();
|
||||
models.forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
|
||||
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
|
||||
// 获得 ProcessDefinition Map
|
||||
List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
|
||||
Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
|
||||
|
||||
// 拼接结果
|
||||
long modelCount = modelQuery.count();
|
||||
return new PageResult<>(BpmModelConvert.INSTANCE.convertList(models, formMap, deploymentMap, processDefinitionMap), modelCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String createModel(@Valid BpmModelCreateReqVO createReqVO, String bpmnXml) {
|
||||
checkKeyNCName(createReqVO.getKey());
|
||||
// 校验流程标识已经存在
|
||||
Model keyModel = this.getModelByKey(createReqVO.getKey());
|
||||
if (keyModel != null) {
|
||||
throw exception(MODEL_KEY_EXISTS, createReqVO.getKey());
|
||||
}
|
||||
|
||||
// 创建流程定义
|
||||
Model model = repositoryService.newModel();
|
||||
BpmModelConvert.INSTANCE.copy(model, createReqVO);
|
||||
// 保存流程定义
|
||||
repositoryService.saveModel(model);
|
||||
// 保存 BPMN XML
|
||||
saveModelBpmnXml(model, bpmnXml);
|
||||
return model.getId();
|
||||
}
|
||||
|
||||
private Model getModelByKey(String key) {
|
||||
return repositoryService.createModelQuery().modelKey(key).singleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BpmModelRespVO getModel(String id) {
|
||||
Model model = repositoryService.getModel(id);
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
BpmModelRespVO modelRespVO = BpmModelConvert.INSTANCE.convert(model);
|
||||
// 拼接 bpmn XML
|
||||
byte[] bpmnBytes = repositoryService.getModelEditorSource(id);
|
||||
modelRespVO.setBpmnXml(StrUtil.utf8Str(bpmnBytes));
|
||||
return modelRespVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
|
||||
public void updateModel(@Valid BpmModelUpdateReqVO updateReqVO) {
|
||||
// 校验流程模型存在
|
||||
Model model = repositoryService.getModel(updateReqVO.getId());
|
||||
if (model == null) {
|
||||
throw exception(MODEL_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 修改流程定义
|
||||
BpmModelConvert.INSTANCE.copy(model, updateReqVO);
|
||||
// 更新模型
|
||||
repositoryService.saveModel(model);
|
||||
// 更新 BPMN XML
|
||||
saveModelBpmnXml(model, updateReqVO.getBpmnXml());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
|
||||
public void deployModel(String id) {
|
||||
// 校验流程模型存在
|
||||
Model model = repositoryService.getModel(id);
|
||||
if (ObjectUtils.isEmpty(model)) {
|
||||
throw exception(MODEL_NOT_EXISTS);
|
||||
}
|
||||
// 校验流程图
|
||||
byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId());
|
||||
if (bpmnBytes == null) {
|
||||
throw exception(MODEL_NOT_EXISTS);
|
||||
}
|
||||
// TODO 芋艿:校验流程图的有效性;例如说,是否有开始的元素,是否有结束的元素;
|
||||
// 校验表单已配
|
||||
BpmFormDO form = checkFormConfig(model.getMetaInfo());
|
||||
//校验任务分配规则已配置
|
||||
taskAssignRuleService.checkTaskAssignRuleAllConfig(id);
|
||||
|
||||
BpmProcessDefinitionCreateReqDTO definitionCreateReqDTO = BpmModelConvert.INSTANCE.convert2(model, form).setBpmnBytes(bpmnBytes);
|
||||
//校验模型是否发生修改。如果未修改,则不允许创建
|
||||
if (processDefinitionService.isProcessDefinitionEquals(definitionCreateReqDTO)) { // 流程定义的信息相等
|
||||
ProcessDefinition oldProcessInstance = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
|
||||
if (oldProcessInstance != null && taskAssignRuleService.isTaskAssignRulesEquals(model.getId(), oldProcessInstance.getId())) {
|
||||
throw exception(MODEL_DEPLOY_FAIL_TASK_INFO_EQUALS);
|
||||
}
|
||||
}
|
||||
// 创建流程定义
|
||||
String definitionId = processDefinitionService.createProcessDefinition(definitionCreateReqDTO);
|
||||
|
||||
// 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
|
||||
updateProcessDefinitionSuspended(model.getDeploymentId());
|
||||
|
||||
// 更新 model 的 deploymentId,进行关联
|
||||
ProcessDefinition definition = processDefinitionService.getProcessDefinition(definitionId);
|
||||
model.setDeploymentId(definition.getDeploymentId());
|
||||
repositoryService.saveModel(model);
|
||||
|
||||
//复制任务分配规则
|
||||
taskAssignRuleService.copyTaskAssignRules(id, definition.getId());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteModel(String id) {
|
||||
// 校验流程模型存在
|
||||
Model model = repositoryService.getModel(id);
|
||||
if (model == null) {
|
||||
throw exception(MODEL_NOT_EXISTS);
|
||||
}
|
||||
// 执行删除
|
||||
repositoryService.deleteModel(id);
|
||||
// 禁用流程实例
|
||||
updateProcessDefinitionSuspended(model.getDeploymentId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateModelState(String id, Integer state) {
|
||||
// 校验流程模型存在
|
||||
Model model = repositoryService.getModel(id);
|
||||
if (model == null) {
|
||||
throw exception(MODEL_NOT_EXISTS);
|
||||
}
|
||||
// 校验流程定义存在
|
||||
ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
|
||||
if (definition == null) {
|
||||
throw exception(PROCESS_DEFINITION_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
processDefinitionService.updateProcessDefinitionState(definition.getId(), state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BpmnModel getBpmnModel(String id) {
|
||||
byte[] bpmnBytes = repositoryService.getModelEditorSource(id);
|
||||
if (ArrayUtil.isEmpty(bpmnBytes)) {
|
||||
return null;
|
||||
}
|
||||
BpmnXMLConverter converter = new BpmnXMLConverter();
|
||||
return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
|
||||
}
|
||||
|
||||
private void checkKeyNCName(String key) {
|
||||
if (!ValidationUtils.isXmlNCName(key)) {
|
||||
throw exception(MODEL_KEY_VALID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验流程表单已配置
|
||||
*
|
||||
* @param metaInfoStr 流程模型 metaInfo 字段
|
||||
* @return 流程表单
|
||||
*/
|
||||
private BpmFormDO checkFormConfig(String metaInfoStr) {
|
||||
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(metaInfoStr, BpmModelMetaInfoRespDTO.class);
|
||||
if (metaInfo == null || metaInfo.getFormType() == null) {
|
||||
throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
|
||||
}
|
||||
// 校验表单存在
|
||||
if (Objects.equals(metaInfo.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) {
|
||||
BpmFormDO form = bpmFormService.getForm(metaInfo.getFormId());
|
||||
if (form == null) {
|
||||
throw exception(FORM_NOT_EXISTS);
|
||||
}
|
||||
return form;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void saveModelBpmnXml(Model model, String bpmnXml) {
|
||||
if (StrUtil.isEmpty(bpmnXml)) {
|
||||
return;
|
||||
}
|
||||
repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂起 deploymentId 对应的流程定义。 这里一个deploymentId 只关联一个流程定义
|
||||
* @param deploymentId 流程发布Id.
|
||||
*/
|
||||
private void updateProcessDefinitionSuspended(String deploymentId) {
|
||||
if (StrUtil.isEmpty(deploymentId)) {
|
||||
return;
|
||||
}
|
||||
ProcessDefinition oldDefinition = processDefinitionService.getProcessDefinitionByDeploymentId(deploymentId);
|
||||
if (oldDefinition == null) {
|
||||
return;
|
||||
}
|
||||
processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
/**
|
||||
* Flowable流程定义接口
|
||||
*
|
||||
* @author yunlong.li
|
||||
* @author ZJQ
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface BpmProcessDefinitionService {
|
||||
|
||||
/**
|
||||
* 获得流程定义分页
|
||||
*
|
||||
* @param pageReqVO 分页入参
|
||||
* @return 流程定义 Page
|
||||
*/
|
||||
PageResult<BpmProcessDefinitionPageItemRespVO> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得流程定义列表
|
||||
*
|
||||
* @param listReqVO 列表入参
|
||||
* @return 流程定义列表
|
||||
*/
|
||||
List<BpmProcessDefinitionRespVO> getProcessDefinitionList(BpmProcessDefinitionListReqVO listReqVO);
|
||||
|
||||
/**
|
||||
* 创建流程定义
|
||||
*
|
||||
* @param createReqDTO 创建信息
|
||||
* @return 流程编号
|
||||
*/
|
||||
String createProcessDefinition(@Valid BpmProcessDefinitionCreateReqDTO createReqDTO);
|
||||
|
||||
/**
|
||||
* 更新流程定义状态
|
||||
*
|
||||
* @param id 流程定义的编号
|
||||
* @param state 状态
|
||||
*/
|
||||
void updateProcessDefinitionState(String id, Integer state);
|
||||
|
||||
/**
|
||||
* 获得流程定义对应的 BPMN XML
|
||||
*
|
||||
* @param id 流程定义编号
|
||||
* @return BPMN XML
|
||||
*/
|
||||
String getProcessDefinitionBpmnXML(String id);
|
||||
|
||||
/**
|
||||
* 获得需要创建的流程定义,是否和当前激活的流程定义相等
|
||||
*
|
||||
* @param createReqDTO 创建信息
|
||||
* @return 是否相等
|
||||
*/
|
||||
boolean isProcessDefinitionEquals(@Valid BpmProcessDefinitionCreateReqDTO createReqDTO);
|
||||
|
||||
/**
|
||||
* 获得编号对应的 BpmProcessDefinitionExtDO
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 流程定义拓展
|
||||
*/
|
||||
BpmProcessDefinitionExtDO getProcessDefinitionExt(String id);
|
||||
|
||||
/**
|
||||
* 获得编号对应的 ProcessDefinition
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 流程定义
|
||||
*/
|
||||
ProcessDefinition getProcessDefinition(String id);
|
||||
|
||||
/**
|
||||
* 获得编号对应的 ProcessDefinition
|
||||
*
|
||||
* 相比 {@link #getProcessDefinition(String)} 方法,category 的取值是正确
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 流程定义
|
||||
*/
|
||||
ProcessDefinition getProcessDefinition2(String id);
|
||||
|
||||
/**
|
||||
* 获得 deploymentId 对应的 ProcessDefinition
|
||||
*
|
||||
* @param deploymentId 部署编号
|
||||
* @return 流程定义
|
||||
*/
|
||||
ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId);
|
||||
|
||||
/**
|
||||
* 获得 deploymentIds 对应的 ProcessDefinition 数组
|
||||
*
|
||||
* @param deploymentIds 部署编号的数组
|
||||
* @return 流程定义的数组
|
||||
*/
|
||||
List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds);
|
||||
|
||||
/**
|
||||
* 获得流程定义标识对应的激活的流程定义
|
||||
*
|
||||
* @param key 流程定义的标识
|
||||
* @return 流程定义
|
||||
*/
|
||||
ProcessDefinition getActiveProcessDefinition(String key);
|
||||
|
||||
/**
|
||||
* 获得 ids 对应的 Deployment Map
|
||||
*
|
||||
* @param ids 部署编号的数组
|
||||
* @return 流程部署 Map
|
||||
*/
|
||||
default Map<String, Deployment> getDeploymentMap(Set<String> ids) {
|
||||
return CollectionUtils.convertMap(getDeployments(ids), Deployment::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 ids 对应的 Deployment 数组
|
||||
*
|
||||
* @param ids 部署编号的数组
|
||||
* @return 流程部署的数组
|
||||
*/
|
||||
List<Deployment> getDeployments(Set<String> ids);
|
||||
|
||||
/**
|
||||
* 获得 id 对应的 Deployment
|
||||
*
|
||||
* @param id 部署编号
|
||||
* @return 流程部署
|
||||
*/
|
||||
Deployment getDeployment(String id);
|
||||
|
||||
/**
|
||||
* 获得 Bpmn 模型
|
||||
*
|
||||
* @param processDefinitionId 流程定义的编号
|
||||
* @return Bpmn 模型
|
||||
*/
|
||||
BpmnModel getBpmnModel(String processDefinitionId);
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.definition;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
|
||||
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionExtMapper;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.bpmn.converter.BpmnXMLConverter;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.common.engine.impl.db.SuspensionState;
|
||||
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
|
||||
import org.flowable.engine.RepositoryService;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.flowable.engine.repository.ProcessDefinitionQuery;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_KEY_NOT_MATCH;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_NAME_NOT_MATCH;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* 流程定义实现
|
||||
* 主要进行 Flowable {@link ProcessDefinition} 和 {@link Deployment} 的维护
|
||||
*
|
||||
* @author yunlongn
|
||||
* @author ZJQ
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService {
|
||||
|
||||
private static final String BPMN_FILE_SUFFIX = ".bpmn";
|
||||
|
||||
@Resource
|
||||
private RepositoryService repositoryService;
|
||||
|
||||
@Resource
|
||||
private BpmProcessDefinitionExtMapper processDefinitionMapper;
|
||||
|
||||
@Resource
|
||||
private BpmFormService formService;
|
||||
|
||||
@Override
|
||||
public ProcessDefinition getProcessDefinition(String id) {
|
||||
return repositoryService.getProcessDefinition(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessDefinition getProcessDefinition2(String id) {
|
||||
return repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId) {
|
||||
if (StrUtil.isEmpty(deploymentId)) {
|
||||
return null;
|
||||
}
|
||||
return repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds) {
|
||||
if (CollUtil.isEmpty(deploymentIds)) {
|
||||
return emptyList();
|
||||
}
|
||||
return repositoryService.createProcessDefinitionQuery().deploymentIds(deploymentIds).list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProcessDefinition getActiveProcessDefinition(String key) {
|
||||
return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Deployment> getDeployments(Set<String> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return emptyList();
|
||||
}
|
||||
List<Deployment> list = new ArrayList<>(ids.size());
|
||||
for (String id : ids) {
|
||||
addIfNotNull(list, getDeployment(id));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Deployment getDeployment(String id) {
|
||||
if (StrUtil.isEmpty(id)) {
|
||||
return null;
|
||||
}
|
||||
return repositoryService.createDeploymentQuery().deploymentId(id).singleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BpmnModel getBpmnModel(String processDefinitionId) {
|
||||
return repositoryService.getBpmnModel(processDefinitionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createProcessDefinition(@Valid BpmProcessDefinitionCreateReqDTO createReqDTO) {
|
||||
// 创建 Deployment 部署
|
||||
Deployment deploy = repositoryService.createDeployment()
|
||||
.key(createReqDTO.getKey()).name(createReqDTO.getName()).category(createReqDTO.getCategory())
|
||||
.addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
|
||||
.deploy();
|
||||
|
||||
// 设置 ProcessDefinition 的 category 分类
|
||||
ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
|
||||
repositoryService.setProcessDefinitionCategory(definition.getId(), createReqDTO.getCategory());
|
||||
// 注意 1,ProcessDefinition 的 key 和 name 是通过 BPMN 中的 <bpmn2:process /> 的 id 和 name 决定
|
||||
// 注意 2,目前该项目的设计上,需要保证 Model、Deployment、ProcessDefinition 使用相同的 key,保证关联性。
|
||||
// 否则,会导致 ProcessDefinition 的分页无法查询到。
|
||||
if (!Objects.equals(definition.getKey(), createReqDTO.getKey())) {
|
||||
throw exception(PROCESS_DEFINITION_KEY_NOT_MATCH, createReqDTO.getKey(), definition.getKey());
|
||||
}
|
||||
if (!Objects.equals(definition.getName(), createReqDTO.getName())) {
|
||||
throw exception(PROCESS_DEFINITION_NAME_NOT_MATCH, createReqDTO.getName(), definition.getName());
|
||||
}
|
||||
|
||||
// 插入拓展表
|
||||
BpmProcessDefinitionExtDO definitionDO = BpmProcessDefinitionConvert.INSTANCE.convert2(createReqDTO)
|
||||
.setProcessDefinitionId(definition.getId());
|
||||
processDefinitionMapper.insert(definitionDO);
|
||||
return definition.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProcessDefinitionState(String id, Integer state) {
|
||||
// 激活
|
||||
if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
|
||||
repositoryService.activateProcessDefinitionById(id, false, null);
|
||||
return;
|
||||
}
|
||||
// 挂起
|
||||
if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
|
||||
// suspendProcessInstances = false,进行中的任务,不进行挂起。
|
||||
// 原因:只要新的流程不允许发起即可,老流程继续可以执行。
|
||||
repositoryService.suspendProcessDefinitionById(id, false, null);
|
||||
return;
|
||||
}
|
||||
log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessDefinitionBpmnXML(String id) {
|
||||
BpmnModel bpmnModel = repositoryService.getBpmnModel(id);
|
||||
if (bpmnModel == null) {
|
||||
return null;
|
||||
}
|
||||
BpmnXMLConverter converter = new BpmnXMLConverter();
|
||||
return StrUtil.utf8Str(converter.convertToXML(bpmnModel));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProcessDefinitionEquals(@Valid BpmProcessDefinitionCreateReqDTO createReqDTO) {
|
||||
// 校验 name、description 是否更新
|
||||
ProcessDefinition oldProcessDefinition = getActiveProcessDefinition(createReqDTO.getKey());
|
||||
if (oldProcessDefinition == null) {
|
||||
return false;
|
||||
}
|
||||
BpmProcessDefinitionExtDO oldProcessDefinitionExt = getProcessDefinitionExt(oldProcessDefinition.getId());
|
||||
if (!StrUtil.equals(createReqDTO.getName(), oldProcessDefinition.getName())
|
||||
|| !StrUtil.equals(createReqDTO.getDescription(), oldProcessDefinitionExt.getDescription())
|
||||
|| !StrUtil.equals(createReqDTO.getCategory(), oldProcessDefinition.getCategory())) {
|
||||
return false;
|
||||
}
|
||||
// 校验 form 信息是否更新
|
||||
if (!ObjectUtil.equal(createReqDTO.getFormType(), oldProcessDefinitionExt.getFormType())
|
||||
|| !ObjectUtil.equal(createReqDTO.getFormId(), oldProcessDefinitionExt.getFormId())
|
||||
|| !ObjectUtil.equal(createReqDTO.getFormConf(), oldProcessDefinitionExt.getFormConf())
|
||||
|| !ObjectUtil.equal(createReqDTO.getFormFields(), oldProcessDefinitionExt.getFormFields())
|
||||
|| !ObjectUtil.equal(createReqDTO.getFormCustomCreatePath(), oldProcessDefinitionExt.getFormCustomCreatePath())
|
||||
|| !ObjectUtil.equal(createReqDTO.getFormCustomViewPath(), oldProcessDefinitionExt.getFormCustomViewPath())) {
|
||||
return false;
|
||||
}
|
||||
// 校验 BPMN XML 信息
|
||||
BpmnModel newModel = buildBpmnModel(createReqDTO.getBpmnBytes());
|
||||
BpmnModel oldModel = getBpmnModel(oldProcessDefinition.getId());
|
||||
//TODO 貌似 flowable 不修改这个也不同。需要看看。 sourceSystemId 不同
|
||||
if (FlowableUtils.equals(oldModel, newModel)) {
|
||||
return false;
|
||||
}
|
||||
// 最终发现都一致,则返回 true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建对应的 BPMN Model
|
||||
*
|
||||
* @param bpmnBytes 原始的 BPMN XML 字节数组
|
||||
* @return BPMN Model
|
||||
*/
|
||||
private BpmnModel buildBpmnModel(byte[] bpmnBytes) {
|
||||
// 转换成 BpmnModel 对象
|
||||
BpmnXMLConverter converter = new BpmnXMLConverter();
|
||||
return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public BpmProcessDefinitionExtDO getProcessDefinitionExt(String id) {
|
||||
return processDefinitionMapper.selectByProcessDefinitionId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BpmProcessDefinitionRespVO> getProcessDefinitionList(BpmProcessDefinitionListReqVO listReqVO) {
|
||||
// 拼接查询条件
|
||||
ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
|
||||
if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), listReqVO.getSuspensionState())) {
|
||||
definitionQuery.suspended();
|
||||
} else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), listReqVO.getSuspensionState())) {
|
||||
definitionQuery.active();
|
||||
}
|
||||
// 执行查询
|
||||
List<ProcessDefinition> processDefinitions = definitionQuery.list();
|
||||
if (CollUtil.isEmpty(processDefinitions)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 获得 BpmProcessDefinitionDO Map
|
||||
List<BpmProcessDefinitionExtDO> processDefinitionDOs = processDefinitionMapper.selectListByProcessDefinitionIds(
|
||||
convertList(processDefinitions, ProcessDefinition::getId));
|
||||
Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap = convertMap(processDefinitionDOs,
|
||||
BpmProcessDefinitionExtDO::getProcessDefinitionId);
|
||||
// 执行查询,并返回
|
||||
return BpmProcessDefinitionConvert.INSTANCE.convertList3(processDefinitions, processDefinitionDOMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<BpmProcessDefinitionPageItemRespVO> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) {
|
||||
ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
|
||||
if (StrUtil.isNotBlank(pageVO.getKey())) {
|
||||
definitionQuery.processDefinitionKey(pageVO.getKey());
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
List<ProcessDefinition> processDefinitions = definitionQuery.orderByProcessDefinitionVersion().desc()
|
||||
.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
|
||||
|
||||
if (CollUtil.isEmpty(processDefinitions)) {
|
||||
return new PageResult<>(emptyList(), definitionQuery.count());
|
||||
}
|
||||
// 获得 Deployment Map
|
||||
Set<String> deploymentIds = new HashSet<>();
|
||||
processDefinitions.forEach(definition -> addIfNotNull(deploymentIds, definition.getDeploymentId()));
|
||||
Map<String, Deployment> deploymentMap = getDeploymentMap(deploymentIds);
|
||||
|
||||
// 获得 BpmProcessDefinitionDO Map
|
||||
List<BpmProcessDefinitionExtDO> processDefinitionDOs = processDefinitionMapper.selectListByProcessDefinitionIds(
|
||||
convertList(processDefinitions, ProcessDefinition::getId));
|
||||
Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap = convertMap(processDefinitionDOs,
|
||||
BpmProcessDefinitionExtDO::getProcessDefinitionId);
|
||||
|
||||
// 获得 Form Map
|
||||
Set<Long> formIds = convertSet(processDefinitionDOs, BpmProcessDefinitionExtDO::getFormId);
|
||||
Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
|
||||
|
||||
// 拼接结果
|
||||
long definitionCount = definitionQuery.count();
|
||||
return new PageResult<>(BpmProcessDefinitionConvert.INSTANCE.convertList(processDefinitions, deploymentMap,
|
||||
processDefinitionDOMap, formMap), definitionCount);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.definition;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BPM 任务分配规则 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface BpmTaskAssignRuleService {
|
||||
|
||||
/**
|
||||
* 获得流程定义的任务分配规则数组
|
||||
*
|
||||
* @param processDefinitionId 流程定义的编号
|
||||
* @param taskDefinitionKey 流程任务定义的 Key。允许空
|
||||
* @return 任务规则数组
|
||||
*/
|
||||
List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId,
|
||||
@Nullable String taskDefinitionKey);
|
||||
|
||||
/**
|
||||
* 获得流程模型的任务规则数组
|
||||
*
|
||||
* @param modelId 流程模型的编号
|
||||
* @return 任务规则数组
|
||||
*/
|
||||
List<BpmTaskAssignRuleDO> getTaskAssignRuleListByModelId(String modelId);
|
||||
|
||||
/**
|
||||
* 获得流程定义的任务分配规则数组
|
||||
*
|
||||
* @param modelId 流程模型的编号
|
||||
* @param processDefinitionId 流程定义的编号
|
||||
* @return 任务规则数组
|
||||
*/
|
||||
List<BpmTaskAssignRuleRespVO> getTaskAssignRuleList(String modelId, String processDefinitionId);
|
||||
|
||||
/**
|
||||
* 创建任务分配规则
|
||||
*
|
||||
* @param reqVO 创建信息
|
||||
* @return 规则编号
|
||||
*/
|
||||
Long createTaskAssignRule(@Valid BpmTaskAssignRuleCreateReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 更新任务分配规则
|
||||
*
|
||||
* @param reqVO 创建信息
|
||||
*/
|
||||
void updateTaskAssignRule(@Valid BpmTaskAssignRuleUpdateReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 判断指定流程模型和流程定义的分配规则是否相等
|
||||
*
|
||||
* @param modelId 流程模型编号
|
||||
* @param processDefinitionId 流程定义编号
|
||||
* @return 是否相等
|
||||
*/
|
||||
boolean isTaskAssignRulesEquals(String modelId, String processDefinitionId);
|
||||
|
||||
/**
|
||||
* 将流程流程模型的任务分配规则,复制一份给流程定义
|
||||
* 目的:每次流程模型部署时,都会生成一个新的流程定义,此时考虑到每次部署的流程不可变性,所以需要复制一份给该流程定义
|
||||
*
|
||||
* @param fromModelId 流程模型编号
|
||||
* @param toProcessDefinitionId 流程定义编号
|
||||
*/
|
||||
void copyTaskAssignRules(String fromModelId, String toProcessDefinitionId);
|
||||
|
||||
/**
|
||||
* 校验流程模型的任务分配规则全部都配置了
|
||||
* 目的:如果有规则未配置,会导致流程任务找不到负责人,进而流程无法进行下去!
|
||||
*
|
||||
* @param id 流程模型编号
|
||||
*/
|
||||
void checkTaskAssignRuleAllConfig(String id);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.definition;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.convert.definition.BpmTaskAssignRuleConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper;
|
||||
import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.dept.PostApi;
|
||||
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
|
||||
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.bpmn.model.UserTask;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* BPM 任务分配规则 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService{
|
||||
|
||||
@Resource
|
||||
private BpmTaskAssignRuleMapper taskRuleMapper;
|
||||
@Resource
|
||||
@Lazy // 解决循环依赖
|
||||
private BpmModelService modelService;
|
||||
@Resource
|
||||
@Lazy // 解决循环依赖
|
||||
private BpmProcessDefinitionService processDefinitionService;
|
||||
@Resource
|
||||
private BpmUserGroupService userGroupService;
|
||||
@Resource
|
||||
private RoleApi roleApi;
|
||||
@Resource
|
||||
private DeptApi deptApi;
|
||||
@Resource
|
||||
private PostApi postApi;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private DictDataApi dictDataApi;
|
||||
|
||||
@Override
|
||||
public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId, String taskDefinitionKey) {
|
||||
return taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, taskDefinitionKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByModelId(String modelId) {
|
||||
return taskRuleMapper.selectListByModelId(modelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BpmTaskAssignRuleRespVO> getTaskAssignRuleList(String modelId, String processDefinitionId) {
|
||||
// 获得规则
|
||||
List<BpmTaskAssignRuleDO> rules = Collections.emptyList();
|
||||
BpmnModel model = null;
|
||||
if (StrUtil.isNotEmpty(modelId)) {
|
||||
rules = getTaskAssignRuleListByModelId(modelId);
|
||||
model = modelService.getBpmnModel(modelId);
|
||||
} else if (StrUtil.isNotEmpty(processDefinitionId)) {
|
||||
rules = getTaskAssignRuleListByProcessDefinitionId(processDefinitionId, null);
|
||||
model = processDefinitionService.getBpmnModel(processDefinitionId);
|
||||
}
|
||||
if (model == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 获得用户任务,只有用户任务才可以设置分配规则
|
||||
List<UserTask> userTasks = FlowableUtils.getBpmnModelElements(model, UserTask.class);
|
||||
if (CollUtil.isEmpty(userTasks)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 转换数据
|
||||
return BpmTaskAssignRuleConvert.INSTANCE.convertList(userTasks, rules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createTaskAssignRule(@Valid BpmTaskAssignRuleCreateReqVO reqVO) {
|
||||
// 校验参数
|
||||
validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
|
||||
// 校验是否已经配置
|
||||
BpmTaskAssignRuleDO existRule = taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(
|
||||
reqVO.getModelId(), reqVO.getTaskDefinitionKey());
|
||||
if (existRule != null) {
|
||||
throw exception(TASK_ASSIGN_RULE_EXISTS, reqVO.getModelId(), reqVO.getTaskDefinitionKey());
|
||||
}
|
||||
|
||||
// 存储
|
||||
BpmTaskAssignRuleDO rule = BpmTaskAssignRuleConvert.INSTANCE.convert(reqVO)
|
||||
.setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL); // 只有流程模型,才允许新建
|
||||
taskRuleMapper.insert(rule);
|
||||
return rule.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTaskAssignRule(@Valid BpmTaskAssignRuleUpdateReqVO reqVO) {
|
||||
// 校验参数
|
||||
validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
|
||||
// 校验是否存在
|
||||
BpmTaskAssignRuleDO existRule = taskRuleMapper.selectById(reqVO.getId());
|
||||
if (existRule == null) {
|
||||
throw exception(TASK_ASSIGN_RULE_NOT_EXISTS);
|
||||
}
|
||||
// 只允许修改流程模型的规则
|
||||
if (!Objects.equals(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL, existRule.getProcessDefinitionId())) {
|
||||
throw exception(TASK_UPDATE_FAIL_NOT_MODEL);
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
taskRuleMapper.updateById(BpmTaskAssignRuleConvert.INSTANCE.convert(reqVO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTaskAssignRulesEquals(String modelId, String processDefinitionId) {
|
||||
// 调用 VO 接口的原因是,过滤掉流程模型不需要的规则,保持和 copyTaskAssignRules 方法的一致性
|
||||
List<BpmTaskAssignRuleRespVO> modelRules = getTaskAssignRuleList(modelId, null);
|
||||
List<BpmTaskAssignRuleRespVO> processInstanceRules = getTaskAssignRuleList(null, processDefinitionId);
|
||||
if (modelRules.size() != processInstanceRules.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 遍历,匹配对应的规则
|
||||
Map<String, BpmTaskAssignRuleRespVO> processInstanceRuleMap = CollectionUtils.convertMap(processInstanceRules,
|
||||
BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
|
||||
for (BpmTaskAssignRuleRespVO modelRule : modelRules) {
|
||||
BpmTaskAssignRuleRespVO processInstanceRule = processInstanceRuleMap.get(modelRule.getTaskDefinitionKey());
|
||||
if (processInstanceRule == null) {
|
||||
return false;
|
||||
}
|
||||
if (!ObjectUtil.equals(modelRule.getType(), processInstanceRule.getType())
|
||||
|| !ObjectUtil.equal(modelRule.getOptions(), processInstanceRule.getOptions())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTaskAssignRules(String fromModelId, String toProcessDefinitionId) {
|
||||
List<BpmTaskAssignRuleRespVO> rules = getTaskAssignRuleList(fromModelId, null);
|
||||
if (CollUtil.isEmpty(rules)) {
|
||||
return;
|
||||
}
|
||||
// 开始复制
|
||||
List<BpmTaskAssignRuleDO> newRules = BpmTaskAssignRuleConvert.INSTANCE.convertList2(rules);
|
||||
newRules.forEach(rule -> rule.setProcessDefinitionId(toProcessDefinitionId).setId(null)
|
||||
.setCreateTime(null).setUpdateTime(null));
|
||||
taskRuleMapper.insertBatch(newRules);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkTaskAssignRuleAllConfig(String id) {
|
||||
// 一个用户任务都没配置,所以无需配置规则
|
||||
List<BpmTaskAssignRuleRespVO> taskAssignRules = getTaskAssignRuleList(id, null);
|
||||
if (CollUtil.isEmpty(taskAssignRules)) {
|
||||
return;
|
||||
}
|
||||
// 校验未配置规则的任务
|
||||
taskAssignRules.forEach(rule -> {
|
||||
if (CollUtil.isEmpty(rule.getOptions())) {
|
||||
throw exception(MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG, rule.getTaskDefinitionName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void validTaskAssignRuleOptions(Integer type, Set<Long> options) {
|
||||
if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
|
||||
roleApi.validRoles(options);
|
||||
} else if (ObjectUtils.equalsAny(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(),
|
||||
BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
|
||||
deptApi.validDepts(options);
|
||||
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
|
||||
postApi.validPosts(options);
|
||||
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER.getType())) {
|
||||
adminUserApi.validUsers(options);
|
||||
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_GROUP.getType())) {
|
||||
userGroupService.validUserGroups(options);
|
||||
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {
|
||||
dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
|
||||
CollectionUtils.convertSet(options, String::valueOf));
|
||||
} else {
|
||||
throw new IllegalArgumentException(StrUtil.format("未知的规则类型({})", type));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.task;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BPM 活动实例 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface BpmActivityService {
|
||||
|
||||
/**
|
||||
* 获得指定流程实例的活动实例列表
|
||||
*
|
||||
* @param processInstanceId 流程实例的编号
|
||||
* @return 活动实例列表
|
||||
*/
|
||||
List<BpmActivityRespVO> getActivityListByProcessInstanceId(String processInstanceId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.task;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.history.HistoricActivityInstance;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.image.ProcessDiagramGenerator;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* BPM 活动实例 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@Validated
|
||||
public class BpmActivityServiceImpl implements BpmActivityService {
|
||||
|
||||
@Resource
|
||||
private HistoryService historyService;
|
||||
|
||||
@Override
|
||||
public List<BpmActivityRespVO> getActivityListByProcessInstanceId(String processInstanceId) {
|
||||
List<HistoricActivityInstance> activityList = historyService.createHistoricActivityInstanceQuery()
|
||||
.processInstanceId(processInstanceId).list();
|
||||
return BpmActivityConvert.INSTANCE.convertList(activityList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
|
||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
|
||||
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 流程实例 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface BpmProcessInstanceService {
|
||||
|
||||
/**
|
||||
* 获得流程实例
|
||||
*
|
||||
* @param id 流程实例的编号
|
||||
* @return 流程实例
|
||||
*/
|
||||
ProcessInstance getProcessInstance(String id);
|
||||
|
||||
/**
|
||||
* 获得流程实例列表
|
||||
*
|
||||
* @param ids 流程实例的编号集合
|
||||
* @return 流程实例列表
|
||||
*/
|
||||
List<ProcessInstance> getProcessInstances(Set<String> ids);
|
||||
|
||||
/**
|
||||
* 获得流程实例 Map
|
||||
*
|
||||
* @param ids 流程实例的编号集合
|
||||
* @return 流程实例列表 Map
|
||||
*/
|
||||
default Map<String, ProcessInstance> getProcessInstanceMap(Set<String> ids) {
|
||||
return CollectionUtils.convertMap(getProcessInstances(ids), ProcessInstance::getProcessInstanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得流程实例的分页
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param pageReqVO 分页请求
|
||||
* @return 流程实例的分页
|
||||
*/
|
||||
PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId,
|
||||
@Valid BpmProcessInstanceMyPageReqVO pageReqVO);
|
||||
/**
|
||||
* 创建流程实例(提供给前端)
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param createReqVO 创建信息
|
||||
* @return 实例的编号
|
||||
*/
|
||||
String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 创建流程实例(提供给内部)
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param createReqDTO 创建信息
|
||||
* @return 实例的编号
|
||||
*/
|
||||
String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO);
|
||||
|
||||
/**
|
||||
* 获得流程实例 VO 信息
|
||||
*
|
||||
* @param id 流程实例的编号
|
||||
* @return 流程实例
|
||||
*/
|
||||
BpmProcessInstanceRespVO getProcessInstanceVO(String id);
|
||||
|
||||
/**
|
||||
* 取消流程实例
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param cancelReqVO 取消信息
|
||||
*/
|
||||
void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO);
|
||||
|
||||
/**
|
||||
* 获得历史的流程实例
|
||||
*
|
||||
* @param id 流程实例的编号
|
||||
* @return 历史的流程实例
|
||||
*/
|
||||
HistoricProcessInstance getHistoricProcessInstance(String id);
|
||||
|
||||
/**
|
||||
* 获得历史的流程实例列表
|
||||
*
|
||||
* @param ids 流程实例的编号集合
|
||||
* @return 历史的流程实例列表
|
||||
*/
|
||||
List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids);
|
||||
|
||||
/**
|
||||
* 获得历史的流程实例 Map
|
||||
*
|
||||
* @param ids 流程实例的编号集合
|
||||
* @return 历史的流程实例列表 Map
|
||||
*/
|
||||
default Map<String, HistoricProcessInstance> getHistoricProcessInstanceMap(Set<String> ids) {
|
||||
return CollectionUtils.convertMap(getHistoricProcessInstances(ids), HistoricProcessInstance::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 ProcessInstance 拓展记录
|
||||
*
|
||||
* @param instance 流程任务
|
||||
*/
|
||||
void createProcessInstanceExt(ProcessInstance instance);
|
||||
|
||||
/**
|
||||
* 更新 ProcessInstance 拓展记录为取消
|
||||
*
|
||||
* @param event 流程取消事件
|
||||
*/
|
||||
void updateProcessInstanceExtCancel(FlowableCancelledEvent event);
|
||||
|
||||
/**
|
||||
* 更新 ProcessInstance 拓展记录为完成
|
||||
*
|
||||
* @param instance 流程任务
|
||||
*/
|
||||
void updateProcessInstanceExtComplete(ProcessInstance instance);
|
||||
|
||||
/**
|
||||
* 更新 ProcessInstance 拓展记录为不通过
|
||||
*
|
||||
* @param id 流程编号
|
||||
* @param comment 理由。例如说,审批不通过时,需要传递该值
|
||||
*/
|
||||
void updateProcessInstanceExtReject(String id, String comment);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.task;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
|
||||
import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
|
||||
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher;
|
||||
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
|
||||
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF;
|
||||
|
||||
/**
|
||||
* 流程实例 Service 实现类
|
||||
*
|
||||
* ProcessDefinition & ProcessInstance & Execution & Task 的关系:
|
||||
* 1. https://blog.csdn.net/bobozai86/article/details/105210414
|
||||
*
|
||||
* HistoricProcessInstance & ProcessInstance 的关系:
|
||||
* 1.https://my.oschina.net/843294669/blog/719024
|
||||
* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {
|
||||
|
||||
@Resource
|
||||
private RuntimeService runtimeService;
|
||||
@Resource
|
||||
private BpmProcessInstanceExtMapper processInstanceExtMapper;
|
||||
@Resource
|
||||
@Lazy // 解决循环依赖
|
||||
private BpmTaskService taskService;
|
||||
@Resource
|
||||
private BpmProcessDefinitionService processDefinitionService;
|
||||
@Resource
|
||||
private HistoryService historyService;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private DeptApi deptApi;
|
||||
@Resource
|
||||
private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher;
|
||||
@Resource
|
||||
private BpmMessageService messageService;
|
||||
@Override
|
||||
public ProcessInstance getProcessInstance(String id) {
|
||||
return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProcessInstance> getProcessInstances(Set<String> ids) {
|
||||
return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId,
|
||||
BpmProcessInstanceMyPageReqVO pageReqVO) {
|
||||
// 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页
|
||||
PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return new PageResult<>(pageResult.getTotal());
|
||||
}
|
||||
|
||||
// 获得流程 Task Map
|
||||
List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId);
|
||||
Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds);
|
||||
// 转换返回
|
||||
return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {
|
||||
// 获得流程定义
|
||||
ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
|
||||
// 发起流程
|
||||
return createProcessInstance0(userId, definition, createReqVO.getVariables(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {
|
||||
// 获得流程定义
|
||||
ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
|
||||
// 发起流程
|
||||
return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BpmProcessInstanceRespVO getProcessInstanceVO(String id) {
|
||||
// 获得流程实例
|
||||
HistoricProcessInstance processInstance = getHistoricProcessInstance(id);
|
||||
if (processInstance == null) {
|
||||
return null;
|
||||
}
|
||||
BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id);
|
||||
Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id);
|
||||
|
||||
// 获得流程定义
|
||||
ProcessDefinition processDefinition = processDefinitionService
|
||||
.getProcessDefinition(processInstance.getProcessDefinitionId());
|
||||
Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId());
|
||||
BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt(
|
||||
processInstance.getProcessDefinitionId());
|
||||
Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id);
|
||||
String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId());
|
||||
|
||||
// 获得 User
|
||||
AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId()));
|
||||
DeptRespDTO dept = null;
|
||||
if (startUser != null) {
|
||||
dept = deptApi.getDept(startUser.getDeptId());
|
||||
}
|
||||
|
||||
// 拼接结果
|
||||
return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt,
|
||||
processDefinition, processDefinitionExt, bpmnXml, startUser, dept);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
|
||||
// 校验流程实例存在
|
||||
ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
|
||||
if (instance == null) {
|
||||
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
|
||||
}
|
||||
// 只能取消自己的
|
||||
if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
|
||||
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
|
||||
}
|
||||
|
||||
// 通过删除流程实例,实现流程实例的取消,
|
||||
// 删除流程实例,正则执行任务ACT_RU_TASK. 任务会被删除。通过历史表查询
|
||||
deleteProcessInstance(cancelReqVO.getId(),
|
||||
BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得历史的流程实例
|
||||
*
|
||||
* @param id 流程实例的编号
|
||||
* @return 历史的流程实例
|
||||
*/
|
||||
@Override
|
||||
public HistoricProcessInstance getHistoricProcessInstance(String id) {
|
||||
return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
|
||||
return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createProcessInstanceExt(ProcessInstance instance) {
|
||||
// 获得流程定义
|
||||
ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId());
|
||||
// 插入 BpmProcessInstanceExtDO 对象
|
||||
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
|
||||
.setProcessInstanceId(instance.getId())
|
||||
.setProcessDefinitionId(definition.getId())
|
||||
.setName(instance.getProcessDefinitionName())
|
||||
.setStartUserId(Long.valueOf(instance.getStartUserId()))
|
||||
.setCategory(definition.getCategory())
|
||||
.setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus())
|
||||
.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
|
||||
|
||||
processInstanceExtMapper.insert(instanceExtDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) {
|
||||
// 判断是否为 Reject 不通过。如果是,则不进行更新
|
||||
if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String)event.getCause())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 需要主动查询,因为 instance 只有 id 属性
|
||||
// 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
|
||||
HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
|
||||
// 更新拓展表
|
||||
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
|
||||
.setProcessInstanceId(event.getProcessInstanceId())
|
||||
.setEndTime(new Date()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置
|
||||
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
|
||||
.setResult(BpmProcessInstanceResultEnum.CANCEL.getResult());
|
||||
|
||||
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
|
||||
|
||||
// 发送流程实例的状态事件
|
||||
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
|
||||
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProcessInstanceExtComplete(ProcessInstance instance) {
|
||||
// 需要主动查询,因为 instance 只有 id 属性
|
||||
// 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
|
||||
HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
|
||||
// 更新拓展表
|
||||
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
|
||||
.setProcessInstanceId(instance.getProcessInstanceId())
|
||||
.setEndTime(new Date()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置
|
||||
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
|
||||
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过
|
||||
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
|
||||
|
||||
// 发送流程被通过的消息
|
||||
messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance));
|
||||
|
||||
// 发送流程实例的状态事件
|
||||
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
|
||||
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateProcessInstanceExtReject(String id, String comment) {
|
||||
// 需要主动查询,因为 instance 只有 id 属性
|
||||
ProcessInstance processInstance = getProcessInstance(id);
|
||||
// 删除流程实例,以实现驳回任务时,取消整个审批流程
|
||||
deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(comment)));
|
||||
|
||||
// 更新 status + result
|
||||
// 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法,
|
||||
// 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的
|
||||
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id)
|
||||
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
|
||||
.setResult(BpmProcessInstanceResultEnum.REJECT.getResult());
|
||||
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
|
||||
|
||||
// 发送流程被不通过的消息
|
||||
messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, comment));
|
||||
|
||||
// 发送流程实例的状态事件
|
||||
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
|
||||
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
|
||||
}
|
||||
|
||||
private void deleteProcessInstance(String id, String reason) {
|
||||
runtimeService.deleteProcessInstance(id, reason);
|
||||
}
|
||||
|
||||
private String createProcessInstance0(Long userId, ProcessDefinition definition,
|
||||
Map<String, Object> variables, String businessKey) {
|
||||
// 校验流程定义
|
||||
if (definition == null) {
|
||||
throw exception(PROCESS_DEFINITION_NOT_EXISTS);
|
||||
}
|
||||
if (definition.isSuspended()) {
|
||||
throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
|
||||
}
|
||||
|
||||
// 创建流程实例
|
||||
ProcessInstance instance = runtimeService.startProcessInstanceById(definition.getId(), businessKey, variables);
|
||||
// 设置流程名字
|
||||
runtimeService.setProcessInstanceName(instance.getId(), definition.getName());
|
||||
|
||||
// 补全流程实例的拓展表
|
||||
//TODO startProcessInstance流程里面修改了 BpmProcessInstanceExt,没有提交,和下面的更新 锁持有冲突了,异步更新这个表
|
||||
// processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()).setFormVariables(variables));
|
||||
BpmProcessInstanceExtDO bpmProcessInstanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()).setFormVariables(variables);
|
||||
asyncUpdateProcesInstance(bpmProcessInstanceExtDO);
|
||||
return instance.getId();
|
||||
}
|
||||
|
||||
@Async
|
||||
public void asyncUpdateProcesInstance(BpmProcessInstanceExtDO bpmProcessInstanceExtDO){
|
||||
log.info("asyncUpdateProcesInstance ,cause MySQL Dead Lock");
|
||||
processInstanceExtMapper.updateByProcessInstanceId(bpmProcessInstanceExtDO);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.task;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
|
||||
import org.flowable.task.api.Task;
|
||||
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 流程任务实例 Service 接口
|
||||
*
|
||||
* @author jason
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface BpmTaskService {
|
||||
/**
|
||||
* 获得待办的流程任务分页
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param pageReqVO 分页请求
|
||||
* @return 流程任务分页
|
||||
*/
|
||||
PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageReqVO);
|
||||
/**
|
||||
* 获得已办的流程任务分页
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param pageReqVO 分页请求
|
||||
* @return 流程任务分页
|
||||
*/
|
||||
PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得流程任务 Map
|
||||
*
|
||||
* @param processInstanceIds 流程实例的编号数组
|
||||
* @return 流程任务 Map
|
||||
*/
|
||||
default Map<String, List<Task>> getTaskMapByProcessInstanceIds(List<String> processInstanceIds) {
|
||||
return CollectionUtils.convertMultiMap(getTasksByProcessInstanceIds(processInstanceIds),
|
||||
Task::getProcessInstanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得流程任务列表
|
||||
*
|
||||
* @param processInstanceIds 流程实例的编号数组
|
||||
* @return 流程任务列表
|
||||
*/
|
||||
List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds);
|
||||
|
||||
/**
|
||||
* 获得指令流程实例的流程任务列表,包括所有状态的
|
||||
*
|
||||
* @param processInstanceId 流程实例的编号
|
||||
* @return 流程任务列表
|
||||
*/
|
||||
List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId);
|
||||
|
||||
/**
|
||||
* 通过任务
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param reqVO 通过请求
|
||||
*/
|
||||
void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 不通过任务
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param reqVO 不通过请求
|
||||
*/
|
||||
void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO);
|
||||
/**
|
||||
* 回退任务
|
||||
*
|
||||
* @param taskId 任务编号
|
||||
*/
|
||||
void backTask(String taskId,String destinationTaskDefKey);
|
||||
|
||||
/**
|
||||
* 将流程任务分配给指定用户
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param reqVO 分配请求
|
||||
*/
|
||||
void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 将流程任务分配给指定用户
|
||||
*
|
||||
* @param id 流程任务编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void updateTaskAssignee(String id, Long userId);
|
||||
|
||||
/**
|
||||
* 创建 Task 拓展记录
|
||||
*
|
||||
* @param task 任务实体
|
||||
*/
|
||||
void createTaskExt(Task task);
|
||||
|
||||
/**
|
||||
* 更新 Task 拓展记录为完成
|
||||
*
|
||||
* @param task 任务实体
|
||||
*/
|
||||
void updateTaskExtComplete(Task task);
|
||||
|
||||
/**
|
||||
* 更新 Task 拓展记录,并发送通知
|
||||
*
|
||||
* @param task 任务实体
|
||||
*/
|
||||
void updateTaskExtAssign(Task task);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.task;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
|
||||
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
|
||||
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.flowable.task.api.TaskQuery;
|
||||
import org.flowable.task.api.history.HistoricTaskInstance;
|
||||
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 流程任务实例 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @author jason
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class BpmTaskServiceImpl implements BpmTaskService{
|
||||
|
||||
@Resource
|
||||
private TaskService taskService;
|
||||
@Resource
|
||||
private RuntimeService runtimeService;
|
||||
@Resource
|
||||
private HistoryService historyService;
|
||||
|
||||
@Resource
|
||||
private BpmProcessInstanceService processInstanceService;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private DeptApi deptApi;
|
||||
@Resource
|
||||
private BpmTaskExtMapper taskExtMapper;
|
||||
@Resource
|
||||
private BpmMessageService messageService;
|
||||
|
||||
@Override
|
||||
public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
|
||||
// 查询待办任务
|
||||
TaskQuery taskQuery = taskService.createTaskQuery()
|
||||
.taskAssignee(String.valueOf(userId)) // 分配给自己
|
||||
.orderByTaskCreateTime().desc(); // 创建时间倒序
|
||||
if (StrUtil.isNotBlank(pageVO.getName())) {
|
||||
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
|
||||
}
|
||||
if (pageVO.getBeginCreateTime() != null) {
|
||||
taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
|
||||
}
|
||||
if (pageVO.getEndCreateTime() != null) {
|
||||
taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
|
||||
}
|
||||
// 执行查询
|
||||
List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
|
||||
if (CollUtil.isEmpty(tasks)) {
|
||||
return PageResult.empty(taskQuery.count());
|
||||
}
|
||||
|
||||
// 获得 ProcessInstance Map
|
||||
Map<String, ProcessInstance> processInstanceMap = processInstanceService.getProcessInstanceMap(
|
||||
convertSet(tasks, Task::getProcessInstanceId));
|
||||
// 获得 User Map
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
|
||||
convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
|
||||
// 拼接结果
|
||||
return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap),
|
||||
taskQuery.count());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) {
|
||||
// 查询已办任务
|
||||
HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
|
||||
.finished() // 已完成
|
||||
.taskAssignee(String.valueOf(userId)) // 分配给自己
|
||||
.orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
|
||||
if (StrUtil.isNotBlank(pageVO.getName())) {
|
||||
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
|
||||
}
|
||||
if (pageVO.getBeginCreateTime() != null) {
|
||||
taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
|
||||
}
|
||||
if (pageVO.getEndCreateTime() != null) {
|
||||
taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
|
||||
}
|
||||
// 执行查询
|
||||
List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
|
||||
if (CollUtil.isEmpty(tasks)) {
|
||||
return PageResult.empty(taskQuery.count());
|
||||
}
|
||||
|
||||
// 获得 TaskExtDO Map
|
||||
List<BpmTaskExtDO> bpmTaskExtDOs = taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
|
||||
Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
|
||||
// 获得 ProcessInstance Map
|
||||
Map<String, HistoricProcessInstance> historicProcessInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
|
||||
convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
|
||||
// 获得 User Map
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
|
||||
convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
|
||||
// 拼接结果
|
||||
return new PageResult<>(BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
|
||||
taskQuery.count());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds) {
|
||||
if (CollUtil.isEmpty(processInstanceIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return taskService.createTaskQuery().processInstanceIdIn(processInstanceIds).list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId) {
|
||||
// 获得任务列表
|
||||
List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
|
||||
.processInstanceId(processInstanceId)
|
||||
.orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
|
||||
.list();
|
||||
if (CollUtil.isEmpty(tasks)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 获得 TaskExtDO Map
|
||||
List<BpmTaskExtDO> bpmTaskExtDOs = taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
|
||||
Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
|
||||
// 获得 ProcessInstance Map
|
||||
HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
|
||||
// 获得 User Map
|
||||
Set<Long> userIds = convertSet(tasks, task -> NumberUtils.parseLong(task.getAssignee()));
|
||||
userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
|
||||
// 获得 Dept Map
|
||||
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
|
||||
|
||||
// 拼接数据
|
||||
return BpmTaskConvert.INSTANCE.convertList3(tasks, bpmTaskExtDOMap, processInstance, userMap, deptMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) {
|
||||
// 校验任务存在
|
||||
Task task = checkTask(userId, reqVO.getId());
|
||||
// 校验流程实例存在
|
||||
ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
|
||||
if (instance == null) {
|
||||
throw exception(PROCESS_INSTANCE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 完成任务,审批通过
|
||||
taskService.complete(task.getId(), instance.getProcessVariables());
|
||||
// 更新任务拓展表为通过
|
||||
taskExtMapper.updateByTaskId(new BpmTaskExtDO().setTaskId(task.getId())
|
||||
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()).setComment(reqVO.getComment()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) {
|
||||
Task task = checkTask(userId, reqVO.getId());
|
||||
// 校验流程实例存在
|
||||
ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
|
||||
if (instance == null) {
|
||||
throw exception(PROCESS_INSTANCE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 更新流程实例为不通过
|
||||
processInstanceService.updateProcessInstanceExtReject(instance.getProcessInstanceId(), reqVO.getComment());
|
||||
|
||||
// 更新任务拓展表为不通过
|
||||
taskExtMapper.updateByTaskId(new BpmTaskExtDO().setTaskId(task.getId())
|
||||
.setResult(BpmProcessInstanceResultEnum.REJECT.getResult()).setComment(reqVO.getComment()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void backTask(String taskId,String destinationTaskDefKey) {
|
||||
Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult();
|
||||
|
||||
runtimeService.createChangeActivityStateBuilder()
|
||||
.processInstanceId(currentTask.getProcessInstanceId())
|
||||
.moveActivityIdTo(currentTask.getTaskDefinitionKey(), destinationTaskDefKey)
|
||||
.changeState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO) {
|
||||
// 校验任务存在
|
||||
Task task = checkTask(userId, reqVO.getId());
|
||||
// 更新负责人
|
||||
updateTaskAssignee(task.getId(), reqVO.getAssigneeUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTaskAssignee(String id, Long userId) {
|
||||
taskService.setAssignee(id, String.valueOf(userId));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void createTaskExt(Task task) {
|
||||
BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert2TaskExt(task)
|
||||
.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
|
||||
taskExtMapper.insert(taskExtDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTaskExtComplete(Task task) {
|
||||
BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert2TaskExt(task)
|
||||
.setEndTime(new Date());
|
||||
taskExtMapper.updateByTaskId(taskExtDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTaskExtAssign(Task task) {
|
||||
BpmTaskExtDO taskExtDO = new BpmTaskExtDO()
|
||||
.setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()))
|
||||
.setTaskId(task.getId());
|
||||
taskExtMapper.updateByTaskId(taskExtDO);
|
||||
// 发送通知。在事务提交时,批量执行操作,所以直接查询会无法查询到 ProcessInstance,所以这里是通过监听事务的提交来实现。
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
|
||||
AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
|
||||
messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验任务是否存在, 并且是否是分配给自己的任务
|
||||
* @param userId 用户 id
|
||||
* @param taskId task id
|
||||
*/
|
||||
private Task checkTask(Long userId, String taskId) {
|
||||
Task task = getTask(taskId);
|
||||
if (task == null) {
|
||||
throw exception(TASK_COMPLETE_FAIL_NOT_EXISTS);
|
||||
}
|
||||
if (!Objects.equals(userId, NumberUtils.parseLong(task.getAssignee()))) {
|
||||
throw exception(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private Task getTask(String id) {
|
||||
return taskService.createTaskQuery().taskId(id).singleResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.iocoder.yudao.module.bpm;
|
||||
|
||||
import org.flowable.engine.*;
|
||||
import org.flowable.engine.impl.ProcessEngineImpl;
|
||||
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
|
||||
import org.flowable.engine.impl.test.TestHelper;
|
||||
import org.flowable.engine.test.FlowableRule;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
|
||||
/**
|
||||
* 抽象测试基类
|
||||
*
|
||||
* @author henryyan cuicui
|
||||
*/
|
||||
public abstract class AbstractOATest {
|
||||
|
||||
protected String configurationResource = "flowable.cfg.xml";
|
||||
|
||||
/**
|
||||
* 专门用于测试套件
|
||||
*/
|
||||
@Rule
|
||||
public FlowableRule activitiRule = new FlowableRule("flowable.cfg.xml");
|
||||
|
||||
protected ProcessEngineConfigurationImpl processEngineConfiguration;
|
||||
|
||||
protected ProcessEngine processEngine;
|
||||
protected RepositoryService repositoryService;
|
||||
protected RuntimeService runtimeService;
|
||||
protected TaskService taskService;
|
||||
protected HistoryService historyService;
|
||||
protected IdentityService identityService;
|
||||
protected ManagementService managementService;
|
||||
protected FormService formService;
|
||||
|
||||
/**
|
||||
* 开始测试
|
||||
*/
|
||||
@BeforeClass
|
||||
public static void setUpForClass() throws Exception {
|
||||
System.out.println("++++++++ 开始测试 ++++++++");
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束测试
|
||||
*/
|
||||
@AfterClass
|
||||
public static void testOverForClass() throws Exception {
|
||||
System.out.println("-------- 结束测试 --------");
|
||||
}
|
||||
|
||||
protected void initializeProcessEngine() {
|
||||
processEngine = TestHelper.getProcessEngine(configurationResource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化变量
|
||||
*/
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
if (processEngine == null) {
|
||||
initializeProcessEngine();
|
||||
}
|
||||
|
||||
processEngineConfiguration = ((ProcessEngineImpl) processEngine).getProcessEngineConfiguration();
|
||||
repositoryService = processEngine.getRepositoryService();
|
||||
runtimeService = processEngine.getRuntimeService();
|
||||
taskService = processEngine.getTaskService();
|
||||
historyService = processEngine.getHistoryService();
|
||||
identityService = processEngine.getIdentityService();
|
||||
managementService = processEngine.getManagementService();
|
||||
formService = processEngine.getFormService();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package cn.iocoder.yudao.module.bpm;
|
||||
|
||||
import org.flowable.engine.history.HistoricDetail;
|
||||
import org.flowable.engine.history.HistoricFormProperty;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.history.HistoricVariableUpdate;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.engine.test.Deployment;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* 条件测试
|
||||
* 1 某个节点-支付额度需要大于70%
|
||||
* 2 支付条件完成后,开始倒计时15天,要完成流程处理
|
||||
* @author cuicui
|
||||
*/
|
||||
public class ConditionTest extends AbstractOATest {
|
||||
|
||||
@Test
|
||||
@Deployment(resources = {"chapter6/leave-timeLimit-money/leave-formkey-ext.bpmn", "chapter6/leave-timeLimit-money/leave-start.form",
|
||||
"chapter6/leave-timeLimit-money/approve-deptLeader.form", "chapter6/leave-timeLimit-money/approve-hr.form", "chapter6/leave-timeLimit-money/report-back.form",
|
||||
"chapter6/leave-timeLimit-money/modify-apply.form"})
|
||||
public void allPass() throws Exception {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Map<String, String> variables = new HashMap<String, String>();
|
||||
Calendar ca = Calendar.getInstance();
|
||||
String startDate = sdf.format(ca.getTime());
|
||||
ca.add(Calendar.DAY_OF_MONTH, 2); // 当前日期加2天
|
||||
String endDate = sdf.format(ca.getTime());
|
||||
|
||||
// 启动流程
|
||||
variables.put("startDate", startDate);
|
||||
variables.put("endDate", endDate);
|
||||
variables.put("reason", "公休");
|
||||
|
||||
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().singleResult();
|
||||
|
||||
// 读取启动表单
|
||||
Object renderedStartForm = formService.getRenderedStartForm(processDefinition.getId());
|
||||
assertNotNull(renderedStartForm);
|
||||
|
||||
// 启动流程
|
||||
// 设置当前用户
|
||||
String currentUserId = "henryyan";
|
||||
identityService.setAuthenticatedUserId(currentUserId);
|
||||
ProcessInstance processInstance = formService.submitStartFormData(processDefinition.getId(), variables);
|
||||
assertNotNull(processInstance);
|
||||
|
||||
// 部门领导审批通过
|
||||
Task deptLeaderTask = taskService.createTaskQuery().taskCandidateGroup("deptLeader").singleResult();
|
||||
assertNotNull(formService.getRenderedTaskForm(deptLeaderTask.getId()));
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("deptLeaderApproved", "true");
|
||||
formService.submitTaskFormData(deptLeaderTask.getId(), variables);
|
||||
|
||||
// 人事审批通过
|
||||
Task hrTask = taskService.createTaskQuery().taskCandidateGroup("hr").singleResult();
|
||||
assertNotNull(formService.getRenderedTaskForm(hrTask.getId()));
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("hrApproved", "true");
|
||||
//手动设置支付金额
|
||||
variables.put("amountMoney", "19999");
|
||||
formService.submitTaskFormData(hrTask.getId(), variables);
|
||||
//判断支付金额是否>1万元
|
||||
// 财务打款通过
|
||||
Task caiwuTask = taskService.createTaskQuery().taskCandidateGroup("caiwu").singleResult();
|
||||
printTask(caiwuTask);
|
||||
taskService.complete(caiwuTask.getId());
|
||||
//判断倒计时15天
|
||||
Task chuNaTask = taskService.createTaskQuery().taskCandidateGroup("chuNa").singleResult();
|
||||
printTask(chuNaTask);
|
||||
taskService.complete(chuNaTask.getId());
|
||||
|
||||
// 销假(根据申请人的用户ID读取)
|
||||
// Task reportBackTask = taskService.createTaskQuery().taskAssignee(currentUserId).singleResult();
|
||||
// assertNotNull(formService.getRenderedTaskForm(reportBackTask.getId()));
|
||||
// variables = new HashMap<String, String>();
|
||||
// variables.put("reportBackDate", sdf.format(ca.getTime()));
|
||||
// formService.submitTaskFormData(reportBackTask.getId(), variables);
|
||||
|
||||
// 验证流程是否已经结束
|
||||
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().finished().singleResult();
|
||||
assertNotNull(historicProcessInstance);
|
||||
|
||||
// 读取历史变量
|
||||
Map<String, Object> historyVariables = packageVariables(processInstance);
|
||||
|
||||
// 验证执行结果
|
||||
assertEquals("ok", historyVariables.get("result"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询过期任务
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"chapter6/leave-timeLimit-money/leave-formkey-ext.bpmn", "chapter6/leave-timeLimit-money/leave-start.form",
|
||||
"chapter6/leave-timeLimit-money/approve-deptLeader.form", "chapter6/leave-timeLimit-money/approve-hr.form", "chapter6/leave-timeLimit-money/report-back.form",
|
||||
"chapter6/leave-timeLimit-money/modify-apply.form"})
|
||||
public void dueDate() throws Exception {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Map<String, String> variables = new HashMap<String, String>();
|
||||
Calendar ca = Calendar.getInstance();
|
||||
String startDate = sdf.format(ca.getTime());
|
||||
ca.add(Calendar.DAY_OF_MONTH, 2); // 当前日期加2天
|
||||
String endDate = sdf.format(ca.getTime());
|
||||
|
||||
// 启动流程
|
||||
variables.put("startDate", startDate);
|
||||
variables.put("endDate", endDate);
|
||||
variables.put("reason", "公休");
|
||||
|
||||
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().singleResult();
|
||||
|
||||
// 读取启动表单
|
||||
Object renderedStartForm = formService.getRenderedStartForm(processDefinition.getId());
|
||||
assertNotNull(renderedStartForm);
|
||||
|
||||
// 启动流程
|
||||
// 设置当前用户
|
||||
String currentUserId = "henryyan";
|
||||
identityService.setAuthenticatedUserId(currentUserId);
|
||||
ProcessInstance processInstance = formService.submitStartFormData(processDefinition.getId(), variables);
|
||||
assertNotNull(processInstance);
|
||||
|
||||
// 部门领导审批通过
|
||||
Task deptLeaderTask = taskService.createTaskQuery().taskCandidateGroup("deptLeader").singleResult();
|
||||
assertNotNull(formService.getRenderedTaskForm(deptLeaderTask.getId()));
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("deptLeaderApproved", "true");
|
||||
formService.submitTaskFormData(deptLeaderTask.getId(), variables);
|
||||
|
||||
// 人事审批通过
|
||||
Task hrTask = taskService.createTaskQuery().taskCandidateGroup("hr").singleResult();
|
||||
assertNotNull(formService.getRenderedTaskForm(hrTask.getId()));
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("hrApproved", "true");
|
||||
//手动设置支付金额
|
||||
variables.put("amountMoney", "19999");
|
||||
formService.submitTaskFormData(hrTask.getId(), variables);
|
||||
//判断支付金额是否>1万元
|
||||
// 财务打款通过
|
||||
Task caiwuTask = taskService.createTaskQuery().taskCandidateGroup("caiwu").singleResult();
|
||||
printTask(caiwuTask);
|
||||
|
||||
//设置5天前就过期了
|
||||
DateTime dateTime = DateTime.now();
|
||||
DateTime minusDays = dateTime.minusDays(5);
|
||||
taskService.setDueDate(caiwuTask.getId(),minusDays.toDate());
|
||||
//查询今天以前的过期任务
|
||||
List<Task> listTask = taskService.createTaskQuery().taskDueBefore(new Date()).list();
|
||||
for (Task task : listTask) {
|
||||
printTask(task);
|
||||
}
|
||||
|
||||
// 销假(根据申请人的用户ID读取)
|
||||
// Task reportBackTask = taskService.createTaskQuery().taskAssignee(currentUserId).singleResult();
|
||||
// assertNotNull(formService.getRenderedTaskForm(reportBackTask.getId()));
|
||||
// variables = new HashMap<String, String>();
|
||||
// variables.put("reportBackDate", sdf.format(ca.getTime()));
|
||||
// formService.submitTaskFormData(reportBackTask.getId(), variables);
|
||||
|
||||
// 验证流程是否已经结束
|
||||
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().finished().singleResult();
|
||||
assertNotNull(historicProcessInstance);
|
||||
|
||||
// 读取历史变量
|
||||
Map<String, Object> historyVariables = packageVariables(processInstance);
|
||||
|
||||
// 验证执行结果
|
||||
assertEquals("ok", historyVariables.get("result"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意流程的跳转
|
||||
*/
|
||||
@Test
|
||||
public void taskJump(){
|
||||
// 当前任务
|
||||
String taskId="ddd";
|
||||
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
|
||||
String assignee = "下一个自由跳转人";
|
||||
taskService.setAssignee(taskId,assignee);
|
||||
// 自由跳转
|
||||
String taskDefKey="目标-任务名称";
|
||||
//moveActivityIdTo的两个参数,源任务key,目标任务key
|
||||
runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveActivityIdTo(task.getTaskDefinitionKey(), taskDefKey).changeState();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取历史变量并封装到Map中
|
||||
*/
|
||||
private Map<String, Object> packageVariables(ProcessInstance processInstance) {
|
||||
Map<String, Object> historyVariables = new HashMap<String, Object>();
|
||||
List<HistoricDetail> list = historyService.createHistoricDetailQuery().processInstanceId(processInstance.getId()).list();
|
||||
for (HistoricDetail historicDetail : list) {
|
||||
if (historicDetail instanceof HistoricFormProperty) {
|
||||
// 表单中的字段
|
||||
HistoricFormProperty field = (HistoricFormProperty) historicDetail;
|
||||
historyVariables.put(field.getPropertyId(), field.getPropertyValue());
|
||||
System.out.println("form field: taskId=" + field.getTaskId() + ", " + field.getPropertyId() + " = " + field.getPropertyValue());
|
||||
} else if (historicDetail instanceof HistoricVariableUpdate) {
|
||||
HistoricVariableUpdate variable = (HistoricVariableUpdate) historicDetail;
|
||||
historyVariables.put(variable.getVariableName(), variable.getValue());
|
||||
System.out.println("variable: " + variable.getVariableName() + " = " + variable.getValue());
|
||||
}
|
||||
}
|
||||
return historyVariables;
|
||||
}
|
||||
private void printTask(Task task){
|
||||
System.out.println(task.getName()+" : " + task.getDueDate());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package cn.iocoder.yudao.module.bpm;
|
||||
|
||||
import org.flowable.engine.history.HistoricDetail;
|
||||
import org.flowable.engine.history.HistoricFormProperty;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.history.HistoricVariableUpdate;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.engine.test.Deployment;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author henryyan
|
||||
*/
|
||||
public class LeaveFormKeyTest extends AbstractOATest {
|
||||
|
||||
@Test
|
||||
@Deployment(resources = {"chapter6/leave-formkey/leave-formkey.bpmn", "chapter6/leave-formkey/leave-start.form",
|
||||
"chapter6/leave-formkey/approve-deptLeader.form", "chapter6/leave-formkey/approve-hr.form", "chapter6/leave-formkey/report-back.form",
|
||||
"chapter6/leave-formkey/modify-apply.form"})
|
||||
public void allPass() throws Exception {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Map<String, String> variables = new HashMap<String, String>();
|
||||
Calendar ca = Calendar.getInstance();
|
||||
String startDate = sdf.format(ca.getTime());
|
||||
ca.add(Calendar.DAY_OF_MONTH, 2); // 当前日期加2天
|
||||
String endDate = sdf.format(ca.getTime());
|
||||
|
||||
// 启动流程
|
||||
variables.put("startDate", startDate);
|
||||
variables.put("endDate", endDate);
|
||||
variables.put("reason", "公休");
|
||||
|
||||
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().singleResult();
|
||||
|
||||
// 读取启动表单
|
||||
Object renderedStartForm = formService.getRenderedStartForm(processDefinition.getId());
|
||||
assertNotNull(renderedStartForm);
|
||||
|
||||
// 启动流程
|
||||
// 设置当前用户
|
||||
String currentUserId = "henryyan";
|
||||
identityService.setAuthenticatedUserId(currentUserId);
|
||||
ProcessInstance processInstance = formService.submitStartFormData(processDefinition.getId(), variables);
|
||||
assertNotNull(processInstance);
|
||||
|
||||
// 部门领导审批通过
|
||||
Task deptLeaderTask = taskService.createTaskQuery().taskCandidateGroup("deptLeader").singleResult();
|
||||
assertNotNull(formService.getRenderedTaskForm(deptLeaderTask.getId()));
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("deptLeaderApproved", "true");
|
||||
formService.submitTaskFormData(deptLeaderTask.getId(), variables);
|
||||
|
||||
// 人事审批通过
|
||||
Task hrTask = taskService.createTaskQuery().taskCandidateGroup("hr").singleResult();
|
||||
assertNotNull(formService.getRenderedTaskForm(hrTask.getId()));
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("hrApproved", "true");
|
||||
formService.submitTaskFormData(hrTask.getId(), variables);
|
||||
|
||||
// 销假(根据申请人的用户ID读取)
|
||||
Task reportBackTask = taskService.createTaskQuery().taskAssignee(currentUserId).singleResult();
|
||||
assertNotNull(formService.getRenderedTaskForm(reportBackTask.getId()));
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("reportBackDate", sdf.format(ca.getTime()));
|
||||
formService.submitTaskFormData(reportBackTask.getId(), variables);
|
||||
|
||||
// 验证流程是否已经结束
|
||||
HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().finished().singleResult();
|
||||
assertNotNull(historicProcessInstance);
|
||||
|
||||
// 读取历史变量
|
||||
Map<String, Object> historyVariables = packageVariables(processInstance);
|
||||
|
||||
// 验证执行结果
|
||||
assertEquals("ok", historyVariables.get("result"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意流程的跳转
|
||||
*/
|
||||
@Test
|
||||
public void taskJump(){
|
||||
// 当前任务
|
||||
String taskId="ddd";
|
||||
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
|
||||
String assignee = "下一个自由跳转人";
|
||||
taskService.setAssignee(taskId,assignee);
|
||||
// 自由跳转
|
||||
String taskDefKey="目标-任务名称";
|
||||
//moveActivityIdTo的两个参数,源任务key,目标任务key
|
||||
runtimeService.createChangeActivityStateBuilder().processInstanceId(task.getProcessInstanceId()).moveActivityIdTo(task.getTaskDefinitionKey(), taskDefKey).changeState();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 领导驳回后申请人取消申请
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = "chapter6/dynamic-form/leave.bpmn")
|
||||
public void cancelApply() throws Exception {
|
||||
|
||||
// 设置当前用户
|
||||
String currentUserId = "henryyan";
|
||||
identityService.setAuthenticatedUserId(currentUserId);
|
||||
|
||||
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("leave").singleResult();
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Map<String, String> variables = new HashMap<String, String>();
|
||||
Calendar ca = Calendar.getInstance();
|
||||
String startDate = sdf.format(ca.getTime());
|
||||
ca.add(Calendar.DAY_OF_MONTH, 2);
|
||||
String endDate = sdf.format(ca.getTime());
|
||||
|
||||
// 启动流程
|
||||
variables.put("startDate", startDate);
|
||||
variables.put("endDate", endDate);
|
||||
variables.put("reason", "公休");
|
||||
ProcessInstance processInstance = formService.submitStartFormData(processDefinition.getId(), variables);
|
||||
assertNotNull(processInstance);
|
||||
|
||||
// 部门领导审批通过
|
||||
Task deptLeaderTask = taskService.createTaskQuery().taskCandidateGroup("deptLeader").singleResult();
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("deptLeaderApproved", "false");
|
||||
formService.submitTaskFormData(deptLeaderTask.getId(), variables);
|
||||
|
||||
// 调整申请
|
||||
Task modifyApply = taskService.createTaskQuery().taskAssignee(currentUserId).singleResult();
|
||||
variables = new HashMap<String, String>();
|
||||
variables.put("reApply", "false");
|
||||
variables.put("startDate", startDate);
|
||||
variables.put("endDate", endDate);
|
||||
variables.put("reason", "公休");
|
||||
formService.submitTaskFormData(modifyApply.getId(), variables);
|
||||
|
||||
// 读取历史变量
|
||||
Map<String, Object> historyVariables = packageVariables(processInstance);
|
||||
|
||||
// 验证执行结果
|
||||
assertEquals("canceled", historyVariables.get("result"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取历史变量并封装到Map中
|
||||
*/
|
||||
private Map<String, Object> packageVariables(ProcessInstance processInstance) {
|
||||
Map<String, Object> historyVariables = new HashMap<String, Object>();
|
||||
List<HistoricDetail> list = historyService.createHistoricDetailQuery().processInstanceId(processInstance.getId()).list();
|
||||
for (HistoricDetail historicDetail : list) {
|
||||
if (historicDetail instanceof HistoricFormProperty) {
|
||||
// 表单中的字段
|
||||
HistoricFormProperty field = (HistoricFormProperty) historicDetail;
|
||||
historyVariables.put(field.getPropertyId(), field.getPropertyValue());
|
||||
System.out.println("form field: taskId=" + field.getTaskId() + ", " + field.getPropertyId() + " = " + field.getPropertyValue());
|
||||
} else if (historicDetail instanceof HistoricVariableUpdate) {
|
||||
HistoricVariableUpdate variable = (HistoricVariableUpdate) historicDetail;
|
||||
historyVariables.put(variable.getVariableName(), variable.getValue());
|
||||
System.out.println("variable: " + variable.getVariableName() + " = " + variable.getValue());
|
||||
}
|
||||
}
|
||||
return historyVariables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package cn.iocoder.yudao.module.bpm;
|
||||
|
||||
import org.flowable.engine.history.HistoricDetail;
|
||||
import org.flowable.engine.history.HistoricFormProperty;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.history.HistoricVariableUpdate;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.engine.test.Deployment;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author henryyan
|
||||
* testMultiInstanceForUserTask 会签
|
||||
* cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmUserTaskActivityBehavior#handleAssignments(org.flowable.task.service.TaskService, java.lang.String, java.lang.String, java.util.List, java.util.List, org.flowable.task.service.impl.persistence.entity.TaskEntity, org.flowable.common.engine.impl.el.ExpressionManager, org.flowable.engine.delegate.DelegateExecution, org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl)
|
||||
*
|
||||
* 发生了死锁
|
||||
* cn.iocoder.yudao.module.bpm.controller.admin.task.BpmProcessInstanceController#createProcessInstance(cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCreateReqVO)
|
||||
* 执行了两次,任务分配到了同一个人
|
||||
*/
|
||||
public class MultiInstancesTest extends AbstractOATest {
|
||||
|
||||
/**
|
||||
* Java Service多实例(是否顺序结果一样)
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/testMultiInstanceFixedNumbers.bpmn"})
|
||||
public void testParallel() throws Exception {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
long loop = 3;
|
||||
variables.put("loop", loop);
|
||||
variables.put("counter", 0); // 计数器
|
||||
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMultiInstanceFixedNumbers", variables);
|
||||
Object variable = runtimeService.getVariable(processInstance.getId(), "counter");
|
||||
assertEquals(loop, variable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户任务多实例--顺序
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.sequential.bpmn"})
|
||||
public void testForUserSequence() throws Exception {
|
||||
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask");
|
||||
long count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count();
|
||||
assertEquals(1, count);
|
||||
|
||||
Task task = taskService.createTaskQuery().singleResult();
|
||||
taskService.complete(task.getId());
|
||||
count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count();
|
||||
assertEquals(1, count);
|
||||
|
||||
task = taskService.createTaskQuery().singleResult();
|
||||
taskService.complete(task.getId());
|
||||
count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count();
|
||||
assertEquals(1, count);
|
||||
|
||||
task = taskService.createTaskQuery().singleResult();
|
||||
taskService.complete(task.getId());
|
||||
count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count();
|
||||
assertEquals(0, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户任务多实例--并行
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.nosequential.bpmn"})
|
||||
public void testForUserNoSequential() throws Exception {
|
||||
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask");
|
||||
long count = taskService.createTaskQuery().processInstanceId(processInstance.getId()).count();
|
||||
assertEquals(3, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户任务多实例,通过用户数量决定实例个数--并行
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.users.nosequential.bpmn"})
|
||||
public void testForUserCreateByUsersNoSequential() throws Exception {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
List<String> users = Arrays.asList("user1", "user2", "user3");
|
||||
variables.put("users", users);
|
||||
runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask", variables);
|
||||
for (String userId : users) {
|
||||
assertEquals(1, taskService.createTaskQuery().taskAssignee(userId).count());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户任务多实例,通过用户数量决定实例个数--顺序
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.bpmn"})
|
||||
public void testForUserCreateByUsersSequential() throws Exception {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
List<String> users = Arrays.asList("user1", "user2", "user3");
|
||||
variables.put("users", users);
|
||||
runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask", variables);
|
||||
for (String userId : users) {
|
||||
Task task = taskService.createTaskQuery().taskAssignee(userId).singleResult();
|
||||
taskService.complete(task.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户任务多实例,按照任务完成的百分比比率决定是否提前结束流程
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.users.sequential.with.complete.conditon.bpmn"})
|
||||
public void testForUserCreateByUsersSequentialWithCompleteCondition() throws Exception {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
List<String> users = Arrays.asList("user1", "user2", "user3");
|
||||
variables.put("users", users);
|
||||
variables.put("rate", 0.6d);
|
||||
runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask", variables);
|
||||
|
||||
Task task = taskService.createTaskQuery().taskAssignee("user1").singleResult();
|
||||
taskService.complete(task.getId());
|
||||
|
||||
task = taskService.createTaskQuery().taskAssignee("user2").singleResult();
|
||||
taskService.complete(task.getId());
|
||||
|
||||
long count = historyService.createHistoricProcessInstanceQuery().finished().count();
|
||||
assertEquals(1, count);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户任务多实例,按照任务完成的百分比比率决定是否提前结束流程
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/testMultiInstanceForUserTask.exception.bpmn"})
|
||||
public void testForUserCreateByUsersException() throws Exception {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
List<String> users = Arrays.asList("user1", "user2", "user3");
|
||||
variables.put("users", users);
|
||||
runtimeService.startProcessInstanceByKey("testMultiInstanceForUserTask", variables);
|
||||
|
||||
Task task = taskService.createTaskQuery().taskAssignee("user1").singleResult();
|
||||
taskService.complete(task.getId());
|
||||
|
||||
task = taskService.createTaskQuery().taskAssignee("user2").singleResult();
|
||||
taskService.complete(task.getId());
|
||||
|
||||
task = taskService.createTaskQuery().taskAssignee("user3").singleResult();
|
||||
taskService.complete(task.getId());
|
||||
|
||||
List<Task> list = taskService.createTaskQuery().list();
|
||||
for (Task task2 : list) {
|
||||
System.out.println("============" + task2.getName());
|
||||
}
|
||||
|
||||
}
|
||||
/////////////////////////////////////////////////
|
||||
/**
|
||||
* 全部通过
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/leave-countersign.bpmn"})
|
||||
public void testAllApproved() throws Exception {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
List<String> users = Arrays.asList("groupLeader", "deptLeader", "hr");
|
||||
variables.put("users", users);
|
||||
identityService.setAuthenticatedUserId("henryyan");
|
||||
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave-countersign", variables);
|
||||
for (String user : users) {
|
||||
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskAssignee(user).singleResult();
|
||||
Map<String, Object> taskVariables = new HashMap<String, Object>();
|
||||
taskVariables.put("approved", "true");
|
||||
taskService.complete(task.getId(), taskVariables);
|
||||
}
|
||||
|
||||
Task task = taskService.createTaskQuery().taskAssignee("henryyan").singleResult();
|
||||
assertNotNull(task);
|
||||
assertEquals("销假", task.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 部分通过
|
||||
*/
|
||||
@Test
|
||||
@Deployment(resources = {"diagrams/chapter9/leave-countersign.bpmn"})
|
||||
public void testNotAllApproved() throws Exception {
|
||||
Map<String, Object> variables = new HashMap<String, Object>();
|
||||
List<String> users = Arrays.asList("groupLeader", "deptLeader", "hr");
|
||||
variables.put("users", users);
|
||||
identityService.setAuthenticatedUserId("henryyan");
|
||||
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave-countersign", variables);
|
||||
for (String user : users) {
|
||||
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskAssignee(user).singleResult();
|
||||
Map<String, Object> taskVariables = new HashMap<String, Object>();
|
||||
taskVariables.put("approved", "false");
|
||||
taskService.complete(task.getId(), taskVariables);
|
||||
}
|
||||
|
||||
Task task = taskService.createTaskQuery().taskAssignee("henryyan").singleResult();
|
||||
assertNotNull(task);
|
||||
assertEquals("调整申请", task.getName());
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,198 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.kafeitu.me/activiti/leave">
|
||||
<process id="leave" name="请假流程-动态表单" isExecutable="true">
|
||||
<documentation>请假流程演示-动态表单</documentation>
|
||||
<startEvent id="startevent1" name="Start" activiti:initiator="applyUserId">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="validScript" type="javascript" default="alert('表单已经加载完毕');"></activiti:formProperty>
|
||||
</extensionElements>
|
||||
</startEvent>
|
||||
<userTask id="deptLeaderAudit" name="部门领导审批" activiti:candidateGroups="deptLeader">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="deptLeaderApproved" name="审批意见" type="enum" required="true">
|
||||
<activiti:value id="true" name="同意"></activiti:value>
|
||||
<activiti:value id="false" name="拒绝"></activiti:value>
|
||||
</activiti:formProperty>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
<exclusiveGateway id="exclusivegateway5" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<userTask id="modifyApply" name="调整申请" activiti:assignee="${applyUserId}">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="reApply" name="重新申请" type="enum" required="true">
|
||||
<activiti:value id="true" name="重新申请"></activiti:value>
|
||||
<activiti:value id="false" name="取消申请"></activiti:value>
|
||||
</activiti:formProperty>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
<userTask id="hrAudit" name="人事审批" activiti:candidateGroups="hr">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="hrApproved" name="审批意见" type="enum" required="true">
|
||||
<activiti:value id="true" name="同意"></activiti:value>
|
||||
<activiti:value id="false" name="拒绝"></activiti:value>
|
||||
</activiti:formProperty>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
<exclusiveGateway id="exclusivegateway6" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<userTask id="reportBack" name="销假" activiti:assignee="${applyUserId}">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="reportBackDate" name="销假日期" type="date" default="${endDate}" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<exclusiveGateway id="exclusivegateway7" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<sequenceFlow id="flow2" sourceRef="startevent1" targetRef="deptLeaderAudit"></sequenceFlow>
|
||||
<sequenceFlow id="flow3" sourceRef="deptLeaderAudit" targetRef="exclusivegateway5"></sequenceFlow>
|
||||
<sequenceFlow id="flow4" name="拒绝" sourceRef="exclusivegateway5" targetRef="modifyApply">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderApproved == 'false'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow5" name="同意" sourceRef="exclusivegateway5" targetRef="hrAudit">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderApproved == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow6" sourceRef="hrAudit" targetRef="exclusivegateway6"></sequenceFlow>
|
||||
<sequenceFlow id="flow7" name="同意" sourceRef="exclusivegateway6" targetRef="reportBack">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrApproved == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow8" name="销假" sourceRef="reportBack" targetRef="endevent1">
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="take" expression="${execution.setVariable('result', 'ok')}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow9" name="拒绝" sourceRef="exclusivegateway6" targetRef="modifyApply">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrApproved == 'false'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow10" name="重新申请" sourceRef="exclusivegateway7" targetRef="deptLeaderAudit">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow11" sourceRef="modifyApply" targetRef="exclusivegateway7"></sequenceFlow>
|
||||
<sequenceFlow id="flow12" name="结束流程" sourceRef="exclusivegateway7" targetRef="endevent1">
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="take" expression="${execution.setVariable('result', 'canceled')}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'false'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<textAnnotation id="textannotation1" textFormat="text/plain">
|
||||
<text>请求被驳回后员工可以选择继续申请,或者取消本次申请</text>
|
||||
</textAnnotation>
|
||||
<association id="association1" sourceRef="modifyApply" targetRef="textannotation1"></association>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_leave">
|
||||
<bpmndi:BPMNPlane bpmnElement="leave" id="BPMNPlane_leave">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="10.0" y="30.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="deptLeaderAudit" id="BPMNShape_deptLeaderAudit">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="90.0" y="20.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway5" id="BPMNShape_exclusivegateway5">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="250.0" y="27.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="modifyApply" id="BPMNShape_modifyApply">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="218.0" y="108.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="hrAudit" id="BPMNShape_hrAudit">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="358.0" y="20.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway6" id="BPMNShape_exclusivegateway6">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="495.0" y="27.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="reportBack" id="BPMNShape_reportBack">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="590.0" y="20.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="625.0" y="223.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway7" id="BPMNShape_exclusivegateway7">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="250.0" y="220.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="textannotation1" id="BPMNShape_textannotation1">
|
||||
<omgdc:Bounds height="57.0" width="120.0" x="361.0" y="174.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="45.0" y="47.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="90.0" y="47.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
|
||||
<omgdi:waypoint x="195.0" y="47.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="250.0" y="47.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
|
||||
<omgdi:waypoint x="270.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="270.0" y="108.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="22.0" x="280.0" y="67.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
|
||||
<omgdi:waypoint x="290.0" y="47.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="358.0" y="47.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="22.0" x="300.0" y="30.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
|
||||
<omgdi:waypoint x="463.0" y="47.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="495.0" y="47.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
|
||||
<omgdi:waypoint x="535.0" y="47.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="590.0" y="47.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="22.0" x="544.0" y="30.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
|
||||
<omgdi:waypoint x="642.0" y="75.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="642.0" y="223.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="22.0" x="652.0" y="75.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
|
||||
<omgdi:waypoint x="515.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="514.0" y="135.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="323.0" y="135.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="22.0" x="525.0" y="67.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow10" id="BPMNEdge_flow10">
|
||||
<omgdi:waypoint x="250.0" y="240.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="142.0" y="239.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="142.0" y="75.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="44.0" x="152.0" y="221.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow11" id="BPMNEdge_flow11">
|
||||
<omgdi:waypoint x="270.0" y="163.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="270.0" y="220.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow12" id="BPMNEdge_flow12">
|
||||
<omgdi:waypoint x="290.0" y="240.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="625.0" y="240.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="44.0" x="429.0" y="247.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="association1" id="BPMNEdge_association1">
|
||||
<omgdi:waypoint x="323.0" y="135.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="421.0" y="174.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,31 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">申请人:</label>
|
||||
<div class="controls">${applyUserId}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" value="${startDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" readonly>${reason}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="deptLeaderApproved">审批意见:</label>
|
||||
<div class="controls">
|
||||
<select name="deptLeaderApproved" id="deptLeaderApproved">
|
||||
<option value="true">同意</option>
|
||||
<option value="false">拒绝</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">申请人:</label>
|
||||
<div class="controls">${applyUserId}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" value="${startDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" readonly>${reason}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="hrApproved">审批意见:</label>
|
||||
<div class="controls">
|
||||
<select name="hrApproved" id="hrApproved">
|
||||
<option value="true">同意</option>
|
||||
<option value="false">拒绝</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.kafeitu.me/activiti">
|
||||
<process id="leave-formkey" name="请假流程-外置表单" isExecutable="true">
|
||||
<documentation>《Activiti实战》第6章的例子--外置表单</documentation>
|
||||
<startEvent id="startevent1" name="Start" activiti:initiator="applyUserId" activiti:formKey="chapter6/leave-formkey/leave-start.form"></startEvent>
|
||||
<userTask id="deptLeaderVerify" name="部门经理审批" activiti:candidateGroups="deptLeader" activiti:formKey="chapter6/leave-formkey/approve-deptLeader.form"></userTask>
|
||||
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<userTask id="hrVerify" name="人事经理审批" activiti:candidateGroups="hr" activiti:formKey="chapter6/leave-formkey/approve-hr.form"></userTask>
|
||||
<exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<userTask id="reportBack" name="销假" activiti:assignee="${applyUserId}" activiti:formKey="chapter6/leave-formkey/report-back.form"></userTask>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<userTask id="modifyApply" name="调整申请内容" activiti:assignee="${applyUserId}" activiti:formKey="chapter6/leave-formkey/modify-apply.form"></userTask>
|
||||
<exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="deptLeaderVerify"></sequenceFlow>
|
||||
<sequenceFlow id="flow2" sourceRef="deptLeaderVerify" targetRef="exclusivegateway1"></sequenceFlow>
|
||||
<sequenceFlow id="flow3" name="同意" sourceRef="exclusivegateway1" targetRef="hrVerify">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderApproved == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow4" sourceRef="hrVerify" targetRef="exclusivegateway2"></sequenceFlow>
|
||||
<sequenceFlow id="flow5" name="同意" sourceRef="exclusivegateway2" targetRef="reportBack">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrApproved == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow6" sourceRef="reportBack" targetRef="endevent1">
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="take" expression="${execution.setVariable('result', 'ok')}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow7" name="不同意" sourceRef="exclusivegateway2" targetRef="modifyApply">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrApproved == 'false'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow8" name="不同意" sourceRef="exclusivegateway1" targetRef="modifyApply">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderApproved == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow9" sourceRef="modifyApply" targetRef="exclusivegateway3"></sequenceFlow>
|
||||
<sequenceFlow id="flow10" name="调整后继续申请" sourceRef="exclusivegateway3" targetRef="deptLeaderVerify">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow11" name="取消申请,并设置取消标志" sourceRef="exclusivegateway3" targetRef="endevent1">
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="take" expression="${execution.setVariable('result', 'canceled')}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'false'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<textAnnotation id="textannotation1" textFormat="text/plain">
|
||||
<text>请求被驳回后员工可以选择继续申请,或者取消本次申请</text>
|
||||
</textAnnotation>
|
||||
<association id="association1" sourceRef="modifyApply" targetRef="textannotation1"></association>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_leave-formkey">
|
||||
<bpmndi:BPMNPlane bpmnElement="leave-formkey" id="BPMNPlane_leave-formkey">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="10.0" y="50.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="deptLeaderVerify" id="BPMNShape_deptLeaderVerify">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="90.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="230.0" y="47.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="hrVerify" id="BPMNShape_hrVerify">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="310.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="470.0" y="47.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="reportBack" id="BPMNShape_reportBack">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="580.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="615.0" y="213.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="modifyApply" id="BPMNShape_modifyApply">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="198.0" y="120.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway3" id="BPMNShape_exclusivegateway3">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="230.0" y="210.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="textannotation1" id="BPMNShape_textannotation1">
|
||||
<omgdc:Bounds height="57.0" width="112.0" x="340.0" y="168.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="45.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="90.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="195.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="230.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
|
||||
<omgdi:waypoint x="270.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="310.0" y="67.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="22.0" x="269.0" y="50.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
|
||||
<omgdi:waypoint x="415.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="470.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
|
||||
<omgdi:waypoint x="510.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="580.0" y="67.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="22.0" x="529.0" y="50.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
|
||||
<omgdi:waypoint x="632.0" y="95.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="632.0" y="213.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
|
||||
<omgdi:waypoint x="490.0" y="87.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="490.0" y="147.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="303.0" y="147.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="33.0" x="438.0" y="119.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
|
||||
<omgdi:waypoint x="250.0" y="87.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="250.0" y="120.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="33.0" x="260.0" y="87.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
|
||||
<omgdi:waypoint x="250.0" y="175.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="250.0" y="210.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow10" id="BPMNEdge_flow10">
|
||||
<omgdi:waypoint x="230.0" y="230.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="142.0" y="230.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="142.0" y="95.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="77.0" x="159.0" y="210.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow11" id="BPMNEdge_flow11">
|
||||
<omgdi:waypoint x="270.0" y="230.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="615.0" y="230.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="33.0" width="100.0" x="58.0" y="-37.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="association1" id="BPMNEdge_association1">
|
||||
<omgdi:waypoint x="303.0" y="147.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="396.0" y="168.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" value="${startDate}" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" value="${endDate}" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" required>${reason}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">是否继续申请:</label>
|
||||
<div class="controls">
|
||||
<select id="reApply" name="reApply">
|
||||
<option value='true'>重新申请</option>
|
||||
<option value='false'>结束流程</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">申请人:</label>
|
||||
<div class="controls">${applyUserId}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" value="${startDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" readonly>${reason}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reportBackDate">销假日期:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="reportBackDate" name="reportBackDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">申请人:</label>
|
||||
<div class="controls">${applyUserId}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" value="${startDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" readonly>${reason}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="deptLeaderApproved">审批意见:</label>
|
||||
<div class="controls">
|
||||
<select name="deptLeaderApproved" id="deptLeaderApproved">
|
||||
<option value="true">同意</option>
|
||||
<option value="false">拒绝</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">申请人:</label>
|
||||
<div class="controls">${applyUserId}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" value="${startDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" readonly>${reason}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="hrApproved">审批意见:</label>
|
||||
<div class="controls">
|
||||
<select name="hrApproved" id="hrApproved">
|
||||
<option value="true">同意</option>
|
||||
<option value="false">拒绝</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,188 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.kafeitu.me/activiti">
|
||||
<process id="conditionTest" name="请假流程-外置表单" isExecutable="true">
|
||||
<documentation>《Activiti实战》第6章的例子--外置表单</documentation>
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="start" delegateExpression="${execution.setVariable('leave', leaveEntityManager.newLeave(execution))}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
<startEvent id="startevent1" name="Start" activiti:initiator="applyUserId" activiti:formKey="chapter6/leave-timeLimit-money/leave-start.form"></startEvent>
|
||||
<userTask id="deptLeaderVerify" name="部门经理审批" activiti:candidateGroups="deptLeader" activiti:formKey="chapter6/leave-timeLimit-money/approve-deptLeader.form">
|
||||
<extensionElements>
|
||||
<activiti:taskListener event="complete" expression="${leave.setDeptLeaderApproved(deptLeaderApproved)}"></activiti:taskListener>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<userTask id="hrVerify" name="人事经理审批" activiti:candidateGroups="hr" activiti:formKey="chapter6/leave-timeLimit-money/approve-hr.form"></userTask>
|
||||
<exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<userTask id="modifyApply" name="调整申请内容" activiti:assignee="${applyUserId}" activiti:formKey="chapter6/leave-timeLimit-money/modify-apply.form"></userTask>
|
||||
<exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="deptLeaderVerify"></sequenceFlow>
|
||||
<sequenceFlow id="flow2" sourceRef="deptLeaderVerify" targetRef="exclusivegateway1"></sequenceFlow>
|
||||
<sequenceFlow id="flow3" name="同意" sourceRef="exclusivegateway1" targetRef="hrVerify">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderApproved == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow4" sourceRef="hrVerify" targetRef="exclusivegateway2"></sequenceFlow>
|
||||
<sequenceFlow id="flow7" name="不同意" sourceRef="exclusivegateway2" targetRef="modifyApply">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrApproved == 'false'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow8" name="不同意" sourceRef="exclusivegateway1" targetRef="modifyApply">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderApproved == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow9" sourceRef="modifyApply" targetRef="exclusivegateway3"></sequenceFlow>
|
||||
<sequenceFlow id="flow10" name="调整后继续申请" sourceRef="exclusivegateway3" targetRef="deptLeaderVerify">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow11" name="取消申请,并设置取消标志" sourceRef="exclusivegateway3" targetRef="endevent1">
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="take" expression="${execution.setVariable('result', 'canceled')}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'false'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<exclusiveGateway id="exclusivegateway4" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<sequenceFlow id="flow12" name="同意" sourceRef="exclusivegateway2" targetRef="exclusivegateway4">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrApproved == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<userTask id="usertask1" name="财务打款" activiti:candidateGroups="caiwu" activiti:dueDate="${moneyDateVariable}">
|
||||
<documentation>倒计时30秒</documentation>
|
||||
</userTask>
|
||||
<sequenceFlow id="flow13" name="金额 >= 1万元" sourceRef="exclusivegateway4" targetRef="usertask1">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${amountMoney >= 10000}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<userTask id="usertask2" name="金额不够重新付款"></userTask>
|
||||
<sequenceFlow id="flow14" name="金额 < 1万" sourceRef="exclusivegateway4" targetRef="usertask2">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${amountMoney < 10000}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<userTask id="usertask3" name="打款完成" activiti:candidateGroups="chuNa"></userTask>
|
||||
<sequenceFlow id="flow15" sourceRef="usertask1" targetRef="usertask3"></sequenceFlow>
|
||||
<textAnnotation id="textannotation1" textFormat="text/plain">
|
||||
<text>请求被驳回后员工可以选择继续申请,或者取消本次申请</text>
|
||||
</textAnnotation>
|
||||
<association id="association1" sourceRef="modifyApply" targetRef="textannotation1"></association>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_conditionTest">
|
||||
<bpmndi:BPMNPlane bpmnElement="conditionTest" id="BPMNPlane_conditionTest">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="10.0" y="50.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="deptLeaderVerify" id="BPMNShape_deptLeaderVerify">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="90.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="230.0" y="47.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="hrVerify" id="BPMNShape_hrVerify">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="310.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="470.0" y="47.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="615.0" y="213.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="modifyApply" id="BPMNShape_modifyApply">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="198.0" y="120.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway3" id="BPMNShape_exclusivegateway3">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="230.0" y="210.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway4" id="BPMNShape_exclusivegateway4">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="600.0" y="47.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="760.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="568.0" y="140.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="910.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="textannotation1" id="BPMNShape_textannotation1">
|
||||
<omgdc:Bounds height="57.0" width="112.0" x="340.0" y="168.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="45.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="90.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="195.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="230.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
|
||||
<omgdi:waypoint x="270.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="310.0" y="67.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="42.0" width="22.0" x="269.0" y="50.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
|
||||
<omgdi:waypoint x="415.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="470.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
|
||||
<omgdi:waypoint x="490.0" y="87.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="490.0" y="147.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="303.0" y="147.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="42.0" width="33.0" x="438.0" y="119.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
|
||||
<omgdi:waypoint x="250.0" y="87.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="250.0" y="120.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="42.0" width="33.0" x="260.0" y="87.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
|
||||
<omgdi:waypoint x="250.0" y="175.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="250.0" y="210.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow10" id="BPMNEdge_flow10">
|
||||
<omgdi:waypoint x="230.0" y="230.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="142.0" y="230.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="142.0" y="95.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="42.0" width="77.0" x="159.0" y="210.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow11" id="BPMNEdge_flow11">
|
||||
<omgdi:waypoint x="270.0" y="230.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="615.0" y="230.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="42.0" width="100.0" x="58.0" y="-37.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow12" id="BPMNEdge_flow12">
|
||||
<omgdi:waypoint x="510.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="600.0" y="67.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="14.0" width="24.0" x="510.0" y="67.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow13" id="BPMNEdge_flow13">
|
||||
<omgdi:waypoint x="640.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="760.0" y="67.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="14.0" width="72.0" x="640.0" y="67.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow14" id="BPMNEdge_flow14">
|
||||
<omgdi:waypoint x="620.0" y="87.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="620.0" y="140.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="14.0" width="54.0" x="620.0" y="87.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow15" id="BPMNEdge_flow15">
|
||||
<omgdi:waypoint x="865.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="910.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="association1" id="BPMNEdge_association1">
|
||||
<omgdi:waypoint x="303.0" y="147.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="396.0" y="168.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,18 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" value="${startDate}" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" value="${endDate}" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" required>${reason}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">是否继续申请:</label>
|
||||
<div class="controls">
|
||||
<select id="reApply" name="reApply">
|
||||
<option value='true'>重新申请</option>
|
||||
<option value='false'>结束流程</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">申请人:</label>
|
||||
<div class="controls">${applyUserId}</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="startDate">开始时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="startDate" name="startDate" value="${startDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="endDate">结束时间:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="endDate" name="endDate" value="${endDate}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reason">请假原因:</label>
|
||||
<div class="controls">
|
||||
<textarea id="reason" name="reason" readonly>${reason}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="reportBackDate">销假日期:</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="reportBackDate" name="reportBackDate" class="datepicker" data-date-format="yyyy-mm-dd" required />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,152 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
|
||||
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath"
|
||||
targetNamespace="http://www.activiti.org/test">
|
||||
<process id="leave-countersign" name="请假流程-会签" isExecutable="true">
|
||||
<documentation>请假流程演示-会签</documentation>
|
||||
<startEvent id="startevent1" name="Start" activiti:initiator="applyUserId">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="users" name="审批参与人" type="users" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="validScript" type="javascript" default="alert('表单已经加载完毕');"></activiti:formProperty>
|
||||
</extensionElements>
|
||||
</startEvent>
|
||||
<userTask id="countersign" name="[部门/人事]联合会签" activiti:assignee="${user}">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="approved" name="审批意见" type="enum" required="true">
|
||||
<activiti:value id="true" name="同意"></activiti:value>
|
||||
<activiti:value id="false" name="拒绝"></activiti:value>
|
||||
</activiti:formProperty>
|
||||
<activiti:taskListener event="complete" delegateExpression="${leaveCounterSignCompleteListener}"></activiti:taskListener>
|
||||
</extensionElements>
|
||||
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="users"
|
||||
activiti:elementVariable="user"></multiInstanceLoopCharacteristics>
|
||||
</userTask>
|
||||
<userTask id="reportBack" name="销假" activiti:assignee="${applyUserId}">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" writable="false"></activiti:formProperty>
|
||||
<activiti:formProperty id="reportBackDate" name="销假日期" type="date" default="${endDate}" datePattern="yyyy-MM-dd"
|
||||
required="true"></activiti:formProperty>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<sequenceFlow id="flow2" sourceRef="startevent1" targetRef="countersign">
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="take" expression="${execution.setVariable('approvedCounter', 0)}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow8" name="销假" sourceRef="reportBack" targetRef="endevent1">
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="take" expression="${execution.setVariable('result', 'ok')}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow13" name="全部通过" sourceRef="exclusivegateway1" targetRef="reportBack">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvedCounter == users.size()}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<sequenceFlow id="flow14" sourceRef="countersign" targetRef="exclusivegateway1"></sequenceFlow>
|
||||
<userTask id="modifyApply" name="调整申请" activiti:assignee="${applyUserId}">
|
||||
<extensionElements>
|
||||
<activiti:formProperty id="startDate" name="请假开始日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="endDate" name="请假结束日期" type="date" datePattern="yyyy-MM-dd" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="reason" name="请假原因" type="string" required="true"></activiti:formProperty>
|
||||
<activiti:formProperty id="reApply" name="重新申请" type="enum" required="true">
|
||||
<activiti:value id="true" name="重新申请"></activiti:value>
|
||||
<activiti:value id="false" name="取消申请"></activiti:value>
|
||||
</activiti:formProperty>
|
||||
</extensionElements>
|
||||
</userTask>
|
||||
<sequenceFlow id="flow15" name="部分通过" sourceRef="exclusivegateway1" targetRef="modifyApply">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvedCounter < users.size()}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
|
||||
<sequenceFlow id="flow16" sourceRef="modifyApply" targetRef="exclusivegateway2"></sequenceFlow>
|
||||
<sequenceFlow id="flow17" sourceRef="exclusivegateway2" targetRef="endevent1">
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'false'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
<sequenceFlow id="flow18" name="重新申请" sourceRef="exclusivegateway2" targetRef="countersign">
|
||||
<extensionElements>
|
||||
<activiti:executionListener event="take" expression="${execution.setVariable('approvedCounter', 0)}"></activiti:executionListener>
|
||||
</extensionElements>
|
||||
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'true'}]]></conditionExpression>
|
||||
</sequenceFlow>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_leave-countersign">
|
||||
<bpmndi:BPMNPlane bpmnElement="leave-countersign" id="BPMNPlane_leave-countersign">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="10.0" y="30.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="reportBack" id="BPMNShape_reportBack">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="340.0" y="20.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="375.0" y="203.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="countersign" id="BPMNShape_countersign">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="90.0" y="20.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="220.0" y="27.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="modifyApply" id="BPMNShape_modifyApply">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="188.0" y="110.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
|
||||
<omgdc:Bounds height="40.0" width="40.0" x="220.0" y="200.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="45.0" y="47.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="90.0" y="47.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
|
||||
<omgdi:waypoint x="392.0" y="75.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="392.0" y="203.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="100.0" x="-22.0" y="-17.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow13" id="BPMNEdge_flow13">
|
||||
<omgdi:waypoint x="260.0" y="47.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="340.0" y="47.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="100.0" x="-29.0" y="-17.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow14" id="BPMNEdge_flow14">
|
||||
<omgdi:waypoint x="195.0" y="47.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="220.0" y="47.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow15" id="BPMNEdge_flow15">
|
||||
<omgdi:waypoint x="240.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="240.0" y="110.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="100.0" x="10.0" y="0.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow16" id="BPMNEdge_flow16">
|
||||
<omgdi:waypoint x="240.0" y="165.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="240.0" y="200.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow17" id="BPMNEdge_flow17">
|
||||
<omgdi:waypoint x="260.0" y="220.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="375.0" y="220.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow18" id="BPMNEdge_flow18">
|
||||
<omgdi:waypoint x="220.0" y="220.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="142.0" y="220.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="142.0" y="75.0"></omgdi:waypoint>
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds height="11.0" width="100.0" x="9.0" y="14.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
|
||||
<process id="testMultiInstanceFixedNumbers" name="testMultiInstanceFixedNumbers" isExecutable="true">
|
||||
<startEvent id="startevent1" name="Start"></startEvent>
|
||||
<serviceTask id="servicetask1" name="Service Task" activiti:expression="${counter + 1}" activiti:resultVariableName="counter">
|
||||
<multiInstanceLoopCharacteristics isSequential="false">
|
||||
<loopCardinality>${loop}</loopCardinality>
|
||||
</multiInstanceLoopCharacteristics>
|
||||
</serviceTask>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<sequenceFlow id="flow2" sourceRef="receivetask1" targetRef="endevent1"></sequenceFlow>
|
||||
<receiveTask id="receivetask1" name="Receive Task"></receiveTask>
|
||||
<sequenceFlow id="flow3" sourceRef="servicetask1" targetRef="receivetask1"></sequenceFlow>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceFixedNumbers">
|
||||
<bpmndi:BPMNPlane bpmnElement="testMultiInstanceFixedNumbers" id="BPMNPlane_testMultiInstanceFixedNumbers">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="50.0" y="50.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="116.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="410.0" y="50.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="receivetask1" id="BPMNShape_receivetask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="260.0" y="40.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="85.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="116.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="365.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="410.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
|
||||
<omgdi:waypoint x="221.0" y="67.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="260.0" y="67.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
|
||||
<process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
|
||||
<startEvent id="startevent1" name="Start"></startEvent>
|
||||
<userTask id="usertask1" name="User Task" activiti:assignee="${user}">
|
||||
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${users}" activiti:elementVariable="user"></multiInstanceLoopCharacteristics>
|
||||
</userTask>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<sequenceFlow id="flow2" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
|
||||
<userTask id="usertask2" name="User Task2"></userTask>
|
||||
<sequenceFlow id="flow3" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="90.0" y="80.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="170.0" y="70.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="340.0" y="190.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="170.0" y="180.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="125.0" y="97.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="170.0" y="97.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="275.0" y="207.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="340.0" y="207.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
|
||||
<omgdi:waypoint x="222.0" y="125.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="222.0" y="180.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
|
||||
<process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
|
||||
<startEvent id="startevent1" name="Start"></startEvent>
|
||||
<userTask id="usertask1" name="User Task">
|
||||
<multiInstanceLoopCharacteristics isSequential="false">
|
||||
<loopCardinality>3</loopCardinality>
|
||||
</multiInstanceLoopCharacteristics>
|
||||
</userTask>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="90.0" y="80.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="170.0" y="70.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="330.0" y="80.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="125.0" y="97.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="170.0" y="97.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="275.0" y="97.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="330.0" y="97.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
|
||||
<process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
|
||||
<startEvent id="startevent1" name="Start"></startEvent>
|
||||
<userTask id="usertask1" name="User Task">
|
||||
<multiInstanceLoopCharacteristics isSequential="true">
|
||||
<loopCardinality>3</loopCardinality>
|
||||
</multiInstanceLoopCharacteristics>
|
||||
</userTask>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="90.0" y="80.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="170.0" y="70.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="330.0" y="80.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="125.0" y="97.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="170.0" y="97.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="275.0" y="97.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="330.0" y="97.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
|
||||
<process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
|
||||
<startEvent id="startevent1" name="Start"></startEvent>
|
||||
<userTask id="usertask1" name="User Task" activiti:assignee="${user}">
|
||||
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${users}" activiti:elementVariable="user"></multiInstanceLoopCharacteristics>
|
||||
</userTask>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="50.0" y="90.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="130.0" y="80.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="290.0" y="90.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="85.0" y="107.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="130.0" y="107.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="235.0" y="107.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="290.0" y="107.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
|
||||
<process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
|
||||
<startEvent id="startevent1" name="Start"></startEvent>
|
||||
<userTask id="usertask1" name="User Task" activiti:assignee="${user}">
|
||||
<multiInstanceLoopCharacteristics isSequential="true" activiti:collection="${users}" activiti:elementVariable="user"></multiInstanceLoopCharacteristics>
|
||||
</userTask>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="50.0" y="90.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="130.0" y="80.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="290.0" y="90.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="85.0" y="107.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="130.0" y="107.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="235.0" y="107.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="290.0" y="107.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
|
||||
<process id="testMultiInstanceForUserTask" name="testMultiInstanceForUserTask" isExecutable="true">
|
||||
<startEvent id="startevent1" name="Start"></startEvent>
|
||||
<userTask id="usertask1" name="User Task" activiti:assignee="${user}">
|
||||
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${users}" activiti:elementVariable="user">
|
||||
<completionCondition>${nrOfCompletedInstances / nrOfInstances >= rate}</completionCondition>
|
||||
</multiInstanceLoopCharacteristics>
|
||||
</userTask>
|
||||
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
|
||||
<endEvent id="endevent1" name="End"></endEvent>
|
||||
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNPlane bpmnElement="testMultiInstanceForUserTask" id="BPMNPlane_testMultiInstanceForUserTask">
|
||||
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="50.0" y="90.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
|
||||
<omgdc:Bounds height="55.0" width="105.0" x="130.0" y="80.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
|
||||
<omgdc:Bounds height="35.0" width="35.0" x="290.0" y="90.0"></omgdc:Bounds>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
|
||||
<omgdi:waypoint x="85.0" y="107.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="130.0" y="107.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
|
||||
<omgdi:waypoint x="235.0" y="107.0"></omgdi:waypoint>
|
||||
<omgdi:waypoint x="290.0" y="107.0"></omgdi:waypoint>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="processEngineConfiguration" class="org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration">
|
||||
<!--保存到文件模式-->
|
||||
<!--<property name="jdbcUrl" value="jdbc:h2:file:~/activiti-in-action-chapter6;AUTO_SERVER=TRUE" />-->
|
||||
<!--内存模式-->
|
||||
<property name="jdbcUrl" value="jdbc:h2:mem:activiti-in-action-chapter6;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE" />
|
||||
<property name="jdbcDriver" value="org.h2.Driver" />
|
||||
<property name="jdbcUsername" value="sa" />
|
||||
<property name="jdbcPassword" value="" />
|
||||
|
||||
<!--HistoryLevel-->
|
||||
<property name="history" value="full" />
|
||||
<property name="databaseSchemaUpdate" value="true" />
|
||||
<property name="flowable5CompatibilityEnabled" value="false" />
|
||||
<!-- <property name="jobExecutorActivate" value="false" />-->
|
||||
</bean>
|
||||
</beans>
|
||||
Reference in New Issue
Block a user