diff --git a/pom.xml b/pom.xml
index 5c3b0aab..4bd8495f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
yudao-server
+ yudao-module-bpm
yudao-module-member
yudao-module-system
yudao-module-infra
diff --git a/yudao-module-bpm/pom.xml b/yudao-module-bpm/pom.xml
new file mode 100644
index 00000000..6ecdc809
--- /dev/null
+++ b/yudao-module-bpm/pom.xml
@@ -0,0 +1,32 @@
+
+
+
+ yudao
+ cn.iocoder.boot
+ ${revision}
+
+ 4.0.0
+
+ yudao-module-bpm-api
+ yudao-module-bpm-base
+ yudao-module-bpm-impl-flowable
+ yudao-module-bpm-impl-activiti
+
+ yudao-module-bpm
+ pom
+
+ ${project.artifactId}
+
+ bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能。
+ 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等
+ bpm 解释:https://baike.baidu.com/item/BPM/1933
+
+ 目前提供两套实现方案:
+ 1. 基于 Activiti 7 实现的 yudao-module-bpm-impl-activiti
+ 2. 基于 Flowable 6 实现的 yudao-module-bpm-impl-flowable
+ 两套实现会存在共享的逻辑,所以会继承 yudao-module-impl-base
+
+
+
diff --git a/yudao-module-bpm/yudao-module-bpm-api/pom.xml b/yudao-module-bpm/yudao-module-bpm-api/pom.xml
new file mode 100644
index 00000000..06554d7f
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-api/pom.xml
@@ -0,0 +1,33 @@
+
+
+
+ cn.iocoder.boot
+ yudao-module-bpm
+ ${revision}
+
+ 4.0.0
+ yudao-module-bpm-api
+ jar
+
+ ${project.artifactId}
+
+ bpm 模块 API,暴露给其它模块调用
+
+
+
+
+ cn.iocoder.boot
+ yudao-common
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ true
+
+
+
+
diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java
new file mode 100644
index 00000000..37a92219
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * bpm API 包,定义暴露给其它模块的 API
+ */
+package cn.iocoder.yudao.module.bpm.api;
diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java
new file mode 100644
index 00000000..e94a2c84
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApi.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.bpm.api.task;
+
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+
+import javax.validation.Valid;
+
+/**
+ * 流程实例 Api 接口
+ *
+ * @author 芋道源码
+ */
+public interface BpmProcessInstanceApi {
+
+ /**
+ * 创建流程实例(提供给内部)
+ *
+ * @param userId 用户编号
+ * @param reqDTO 创建信息
+ * @return 实例的编号
+ */
+ String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java
new file mode 100644
index 00000000..5d7edbe8
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.bpm.api.task.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import java.util.Map;
+
+/**
+ * 流程实例的创建 Request DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class BpmProcessInstanceCreateReqDTO {
+
+ /**
+ * 流程定义的标识
+ */
+ @NotEmpty(message = "流程定义的标识不能为空")
+ private String processDefinitionKey;
+ /**
+ * 变量实例
+ */
+ private Map variables;
+
+ /**
+ * 业务的唯一标识
+ *
+ * 例如说,请假申请的编号。通过它,可以查询到对应的实例
+ */
+ @NotEmpty(message = "业务的唯一标识")
+ private String businessKey;
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/DictTypeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/DictTypeConstants.java
new file mode 100644
index 00000000..7abb3e1d
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/DictTypeConstants.java
@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.bpm.enums;
+
+/**
+ * BPM 字典类型的枚举类
+ *
+ * @author 芋道源码
+ */
+public interface DictTypeConstants {
+
+ String TASK_ASSIGN_RULE_TYPE = "bpm_task_assign_rule_type"; // 任务分配规则类型
+ String TASK_ASSIGN_SCRIPT = "bpm_task_assign_script"; // 任务分配自定义脚本
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
new file mode 100644
index 00000000..9451f6d4
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.bpm.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * 工作流 错误码枚举类
+ *
+ * 工作流系统,使用 1-009-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 通用流程处理 模块 1-009-000-000 ==========
+ ErrorCode HIGHLIGHT_IMG_ERROR = new ErrorCode(1009000002, "获取高亮流程图异常");
+
+ // ========== OA 流程模块 1-009-001-000 ==========
+ ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1009001001, "请假申请不存在");
+ ErrorCode OA_PM_POST_NOT_EXISTS = new ErrorCode(1009001002, "项目经理岗位未设置");
+ ErrorCode OA_DEPART_PM_POST_NOT_EXISTS = new ErrorCode(1009001009, "部门的项目经理不存在");
+ ErrorCode OA_BM_POST_NOT_EXISTS = new ErrorCode(1009001004, "部门经理岗位未设置");
+ ErrorCode OA_DEPART_BM_POST_NOT_EXISTS = new ErrorCode(1009001005, "部门的部门经理不存在");
+ ErrorCode OA_HR_POST_NOT_EXISTS = new ErrorCode(1009001006, "HR岗位未设置");
+ ErrorCode OA_DAY_LEAVE_ERROR = new ErrorCode(1009001007, "请假天数必须>=1");
+
+ // ========== 流程模型 1-009-002-000 ==========
+ ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1009002000, "已经存在流程标识为【{}】的流程");
+ ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1009002001, "流程模型不存在");
+ ErrorCode MODEL_KEY_VALID = new ErrorCode(1009002002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!");
+ ErrorCode MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG = new ErrorCode(1009002003, "部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置");
+ ErrorCode MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG = new ErrorCode(1009002004, "部署流程失败," +
+ "原因:用户任务({})未配置分配规则,请点击【修改流程】按钮进行配置");
+ ErrorCode MODEL_DEPLOY_FAIL_TASK_INFO_EQUALS = new ErrorCode(1009003005, "流程定义部署失败,原因:信息未发生变化");
+
+ // ========== 流程定义 1-009-003-000 ==========
+ ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1009003000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
+ ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1009003001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
+ ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1009003002, "流程定义不存在");
+ ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1009003003, "流程定义处于挂起状态");
+ ErrorCode PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS = new ErrorCode(1009003004, "流程定义的模型不存在");
+
+ // ========== 流程实例 1-009-004-000 ==========
+ ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1009004000, "流程实例不存在");
+ ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1009004001, "流程取消失败,流程不处于运行中");
+ ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1009004002, "流程取消失败,该流程不是你发起的");
+
+ // ========== 流程任务 1-009-005-000 ==========
+ ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009005000, "审批任务失败,原因:该任务不处于未审批");
+ ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009005001, "审批任务失败,原因:该任务的审批人不是你");
+
+ // ========== 流程任务分配规则 1-009-006-000 ==========
+ ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1009006000, "流程({}) 的任务({}) 已经存在分配规则");
+ ErrorCode TASK_ASSIGN_RULE_NOT_EXISTS = new ErrorCode(1009006001, "流程任务分配规则不存在");
+ ErrorCode TASK_UPDATE_FAIL_NOT_MODEL = new ErrorCode(1009006002, "只有流程模型的任务分配规则,才允许被修改");
+ ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1009006003, "操作失败,原因:找不到任务的审批人!");
+ ErrorCode TASK_ASSIGN_SCRIPT_NOT_EXISTS = new ErrorCode(1009006004, "操作失败,原因:任务分配脚本({}) 不存在");
+
+ // ========== 动态表单模块 1-009-010-000 ==========
+ ErrorCode FORM_NOT_EXISTS = new ErrorCode(1009010000, "动态表单不存在");
+ ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
+
+ // ========== 用户组模块 1-009-011-000 ==========
+ ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1009011000, "用户组不存在");
+ ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1009011001, "名字为【{}】的用户组已被禁用");
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/pom.xml b/yudao-module-bpm/yudao-module-bpm-base/pom.xml
new file mode 100644
index 00000000..1980a094
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+ cn.iocoder.boot
+ yudao-module-bpm
+ ${revision}
+
+ 4.0.0
+ yudao-module-bpm-base
+
+ ${project.artifactId}
+
+ bpm-base 模块,实现公用的工作流的逻辑,提供给 bpm-activiti 和 bpm-flowable 复用
+
+
+
+
+ cn.iocoder.boot
+ yudao-module-bpm-api
+ ${revision}
+
+
+ cn.iocoder.boot
+ yudao-module-system-api
+ ${revision}
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-operatelog
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-biz-data-permission
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-web
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-security
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-mybatis
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-test
+
+
+
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmFormController.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmFormController.java
new file mode 100644
index 00000000..217067f3
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmFormController.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.*;
+import cn.iocoder.yudao.module.bpm.convert.definition.BpmFormConvert;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 动态表单")
+@RestController
+@RequestMapping("/bpm/form")
+@Validated
+public class BpmFormController {
+
+ @Resource
+ private BpmFormService formService;
+
+ @PostMapping("/create")
+ @ApiOperation("创建动态表单")
+ @PreAuthorize("@ss.hasPermission('bpm:form:create')")
+ public CommonResult createForm(@Valid @RequestBody BpmFormCreateReqVO createReqVO) {
+ return success(formService.createForm(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @ApiOperation("更新动态表单")
+ @PreAuthorize("@ss.hasPermission('bpm:form:update')")
+ public CommonResult updateForm(@Valid @RequestBody BpmFormUpdateReqVO updateReqVO) {
+ formService.updateForm(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @ApiOperation("删除动态表单")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('bpm:form:delete')")
+ public CommonResult deleteForm(@RequestParam("id") Long id) {
+ formService.deleteForm(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @ApiOperation("获得动态表单")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('bpm:form:query')")
+ public CommonResult getForm(@RequestParam("id") Long id) {
+ BpmFormDO form = formService.getForm(id);
+ return success(BpmFormConvert.INSTANCE.convert(form));
+ }
+
+ @GetMapping("/list-all-simple")
+ @ApiOperation(value = "获得动态表单的精简列表", notes = "用于表单下拉框")
+ public CommonResult> getSimpleForms() {
+ List list = formService.getFormList();
+ return success(BpmFormConvert.INSTANCE.convertList2(list));
+ }
+
+ @GetMapping("/page")
+ @ApiOperation("获得动态表单分页")
+ @PreAuthorize("@ss.hasPermission('bpm:form:query')")
+ public CommonResult> getFormPage(@Valid BpmFormPageReqVO pageVO) {
+ PageResult pageResult = formService.getFormPage(pageVO);
+ return success(BpmFormConvert.INSTANCE.convertPage(pageResult));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmUserGroupController.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmUserGroupController.java
new file mode 100644
index 00000000..4267f090
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmUserGroupController.java
@@ -0,0 +1,85 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.convert.definition.BpmUserGroupConvert;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 用户组")
+@RestController
+@RequestMapping("/bpm/user-group")
+@Validated
+public class BpmUserGroupController {
+
+ @Resource
+ private BpmUserGroupService userGroupService;
+
+ @PostMapping("/create")
+ @ApiOperation("创建用户组")
+ @PreAuthorize("@ss.hasPermission('bpm:user-group:create')")
+ public CommonResult createUserGroup(@Valid @RequestBody BpmUserGroupCreateReqVO createReqVO) {
+ return success(userGroupService.createUserGroup(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @ApiOperation("更新用户组")
+ @PreAuthorize("@ss.hasPermission('bpm:user-group:update')")
+ public CommonResult updateUserGroup(@Valid @RequestBody BpmUserGroupUpdateReqVO updateReqVO) {
+ userGroupService.updateUserGroup(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @ApiOperation("删除用户组")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('bpm:user-group:delete')")
+ public CommonResult deleteUserGroup(@RequestParam("id") Long id) {
+ userGroupService.deleteUserGroup(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @ApiOperation("获得用户组")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+ @PreAuthorize("@ss.hasPermission('bpm:user-group:query')")
+ public CommonResult getUserGroup(@RequestParam("id") Long id) {
+ BpmUserGroupDO userGroup = userGroupService.getUserGroup(id);
+ return success(BpmUserGroupConvert.INSTANCE.convert(userGroup));
+ }
+
+ @GetMapping("/page")
+ @ApiOperation("获得用户组分页")
+ @PreAuthorize("@ss.hasPermission('bpm:user-group:query')")
+ public CommonResult> getUserGroupPage(@Valid BpmUserGroupPageReqVO pageVO) {
+ PageResult pageResult = userGroupService.getUserGroupPage(pageVO);
+ return success(BpmUserGroupConvert.INSTANCE.convertPage(pageResult));
+ }
+
+ @GetMapping("/list-all-simple")
+ @ApiOperation(value = "获取用户组精简信息列表", notes = "只包含被开启的用户组,主要用于前端的下拉选项")
+ public CommonResult> getSimpleUserGroups() {
+ // 获用户门列表,只要开启状态的
+ List list = userGroupService.getUserGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
+ // 排序后,返回给前端
+ return success(BpmUserGroupConvert.INSTANCE.convertList2(list));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormBaseVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormBaseVO.java
new file mode 100644
index 00000000..11fee5c0
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormBaseVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+/**
+* 动态表单 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class BpmFormBaseVO {
+
+ @ApiModelProperty(value = "表单名称", required = true, example = "芋道")
+ @NotNull(message = "表单名称不能为空")
+ private String name;
+
+ @ApiModelProperty(value = "表单状态", required = true, notes = "参见 CommonStatusEnum 枚举", example = "1")
+ @NotNull(message = "表单状态不能为空")
+ private Integer status;
+
+ @ApiModelProperty(value = "备注", example = "我是备注")
+ private String remark;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormCreateReqVO.java
new file mode 100644
index 00000000..d9a62812
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormCreateReqVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
+
+import lombok.*;
+import io.swagger.annotations.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@ApiModel("管理后台 - 动态表单创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmFormCreateReqVO extends BpmFormBaseVO {
+
+ @ApiModelProperty(value = "表单的配置", required = true, notes = "JSON 字符串")
+ @NotNull(message = "表单的配置不能为空")
+ private String conf;
+
+ @ApiModelProperty(value = "表单项的数组", required = true, notes = "JSON 字符串的数组")
+ @NotNull(message = "表单项的数组不能为空")
+ private List fields;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java
new file mode 100644
index 00000000..a8097c43
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 动态表单分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmFormPageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "表单名称", example = "芋道")
+ private String name;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java
new file mode 100644
index 00000000..76cf6204
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+
+@ApiModel("管理后台 - 动态表单 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmFormRespVO extends BpmFormBaseVO {
+
+ @ApiModelProperty(value = "表单编号", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "表单的配置", required = true, notes = "JSON 字符串")
+ @NotNull(message = "表单的配置不能为空")
+ private String conf;
+
+ @ApiModelProperty(value = "表单项的数组", required = true, notes = "JSON 字符串的数组")
+ @NotNull(message = "表单项的数组不能为空")
+ private List fields;
+
+ @ApiModelProperty(value = "创建时间", required = true)
+ private Date createTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormSimpleRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormSimpleRespVO.java
new file mode 100644
index 00000000..3a6b3c09
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormSimpleRespVO.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@ApiModel("管理后台 - 流程表单精简 Response VO")
+@Data
+public class BpmFormSimpleRespVO {
+
+ @ApiModelProperty(value = "表单编号", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "表单名称", required = true, example = "芋道")
+ private String name;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormUpdateReqVO.java
new file mode 100644
index 00000000..55b99767
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/form/BpmFormUpdateReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+import java.util.List;
+
+@ApiModel("管理后台 - 动态表单更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmFormUpdateReqVO extends BpmFormBaseVO {
+
+ @ApiModelProperty(value = "表单编号", required = true, example = "1024")
+ @NotNull(message = "表单编号不能为空")
+ private Long id;
+
+ @ApiModelProperty(value = "表单的配置", required = true, notes = "JSON 字符串")
+ @NotNull(message = "表单的配置不能为空")
+ private String conf;
+
+ @ApiModelProperty(value = "表单项的数组", required = true, notes = "JSON 字符串的数组")
+ @NotNull(message = "表单项的数组不能为空")
+ private List fields;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupBaseVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupBaseVO.java
new file mode 100644
index 00000000..e0dfe568
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupBaseVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+/**
+* 用户组 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class BpmUserGroupBaseVO {
+
+ @ApiModelProperty(value = "组名", required = true, example = "芋道")
+ @NotNull(message = "组名不能为空")
+ private String name;
+
+ @ApiModelProperty(value = "描述", required = true, example = "芋道源码")
+ @NotNull(message = "描述不能为空")
+ private String description;
+
+ @ApiModelProperty(value = "成员编号数组", required = true, example = "1,2,3")
+ @NotNull(message = "成员编号数组不能为空")
+ private Set memberUserIds;
+
+ @ApiModelProperty(value = "状态", required = true, example = "1")
+ @NotNull(message = "状态不能为空")
+ private Integer status;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupCreateReqVO.java
new file mode 100644
index 00000000..48b44d38
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupCreateReqVO.java
@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group;
+
+import lombok.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 用户组创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmUserGroupCreateReqVO extends BpmUserGroupBaseVO {
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java
new file mode 100644
index 00000000..44631737
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 用户组分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmUserGroupPageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "组名", example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "状态", example = "1")
+ private Integer status;
+
+ @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @ApiModelProperty(value = "开始创建时间")
+ private Date beginCreateTime;
+
+ @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @ApiModelProperty(value = "结束创建时间")
+ private Date endCreateTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java
new file mode 100644
index 00000000..55018d50
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+
+@ApiModel("管理后台 - 用户组 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmUserGroupRespVO extends BpmUserGroupBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "创建时间", required = true)
+ private Date createTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSimpleRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSimpleRespVO.java
new file mode 100644
index 00000000..0b47f447
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSimpleRespVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@ApiModel("管理后台 - 用户组精简信息 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmUserGroupSimpleRespVO {
+
+ @ApiModelProperty(value = "用户组编号", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "用户组名字", required = true, example = "芋道")
+ private String name;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupUpdateReqVO.java
new file mode 100644
index 00000000..14aa25c5
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/group/BpmUserGroupUpdateReqVO.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+
+@ApiModel("管理后台 - 用户组更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmUserGroupUpdateReqVO extends BpmUserGroupBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java
new file mode 100644
index 00000000..7240ba15
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel(value = "管理后台 - 流程模型的导入 Request VO", description = "相比流程模型的新建来说,只是多了一个 bpmnFile 文件")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmModeImportReqVO extends BpmModelCreateReqVO {
+
+ @ApiModelProperty(value = "BPMN 文件", required = true)
+ @NotNull(message = "BPMN 文件不能为空")
+ private MultipartFile bpmnFile;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelBaseVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelBaseVO.java
new file mode 100644
index 00000000..542a177e
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelBaseVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+/**
+* 流程模型 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class BpmModelBaseVO {
+
+ @ApiModelProperty(value = "流程标识", required = true, example = "process_yudao")
+ @NotEmpty(message = "流程标识不能为空")
+ private String key;
+
+ @ApiModelProperty(value = "流程名称", required = true, example = "芋道")
+ @NotEmpty(message = "流程名称不能为空")
+ private String name;
+
+ @ApiModelProperty(value = "流程描述", example = "我是描述")
+ private String description;
+
+ @ApiModelProperty(value = "流程分类", notes = "参见 bpm_model_category 数据字典", example = "1")
+ @NotEmpty(message = "流程分类不能为空")
+ private String category;
+
+ @ApiModelProperty(value = "表单类型", notes = "参见 bpm_model_form_type 数据字典", example = "1")
+ private Integer formType;
+ @ApiModelProperty(value = "表单编号", example = "1024", notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private Long formId;
+ @ApiModelProperty(value = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create",
+ notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formCustomCreatePath;
+ @ApiModelProperty(value = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view",
+ notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formCustomViewPath;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java
new file mode 100644
index 00000000..c85ef3ae
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotEmpty;
+
+@ApiModel("管理后台 - 流程模型的创建 Request VO")
+@Data
+public class BpmModelCreateReqVO {
+
+ @ApiModelProperty(value = "流程标识", required = true, example = "process_yudao")
+ @NotEmpty(message = "流程标识不能为空")
+ private String key;
+
+ @ApiModelProperty(value = "流程名称", required = true, example = "芋道")
+ @NotEmpty(message = "流程名称不能为空")
+ private String name;
+
+ @ApiModelProperty(value = "流程描述", example = "我是描述")
+ private String description;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageItemRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageItemRespVO.java
new file mode 100644
index 00000000..7442b5ab
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageItemRespVO.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 流程模型的分页的每一项 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmModelPageItemRespVO extends BpmModelBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "表单名字", example = "请假表单")
+ private String formName;
+
+ @ApiModelProperty(value = "创建时间", required = true)
+ private Date createTime;
+
+ /**
+ * 最新部署的流程定义
+ */
+ private ProcessDefinition processDefinition;
+
+ @ApiModel("流程定义")
+ @Data
+ public static class ProcessDefinition {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "版本", required = true, example = "1")
+ private Integer version;
+
+ @ApiModelProperty(value = "部署时间", required = true)
+ private Date deploymentTime;
+
+ @ApiModelProperty(value = "中断状态", required = true, example = "1", notes = "参见 SuspensionState 枚举")
+ private Integer suspensionState;
+
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java
new file mode 100644
index 00000000..945da139
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+
+@ApiModel("管理后台 - 流程模型分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmModelPageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "标识", example = "process1641042089407", notes = "精准匹配")
+ private String key;
+
+ @ApiModelProperty(value = "名字", example = "芋道", notes = "模糊匹配")
+ private String name;
+
+ @ApiModelProperty(value = "流程分类", notes = "参见 bpm_model_category 数据字典", example = "1")
+ private String category;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
new file mode 100644
index 00000000..bce0f910
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 流程模型的创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmModelRespVO extends BpmModelBaseVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "BPMN XML", required = true)
+ private String bpmnXml;
+
+ @ApiModelProperty(value = "创建时间", required = true)
+ private Date createTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java
new file mode 100644
index 00000000..972aa31b
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+@ApiModel("管理后台 - 流程模型的更新 Request VO")
+@Data
+public class BpmModelUpdateReqVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ @NotEmpty(message = "编号不能为空")
+ private String id;
+
+ @ApiModelProperty(value = "流程名称", example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "流程描述", example = "我是描述")
+ private String description;
+
+ @ApiModelProperty(value = "流程分类", notes = "参见 bpm_model_category 数据字典", example = "1")
+ private String category;
+
+ @ApiModelProperty(value = "BPMN XML", required = true)
+ private String bpmnXml;
+
+ @ApiModelProperty(value = "表单类型", notes = "参见 bpm_model_form_type 数据字典", example = "1")
+ private Integer formType;
+ @ApiModelProperty(value = "表单编号", example = "1024", notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private Long formId;
+ @ApiModelProperty(value = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create",
+ notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formCustomCreatePath;
+ @ApiModelProperty(value = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view",
+ notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formCustomViewPath;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java
new file mode 100644
index 00000000..9cfcebbd
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 流程模型更新状态 Request VO")
+@Data
+public class BpmModelUpdateStateReqVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ @NotNull(message = "编号不能为空")
+ private String id;
+
+ @ApiModelProperty(value = "状态", required = true, example = "1", notes = "见 SuspensionState 枚举")
+ @NotNull(message = "状态不能为空")
+ private Integer state;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionListReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionListReqVO.java
new file mode 100644
index 00000000..5e45b8bd
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionListReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 流程定义列表 Request VO")
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class BpmProcessDefinitionListReqVO extends PageParam {
+
+ @ApiModelProperty(value = "中断状态", example = "1", notes = "参见 SuspensionState 枚举")
+ private Integer suspensionState;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageItemRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageItemRespVO.java
new file mode 100644
index 00000000..75990f08
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageItemRespVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 流程定义的分页的每一项 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmProcessDefinitionPageItemRespVO extends BpmProcessDefinitionRespVO {
+
+ @ApiModelProperty(value = "表单名字", example = "请假表单")
+ private String formName;
+
+ @ApiModelProperty(value = "部署时间", required = true)
+ private Date deploymentTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java
new file mode 100644
index 00000000..a9657e9a
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 流程定义分页 Request VO")
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class BpmProcessDefinitionPageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "标识", example = "process1641042089407", notes = "精准匹配")
+ private String key;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
new file mode 100644
index 00000000..6f8fd4e5
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@ApiModel("管理后台 - 流程定义 Response VO")
+@Data
+public class BpmProcessDefinitionRespVO {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "版本", required = true, example = "1")
+ private Integer version;
+
+ @ApiModelProperty(value = "流程名称", required = true, example = "芋道")
+ @NotEmpty(message = "流程名称不能为空")
+ private String name;
+
+ @ApiModelProperty(value = "流程描述", example = "我是描述")
+ private String description;
+
+ @ApiModelProperty(value = "流程分类", notes = "参见 bpm_model_category 数据字典", example = "1")
+ @NotEmpty(message = "流程分类不能为空")
+ private String category;
+
+ @ApiModelProperty(value = "表单类型", notes = "参见 bpm_model_form_type 数据字典", example = "1")
+ private Integer formType;
+ @ApiModelProperty(value = "表单编号", example = "1024", notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private Long formId;
+ @ApiModelProperty(value = "表单的配置", required = true,
+ notes = "JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formConf;
+ @ApiModelProperty(value = "表单项的数组", required = true,
+ notes = "JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private List formFields;
+ @ApiModelProperty(value = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create",
+ notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formCustomCreatePath;
+ @ApiModelProperty(value = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view",
+ notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formCustomViewPath;
+
+ @ApiModelProperty(value = "中断状态", required = true, example = "1", notes = "参见 SuspensionState 枚举")
+ private Integer suspensionState;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleBaseVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleBaseVO.java
new file mode 100644
index 00000000..bcb9fc0f
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleBaseVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Set;
+
+/**
+ * 流程任务分配规则 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class BpmTaskAssignRuleBaseVO {
+
+ @ApiModelProperty(value = "规则类型", required = true, example = "bpm_task_assign_rule_type")
+ @NotNull(message = "规则类型不能为空")
+ private Integer type;
+
+ @ApiModelProperty(value = "规则值数组", required = true, example = "1,2,3")
+ @NotNull(message = "规则值数组不能为空")
+ private Set options;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleCreateReqVO.java
new file mode 100644
index 00000000..cbbc108c
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleCreateReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotEmpty;
+
+@ApiModel("管理后台 - 流程任务分配规则的创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmTaskAssignRuleCreateReqVO extends BpmTaskAssignRuleBaseVO {
+
+ @ApiModelProperty(value = "流程模型的编号", required = true, example = "1024")
+ @NotEmpty(message = "流程模型的编号不能为空")
+ private String modelId;
+
+ @ApiModelProperty(value = "流程任务定义的编号", required = true, example = "2048")
+ @NotEmpty(message = "流程任务定义的编号不能为空")
+ private String taskDefinitionKey;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleRespVO.java
new file mode 100644
index 00000000..c0786e52
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleRespVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 流程任务分配规则的 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmTaskAssignRuleRespVO extends BpmTaskAssignRuleBaseVO {
+
+ @ApiModelProperty(value = "任务分配规则的编号", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "流程模型的编号", required = true, example = "2048")
+ private String modelId;
+
+ @ApiModelProperty(value = "流程定义的编号", required = true, example = "4096")
+ private String processDefinitionId;
+
+ @ApiModelProperty(value = "流程任务定义的编号", required = true, example = "2048")
+ private String taskDefinitionKey;
+ @ApiModelProperty(value = "流程任务定义的名字", required = true, example = "关注芋道")
+ private String taskDefinitionName;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleUpdateReqVO.java
new file mode 100644
index 00000000..8f1c1270
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/rule/BpmTaskAssignRuleUpdateReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 流程任务分配规则的更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmTaskAssignRuleUpdateReqVO extends BpmTaskAssignRuleBaseVO {
+
+ @ApiModelProperty(value = "任务分配规则的编号", required = true, example = "1024")
+ @NotNull(message = "任务分配规则的编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/BpmOALeaveController.http b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/BpmOALeaveController.http
new file mode 100644
index 00000000..96bbf964
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/BpmOALeaveController.http
@@ -0,0 +1,12 @@
+### 请求 /bpm/oa/leave/create 接口 => 成功
+POST {{baseUrl}}/bpm/oa/leave/create
+Content-Type: application/json
+tenant-id: 1
+Authorization: Bearer {{token}}
+
+{
+ "startTime": "2022-03-01",
+ "endTime": "2022-03-05",
+ "type": 1,
+ "reason": "我要请假啦啦啦!"
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/BpmOALeaveController.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/BpmOALeaveController.java
new file mode 100644
index 00000000..dd66580d
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/BpmOALeaveController.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.oa;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveRespVO;
+import cn.iocoder.yudao.module.bpm.convert.oa.BpmOALeaveConvert;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveService;
+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;
+
+/**
+ * OA 请假申请 Controller,用于演示自己存储数据,接入工作流的例子
+ *
+ * @author jason
+ * @author 芋道源码
+ */
+@Api(tags = "管理后台 - OA 请假申请")
+@RestController
+@RequestMapping("/bpm/oa/leave")
+@Validated
+public class BpmOALeaveController {
+
+ @Resource
+ private BpmOALeaveService leaveService;
+
+ @PostMapping("/create")
+ @PreAuthorize("@ss.hasPermission('bpm:oa-leave:create')")
+ @ApiOperation("创建请求申请")
+ public CommonResult createLeave(@Valid @RequestBody BpmOALeaveCreateReqVO createReqVO) {
+ return success(leaveService.createLeave(getLoginUserId(), createReqVO));
+ }
+
+ @GetMapping("/get")
+ @PreAuthorize("@ss.hasPermission('bpm:oa-leave:query')")
+ @ApiOperation("获得请假申请")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+ public CommonResult getLeave(@RequestParam("id") Long id) {
+ BpmOALeaveDO leave = leaveService.getLeave(id);
+ return success(BpmOALeaveConvert.INSTANCE.convert(leave));
+ }
+
+ @GetMapping("/page")
+ @PreAuthorize("@ss.hasPermission('bpm:oa-leave:query')")
+ @ApiOperation("获得请假申请分页")
+ public CommonResult> getLeavePage(@Valid BpmOALeavePageReqVO pageVO) {
+ PageResult pageResult = leaveService.getLeavePage(getLoginUserId(), pageVO);
+ return success(BpmOALeaveConvert.INSTANCE.convertPage(pageResult));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/package-info.java
new file mode 100644
index 00000000..7028708b
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * OA 示例,用于演示外部业务接入 BPM 工作流的示例
+ * 一般的接入方式,只需要调用 接口,后续 Admin 用户在管理后台的【待办事务】进行审批
+ */
+package cn.iocoder.yudao.module.bpm.controller.admin.oa;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveBaseVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveBaseVO.java
new file mode 100644
index 00000000..cbc568a1
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveBaseVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import javax.validation.constraints.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+/**
+* 请假申请 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class BpmOALeaveBaseVO {
+
+ @ApiModelProperty(value = "请假的开始时间", required = true)
+ @NotNull(message = "开始时间不能为空")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date startTime;
+ @ApiModelProperty(value = "请假的结束时间", required = true)
+ @NotNull(message = "结束时间不能为空")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date endTime;
+
+ @ApiModelProperty(value = "请假类型", required = true, example = "1", notes = "参见 bpm_oa_type 枚举")
+ private Integer type;
+
+ @ApiModelProperty(value = "原因", required = true, example = "阅读芋道源码")
+ private String reason;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java
new file mode 100644
index 00000000..2ebab63e
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.AssertTrue;
+
+@ApiModel("管理后台 - 请假申请创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmOALeaveCreateReqVO extends BpmOALeaveBaseVO {
+
+ @AssertTrue(message = "结束时间,需要在开始时间之后")
+ public boolean isEndTimeValid() {
+ return !getEndTime().before(getStartTime());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java
new file mode 100644
index 00000000..0443d44f
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.annotations.*;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 请假申请分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmOALeavePageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "状态", example = "1", notes = "参见 bpm_process_instance_result 枚举")
+ private Integer result;
+
+ @ApiModelProperty(value = "请假类型", example = "1", notes = "参见 bpm_oa_type")
+ private Integer type;
+
+ @ApiModelProperty(value = "原因", example = "阅读芋道源码", notes = "模糊匹配")
+ private String reason;
+
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @ApiModelProperty(value = "开始申请时间")
+ private Date beginCreateTime;
+
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ @ApiModelProperty(value = "结束申请时间")
+ private Date endCreateTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java
new file mode 100644
index 00000000..908c48e0
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo;
+
+import lombok.*;
+import io.swagger.annotations.*;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 请假申请 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmOALeaveRespVO extends BpmOALeaveBaseVO {
+
+ @ApiModelProperty(value = "请假表单主键", required = true, example = "1024")
+ private Long id;
+
+ @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 bpm_process_instance_result 枚举")
+ private Integer result;
+
+ @ApiModelProperty(value = "申请时间", required = true)
+ @NotNull(message = "申请时间不能为空")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date createTime;
+
+ @ApiModelProperty(value = "流程id")
+ private String processInstanceId;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/package-info.java
new file mode 100644
index 00000000..6cf7f955
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java
new file mode 100644
index 00000000..ef9d11ac
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 流程活动的 Response VO")
+@Data
+public class BpmActivityRespVO {
+
+ @ApiModelProperty(value = "流程活动的标识", required = true, example = "1024")
+ private String key;
+ @ApiModelProperty(value = "流程活动的类型", required = true, example = "StartEvent")
+ private String type;
+
+ @ApiModelProperty(value = "流程活动的开始时间", required = true)
+ private Date startTime;
+ @ApiModelProperty(value = "流程活动的结束时间", required = true)
+ private Date endTime;
+
+ @ApiModelProperty(value = "关联的流程任务的编号", example = "2048", notes = "关联的流程任务,只有 UserTask 等类型才有")
+ private String taskId;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java
new file mode 100644
index 00000000..928f187e
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+@ApiModel("管理后台 - 流程实例的取消 Request VO")
+@Data
+public class BpmProcessInstanceCancelReqVO {
+
+ @ApiModelProperty(value = "流程实例的编号", required = true, example = "1024")
+ @NotEmpty(message = "流程实例的编号不能为空")
+ private String id;
+
+ @ApiModelProperty(value = "取消原因", required = true, example = "不请假了!")
+ @NotEmpty(message = "取消原因不能为空")
+ private String reason;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java
new file mode 100644
index 00000000..2085253d
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import java.util.Map;
+
+@ApiModel("管理后台 - 流程实例的创建 Request VO")
+@Data
+public class BpmProcessInstanceCreateReqVO {
+
+ @ApiModelProperty(value = "流程定义的编号", required = true, example = "1024")
+ @NotEmpty(message = "流程定义编号不能为空")
+ private String processDefinitionId;
+
+ @ApiModelProperty(value = "变量实例")
+ private Map variables;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceMyPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceMyPageReqVO.java
new file mode 100644
index 00000000..e1e4327e
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceMyPageReqVO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 流程实例的分页 Item Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmProcessInstanceMyPageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "流程名称", example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "流程定义的编号", example = "2048")
+ private String processDefinitionId;
+
+ @ApiModelProperty(value = "流程实例的状态", notes = "参见 bpm_process_instance_status", example = "1")
+ private Integer status;
+
+ @ApiModelProperty(value = "流程实例的结果", notes = "参见 bpm_process_instance_result", example = "2")
+ private Integer result;
+
+ @ApiModelProperty(value = "流程分类", notes = "参见 bpm_model_category 数据字典", example = "1")
+ private String category;
+
+ @ApiModelProperty(value = "开始的创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date beginCreateTime;
+
+ @ApiModelProperty(value = "结束的创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date endCreateTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageItemRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageItemRespVO.java
new file mode 100644
index 00000000..5b5d5797
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageItemRespVO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+@ApiModel("管理后台 - 流程实例的分页 Item Response VO")
+@Data
+public class BpmProcessInstancePageItemRespVO {
+
+ @ApiModelProperty(value = "流程实例的编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "流程名称", required = true, example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "流程定义的编号", required = true, example = "2048")
+ private String processDefinitionId;
+
+ @ApiModelProperty(value = "流程分类", required = true, notes = "参见 bpm_model_category 数据字典", example = "1")
+ private String category;
+
+ @ApiModelProperty(value = "流程实例的状态", required = true, notes = "参见 bpm_process_instance_status", example = "1")
+ private Integer status;
+
+ @ApiModelProperty(value = "流程实例的结果", required = true, notes = "参见 bpm_process_instance_result", example = "2")
+ private Integer result;
+
+ @ApiModelProperty(value = "提交时间", required = true)
+ private Date createTime;
+
+ @ApiModelProperty(value = "结束时间", required = true)
+ private Date endTime;
+
+ /**
+ * 当前任务
+ */
+ private List tasks;
+
+ @ApiModel("流程任务")
+ @Data
+ public static class Task {
+
+ @ApiModelProperty(value = "流程任务的编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "任务名称", required = true, example = "芋道")
+ private String name;
+
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
new file mode 100644
index 00000000..a1547b17
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@ApiModel("管理后台 - 流程实例的 Response VO")
+@Data
+public class BpmProcessInstanceRespVO {
+
+ @ApiModelProperty(value = "流程实例的编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "流程名称", required = true, example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "流程分类", required = true, notes = "参见 bpm_model_category 数据字典", example = "1")
+ private String category;
+
+ @ApiModelProperty(value = "流程实例的状态", required = true, notes = "参见 bpm_process_instance_status", example = "1")
+ private Integer status;
+
+ @ApiModelProperty(value = "流程实例的结果", required = true, notes = "参见 bpm_process_instance_result", example = "2")
+ private Integer result;
+
+ @ApiModelProperty(value = "提交时间", required = true)
+ private Date createTime;
+
+ @ApiModelProperty(value = "结束时间", required = true)
+ private Date endTime;
+
+ @ApiModelProperty(value = "提交的表单值", required = true)
+ private Map formVariables;
+
+ @ApiModelProperty(value = "业务的唯一标识", example = "1", notes = "例如说,请假申请的编号")
+ private String businessKey;
+
+ /**
+ * 发起流程的用户
+ */
+ private User startUser;
+
+ /**
+ * 流程定义
+ */
+ private ProcessDefinition processDefinition;
+
+ @ApiModel("用户信息")
+ @Data
+ public static class User {
+
+ @ApiModelProperty(value = "用户编号", required = true, example = "1")
+ private Long id;
+ @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
+ private String nickname;
+
+ @ApiModelProperty(value = "部门编号", required = true, example = "1")
+ private Long deptId;
+ @ApiModelProperty(value = "部门名称", required = true, example = "研发部")
+ private String deptName;
+
+ }
+
+ @ApiModel("流程定义信息")
+ @Data
+ public static class ProcessDefinition {
+
+ @ApiModelProperty(value = "编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "表单类型", notes = "参见 bpm_model_form_type 数据字典", example = "1")
+ private Integer formType;
+ @ApiModelProperty(value = "表单编号", example = "1024", notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private Long formId;
+ @ApiModelProperty(value = "表单的配置", required = true,
+ notes = "JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formConf;
+ @ApiModelProperty(value = "表单项的数组", required = true,
+ notes = "JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private List formFields;
+ @ApiModelProperty(value = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create",
+ notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formCustomCreatePath;
+ @ApiModelProperty(value = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view",
+ notes = "在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空")
+ private String formCustomViewPath;
+
+ @ApiModelProperty(value = "BPMN XML", required = true)
+ private String bpmnXml;
+
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
new file mode 100644
index 00000000..6effcc8a
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+
+@ApiModel("管理后台 - 通过流程任务的 Request VO")
+@Data
+public class BpmTaskApproveReqVO {
+
+ @ApiModelProperty(value = "任务编号", required = true, example = "1024")
+ @NotEmpty(message = "任务编号不能为空")
+ private String id;
+
+ @ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
+ @NotEmpty(message = "审批意见不能为空")
+ private String comment;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDonePageItemRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDonePageItemRespVO.java
new file mode 100644
index 00000000..6d186557
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDonePageItemRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 流程任务的 Done 已完成的分页项 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmTaskDonePageItemRespVO extends BpmTaskTodoPageItemRespVO {
+
+ @ApiModelProperty(value = "结束时间", required = true)
+ private Date endTime;
+ @ApiModelProperty(value = "持续时间", required = true, example = "1000")
+ private Long durationInMillis;
+
+ @ApiModelProperty(value = "任务结果", required = true, notes = "参见 bpm_process_instance_result", example = "2")
+ private Integer result;
+ @ApiModelProperty(value = "审批建议", required = true, example = "不请假了!")
+ private String comment;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDonePageReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDonePageReqVO.java
new file mode 100644
index 00000000..99145b43
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskDonePageReqVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 流程任务的 Done 已办的分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmTaskDonePageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "流程任务名", example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "开始的创建收间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date beginCreateTime;
+
+ @ApiModelProperty(value = "结束的创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date endCreateTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java
new file mode 100644
index 00000000..7196718f
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+@ApiModel("管理后台 - 不通过流程任务的 Request VO")
+@Data
+public class BpmTaskRejectReqVO {
+
+ @ApiModelProperty(value = "任务编号", required = true, example = "1024")
+ @NotEmpty(message = "任务编号不能为空")
+ private String id;
+
+ @ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
+ @NotEmpty(message = "审批意见不能为空")
+ private String comment;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
new file mode 100644
index 00000000..9100eb3c
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@ApiModel("管理后台 - 流程任务的 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmTaskRespVO extends BpmTaskDonePageItemRespVO {
+
+ @ApiModelProperty(value = "任务定义的标识", required = true, example = "user-001")
+ private String definitionKey;
+
+ /**
+ * 审核的用户信息
+ */
+ private User assigneeUser;
+
+ @ApiModel("用户信息")
+ @Data
+ public static class User {
+
+ @ApiModelProperty(value = "用户编号", required = true, example = "1")
+ private Long id;
+ @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
+ private String nickname;
+
+ @ApiModelProperty(value = "部门编号", required = true, example = "1")
+ private Long deptId;
+ @ApiModelProperty(value = "部门名称", required = true, example = "研发部")
+ private String deptName;
+
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskTodoPageItemRespVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskTodoPageItemRespVO.java
new file mode 100644
index 00000000..e25e3aa4
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskTodoPageItemRespVO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@ApiModel("管理后台 - 流程任务的 Running 进行中的分页项 Response VO")
+@Data
+public class BpmTaskTodoPageItemRespVO {
+
+ @ApiModelProperty(value = "任务编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "任务名字", required = true, example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "接收时间", required = true)
+ private Date claimTime;
+
+ @ApiModelProperty(value = "创建时间", required = true)
+ private Date createTime;
+
+ @ApiModelProperty(value = "激活状态", required = true, example = "1", notes = "参见 SuspensionState 枚举")
+ private Integer suspensionState;
+
+ /**
+ * 所属流程实例
+ */
+ private ProcessInstance processInstance;
+
+ @Data
+ @ApiModel("流程实例")
+ public static class ProcessInstance {
+
+ @ApiModelProperty(value = "流程实例编号", required = true, example = "1024")
+ private String id;
+
+ @ApiModelProperty(value = "流程实例名称", required = true, example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "发起人的用户编号", required = true, example = "1024")
+ private Long startUserId;
+
+ @ApiModelProperty(value = "发起人的用户昵称", required = true, example = "芋艿")
+ private String startUserNickname;
+
+ @ApiModelProperty(value = "流程定义的编号", required = true, example = "2048")
+ private String processDefinitionId;
+
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskTodoPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskTodoPageReqVO.java
new file mode 100644
index 00000000..9f248e19
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskTodoPageReqVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@ApiModel("管理后台 - 流程任务的 TODO 待办的分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmTaskTodoPageReqVO extends PageParam {
+
+ @ApiModelProperty(value = "流程任务名", example = "芋道")
+ private String name;
+
+ @ApiModelProperty(value = "开始的创建收间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date beginCreateTime;
+
+ @ApiModelProperty(value = "结束的创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private Date endCreateTime;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskUpdateAssigneeReqVO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskUpdateAssigneeReqVO.java
new file mode 100644
index 00000000..0dd0ffed
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskUpdateAssigneeReqVO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import net.bytebuddy.implementation.bind.annotation.Empty;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 流程任务的更新负责人的 Request VO")
+@Data
+public class BpmTaskUpdateAssigneeReqVO {
+
+ @ApiModelProperty(value = "任务编号", required = true, example = "1024")
+ @NotEmpty(message = "任务编号不能为空")
+ private String id;
+
+ @ApiModelProperty(value = "新审批人的用户编号", required = true, example = "2048")
+ @NotNull(message = "新审批人的用户编号不能为空")
+ private Long assigneeUserId;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java
new file mode 100644
index 00000000..e8d285e3
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.bpm.controller.app;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java
new file mode 100644
index 00000000..d1930bd6
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java
@@ -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;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmFormConvert.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmFormConvert.java
new file mode 100644
index 00000000..6ba75741
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmFormConvert.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.bpm.convert.definition;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormSimpleRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 动态表单 Convert
+ *
+ * @author 芋艿
+ */
+@Mapper
+public interface BpmFormConvert {
+
+ BpmFormConvert INSTANCE = Mappers.getMapper(BpmFormConvert.class);
+
+ BpmFormDO convert(BpmFormCreateReqVO bean);
+
+ BpmFormDO convert(BpmFormUpdateReqVO bean);
+
+ BpmFormRespVO convert(BpmFormDO bean);
+
+ List convertList2(List list);
+
+ PageResult convertPage(PageResult page);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmUserGroupConvert.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmUserGroupConvert.java
new file mode 100644
index 00000000..bbf00ba5
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmUserGroupConvert.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.bpm.convert.definition;
+
+import java.util.*;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Named;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * 用户组 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface BpmUserGroupConvert {
+
+ BpmUserGroupConvert INSTANCE = Mappers.getMapper(BpmUserGroupConvert.class);
+
+ BpmUserGroupDO convert(BpmUserGroupCreateReqVO bean);
+
+ BpmUserGroupDO convert(BpmUserGroupUpdateReqVO bean);
+
+ BpmUserGroupRespVO convert(BpmUserGroupDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+ @Named("convertList2")
+ List convertList2(List list);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/message/BpmMessageConvert.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/message/BpmMessageConvert.java
new file mode 100644
index 00000000..92683f4c
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/message/BpmMessageConvert.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.bpm.convert.message;
+
+import cn.iocoder.yudao.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.Map;
+
+@Mapper
+public interface BpmMessageConvert {
+
+ BpmMessageConvert INSTANCE = Mappers.getMapper(BpmMessageConvert.class);
+
+ SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map templateParams);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/oa/BpmOALeaveConvert.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/oa/BpmOALeaveConvert.java
new file mode 100644
index 00000000..f87531bf
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/oa/BpmOALeaveConvert.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.bpm.convert.oa;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveRespVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 请假申请 Convert
+ *
+ * @author 芋艿
+ */
+@Mapper
+public interface BpmOALeaveConvert {
+
+ BpmOALeaveConvert INSTANCE = Mappers.getMapper(BpmOALeaveConvert.class);
+
+ BpmOALeaveDO convert(BpmOALeaveCreateReqVO bean);
+
+ BpmOALeaveRespVO convert(BpmOALeaveDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/package-info.java
new file mode 100644
index 00000000..6a53114a
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.bpm.convert.task;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmFormDO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmFormDO.java
new file mode 100644
index 00000000..76bf777e
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmFormDO.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * 工作流的表单定义
+ * 用于工作流的申请表单,需要动态配置的场景
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "bpm_form", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmFormDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 表单名
+ */
+ private String name;
+ /**
+ * 状态
+ */
+ private Integer status;
+ /**
+ * 表单的配置
+ */
+ private String conf;
+ /**
+ * 表单项的数组
+ *
+ * 目前直接将 https://github.com/JakHuang/form-generator 生成的 JSON 串,直接保存
+ * 定义:https://github.com/JakHuang/form-generator/issues/46
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List fields;
+ /**
+ * 备注
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionExtDO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionExtDO.java
new file mode 100644
index 00000000..57abc0b9
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionExtDO.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.*;
+
+import java.util.List;
+
+/**
+ * Bpm 流程定义的拓展表
+ * 主要解决 Activiti {@link ProcessDefinition} 不支持拓展字段,所以新建拓展表
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "bpm_process_definition_ext", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmProcessDefinitionExtDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 流程定义的编号
+ *
+ * 关联 ProcessDefinition 的 id 属性
+ */
+ private String processDefinitionId;
+ /**
+ * 流程模型的编号
+ *
+ * 关联 Model 的 id 属性
+ */
+ private String modelId;
+ /**
+ * 描述
+ */
+ private String description;
+
+ /**
+ * 表单类型
+ *
+ * 关联 {@link BpmModelFormTypeEnum}
+ */
+ private Integer formType;
+ /**
+ * 动态表单编号
+ * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
+ *
+ * 关联 {@link BpmFormDO#getId()}
+ */
+ private Long formId;
+ /**
+ * 表单的配置
+ * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
+ *
+ * 冗余 {@link BpmFormDO#getConf()}
+ */
+ private String formConf;
+ /**
+ * 表单项的数组
+ * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
+ *
+ * 冗余 {@link BpmFormDO#getFields()} ()}
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List formFields;
+ /**
+ * 自定义表单的提交路径,使用 Vue 的路由地址
+ * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
+ */
+ private String formCustomCreatePath;
+ /**
+ * 自定义表单的查看路径,使用 Vue 的路由地址
+ * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
+ */
+ private String formCustomViewPath;
+
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmTaskAssignRuleDO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmTaskAssignRuleDO.java
new file mode 100644
index 00000000..e65764f1
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmTaskAssignRuleDO.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.util.Set;
+
+/**
+ * Bpm 任务分配的规则表,用于自定义配置每个任务的负责人、候选人的分配规则。
+ * 也就是说,废弃 BPMN 原本的 UserTask 设置的 assignee、candidateUsers 等配置,而是通过使用该规则进行计算对应的负责人。
+ *
+ * 1. 默认情况下,{@link #processDefinitionId} 为 {@link #PROCESS_DEFINITION_ID_NULL} 值,表示贵改则与流程模型关联
+ * 2. 在流程模型部署后,会将他的所有规则记录,复制出一份新部署出来的流程定义,通过设置 {@link #processDefinitionId} 为新的流程定义的编号进行关联
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "bpm_task_assign_rule", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmTaskAssignRuleDO extends BaseDO {
+
+ /**
+ * {@link #processDefinitionId} 空串,用于标识属于流程模型,而不属于流程定义
+ */
+ public static final String PROCESS_DEFINITION_ID_NULL = "";
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 流程模型编号
+ *
+ * 关联 Model 的 id 属性
+ */
+ private String modelId;
+ /**
+ * 流程定义编号
+ *
+ * 关联 ProcessDefinition 的 id 属性
+ */
+ private String processDefinitionId;
+ /**
+ * 流程任务的定义 Key
+ *
+ * 关联 Task 的 taskDefinitionKey 属性
+ */
+ private String taskDefinitionKey;
+
+ /**
+ * 规则类型
+ *
+ * 枚举 {@link BpmTaskAssignRuleTypeEnum}
+ */
+ @TableField("`type`")
+ private Integer type;
+ /**
+ * 规则值数组,一般关联指定表的编号
+ * 根据 type 不同,对应的值是不同的:
+ *
+ * 1. {@link BpmTaskAssignRuleTypeEnum#ROLE} 时:角色编号
+ * 2. {@link BpmTaskAssignRuleTypeEnum#DEPT_MEMBER} 时:部门编号
+ * 3. {@link BpmTaskAssignRuleTypeEnum#DEPT_LEADER} 时:部门编号
+ * 4. {@link BpmTaskAssignRuleTypeEnum#USER} 时:用户编号
+ * 5. {@link BpmTaskAssignRuleTypeEnum#USER_GROUP} 时:用户组编号
+ * 6. {@link BpmTaskAssignRuleTypeEnum#SCRIPT} 时:脚本编号,目前通过 {@link BpmTaskRuleScriptEnum#getId()} 标识
+ */
+ @TableField(typeHandler = JsonLongSetTypeHandler.class)
+ private Set options;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmTaskMessageRuleDO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmTaskMessageRuleDO.java
new file mode 100644
index 00000000..db204c02
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmTaskMessageRuleDO.java
@@ -0,0 +1,5 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
+
+// TODO 芋艿:先埋个坑。任务消息的配置规则。说白了,就是不同的
+public class BpmTaskMessageRuleDO {
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java
new file mode 100644
index 00000000..ec116818
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.util.Set;
+
+/**
+ * Bpm 用户组
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "bpm_user_group", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmUserGroupDO extends BaseDO {
+
+ /**
+ * 编号,自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * 组名
+ */
+ private String name;
+ /**
+ * 描述
+ */
+ private String description;
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 成员用户编号数组
+ */
+ @TableField(typeHandler = JsonLongSetTypeHandler.class)
+ private Set memberUserIds;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java
new file mode 100644
index 00000000..31381a55
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.oa;
+
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
+import lombok.*;
+import java.util.*;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * OA 请假申请 DO
+ *
+ * {@link #day} 请假天数,目前先简单做。一般是分成请假上午和下午,可以是 1 整天,可以是 0.5 半天
+ *
+ * @author jason
+ * @author 芋道源码
+ */
+@TableName("bpm_oa_leave")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmOALeaveDO extends BaseDO {
+
+ /**
+ * 请假表单主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 申请人的用户编号
+ *
+ * 关联 AdminUserDO 的 id 属性
+ */
+ private Long userId;
+ /**
+ * 请假类型
+ */
+ @TableField("`type`")
+ private String type;
+ /**
+ * 原因
+ */
+ private String reason;
+ /**
+ * 开始时间
+ */
+ private Date startTime;
+ /**
+ * 结束时间
+ */
+ private Date endTime;
+ /**
+ * 请假天数
+ */
+ private Long day;
+ /**
+ * 请假的结果
+ *
+ * 枚举 {@link BpmProcessInstanceResultEnum}
+ * 考虑到简单,所以直接复用了 BpmProcessInstanceResultEnum 枚举,也可以自己定义一个枚举哈
+ */
+ private Integer result;
+
+ /**
+ * 对应的流程编号
+ *
+ * 关联 ProcessInstance 的 id 属性
+ */
+ private String processInstanceId;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java
new file mode 100644
index 00000000..b3770996
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.task;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * Bpm 流程实例的拓展表
+ * 主要解决 Activiti ProcessInstance 和 HistoricProcessInstance 不支持拓展字段,所以新建拓展表
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "bpm_process_instance_ext", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmProcessInstanceExtDO extends BaseDO {
+
+ /**
+ * 编号,自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * 发起流程的用户编号
+ *
+ * 冗余 HistoricProcessInstance 的 startUserId 属性
+ */
+ private Long startUserId;
+ /**
+ * 流程实例的名字
+ *
+ * 冗余 ProcessInstance 的 name 属性,用于筛选
+ */
+ private String name;
+ /**
+ * 流程实例的编号
+ *
+ * 关联 ProcessInstance 的 id 属性
+ */
+ private String processInstanceId;
+ /**
+ * 流程定义的编号
+ *
+ * 关联 ProcessDefinition 的 id 属性
+ */
+ private String processDefinitionId;
+ /**
+ * 流程分类
+ *
+ * 冗余 ProcessDefinition 的 category 属性
+ * 数据字典 bpm_model_category
+ */
+ private String category;
+ /**
+ * 流程实例的状态
+ *
+ * 枚举 {@link BpmProcessInstanceStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 流程实例的结果
+ *
+ * 枚举 {@link BpmProcessInstanceResultEnum}
+ */
+ private Integer result;
+ /**
+ * 结束时间
+ *
+ * 冗余 HistoricProcessInstance 的 endTime 属性
+ */
+ private Date endTime;
+
+ /**
+ * 提交的表单值
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private Map formVariables;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmTaskExtDO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmTaskExtDO.java
new file mode 100644
index 00000000..780ac505
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmTaskExtDO.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.task;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Date;
+
+/**
+ * Bpm 流程任务的拓展表
+ * 主要解决 Activiti Task 和 HistoricTaskInstance 不支持拓展字段,所以新建拓展表
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "bpm_task_ext", autoResultMap = true)
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmTaskExtDO extends BaseDO {
+
+ /**
+ * 编号,自增
+ */
+ @TableId
+ private Long id;
+ /**
+ * 任务的审批人
+ *
+ * 冗余 Task 的 assignee 属性
+ */
+ private Long assigneeUserId;
+ /**
+ * 任务的名字
+ *
+ * 冗余 Task 的 name 属性,为了筛选
+ */
+ private String name;
+ /**
+ * 任务的编号
+ *
+ * 关联 Task 的 id 属性
+ */
+ private String taskId;
+// /**
+// * 任务的标识
+// *
+// * 关联 {@link Task#getTaskDefinitionKey()}
+// */
+// private String definitionKey;
+ /**
+ * 任务的结果
+ *
+ * 枚举 {@link BpmProcessInstanceResultEnum}
+ */
+ private Integer result;
+ /**
+ * 审批建议
+ */
+ private String comment;
+ /**
+ * 任务的结束时间
+ *
+ * 冗余 HistoricTaskInstance 的 endTime 属性
+ */
+ private Date endTime;
+
+ /**
+ * 流程实例的编号
+ *
+ * 关联 ProcessInstance 的 id 属性
+ */
+ private String processInstanceId;
+ /**
+ * 流程定义的编号
+ *
+ * 关联 ProcessDefinition 的 id 属性
+ */
+ private String processDefinitionId;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmFormMapper.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmFormMapper.java
new file mode 100644
index 00000000..53c01d92
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmFormMapper.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
+
+
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 动态表单 Mapper
+ *
+ * @author 风里雾里
+ */
+@Mapper
+public interface BpmFormMapper extends BaseMapperX {
+
+ default PageResult selectPage(BpmFormPageReqVO reqVO) {
+ return selectPage(reqVO, new QueryWrapperX()
+ .likeIfPresent("name", reqVO.getName())
+ .orderByDesc("id"));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionExtMapper.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionExtMapper.java
new file mode 100644
index 00000000..3ff53f2d
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmProcessDefinitionExtMapper.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
+
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+@Mapper
+public interface BpmProcessDefinitionExtMapper extends BaseMapperX {
+
+ default List selectListByProcessDefinitionIds(Collection processDefinitionIds) {
+ return selectList("process_definition_id", processDefinitionIds);
+ }
+
+ default BpmProcessDefinitionExtDO selectByProcessDefinitionId(String processDefinitionId) {
+ return selectOne("process_definition_id", processDefinitionId);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmTaskAssignRuleMapper.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmTaskAssignRuleMapper.java
new file mode 100644
index 00000000..1205d30e
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmTaskAssignRuleMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
+
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.lang.Nullable;
+
+import java.util.List;
+
+@Mapper
+public interface BpmTaskAssignRuleMapper extends BaseMapperX {
+
+ default List selectListByProcessDefinitionId(String processDefinitionId,
+ @Nullable String taskDefinitionKey) {
+ return selectList(new QueryWrapperX()
+ .eq("process_definition_id", processDefinitionId)
+ .eqIfPresent("task_definition_key", taskDefinitionKey));
+ }
+
+ default List selectListByModelId(String modelId) {
+ return selectList(new QueryWrapperX()
+ .eq("model_id", modelId)
+ .eq("process_definition_id", BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL));
+ }
+
+ default BpmTaskAssignRuleDO selectListByModelIdAndTaskDefinitionKey(String modelId,
+ String taskDefinitionKey) {
+ return selectOne(new QueryWrapperX()
+ .eq("model_id", modelId)
+ .eq("process_definition_id", BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL)
+ .eq("task_definition_key", taskDefinitionKey));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java
new file mode 100644
index 00000000..982e678b
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 用户组 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface BpmUserGroupMapper extends BaseMapperX {
+
+ default PageResult selectPage(BpmUserGroupPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(BpmUserGroupDO::getName, reqVO.getName())
+ .eqIfPresent(BpmUserGroupDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(BpmUserGroupDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+ .orderByDesc(BpmUserGroupDO::getId));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(BpmUserGroupDO::getStatus, status);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java
new file mode 100644
index 00000000..ba781e35
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.oa;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 请假申请 Mapper
+ *
+ * @author jason
+ * @author 芋道源码
+ */
+@Mapper
+public interface BpmOALeaveMapper extends BaseMapperX {
+
+ default PageResult selectPage(Long userId, BpmOALeavePageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(BpmOALeaveDO::getUserId, userId)
+ .eqIfPresent(BpmOALeaveDO::getResult, reqVO.getResult())
+ .eqIfPresent(BpmOALeaveDO::getType, reqVO.getType())
+ .likeIfPresent(BpmOALeaveDO::getReason, reqVO.getReason())
+ .betweenIfPresent(BpmOALeaveDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+ .orderByDesc(BpmOALeaveDO::getId));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceExtMapper.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceExtMapper.java
new file mode 100644
index 00000000..ee31b08c
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmProcessInstanceExtMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.task;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceMyPageReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface BpmProcessInstanceExtMapper extends BaseMapperX {
+
+ default PageResult selectPage(Long userId, BpmProcessInstanceMyPageReqVO reqVO) {
+ return selectPage(reqVO, new QueryWrapperX()
+ .eqIfPresent("start_user_id", userId)
+ .likeIfPresent("name", reqVO.getName())
+ .eqIfPresent("process_definition_id", reqVO.getProcessDefinitionId())
+ .eqIfPresent("category", reqVO.getCategory())
+ .eqIfPresent("status", reqVO.getStatus())
+ .eqIfPresent("result", reqVO.getResult())
+ .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
+ .orderByDesc("id"));
+ }
+
+ default BpmProcessInstanceExtDO selectByProcessInstanceId(String processDefinitionId) {
+ return selectOne("process_instance_id", processDefinitionId);
+ }
+
+ default void updateByProcessInstanceId(BpmProcessInstanceExtDO updateObj) {
+ update(updateObj, new QueryWrapper()
+ .eq("process_instance_id", updateObj.getProcessInstanceId()));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java
new file mode 100644
index 00000000..b2927a65
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.task;
+
+import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+@Mapper
+public interface BpmTaskExtMapper extends BaseMapperX {
+
+ default void updateByTaskId(BpmTaskExtDO entity) {
+ update(entity, new LambdaQueryWrapper().eq(BpmTaskExtDO::getTaskId, entity.getTaskId()));
+ }
+
+ default List selectListByTaskIds(Collection taskIds) {
+ return selectList(BpmTaskExtDO::getTaskId, taskIds);
+ }
+
+ default List selectListByProcessInstanceId(String processInstanceId) {
+ return selectList("process_instance_id", processInstanceId);
+ }
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelFormTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelFormTypeEnum.java
new file mode 100644
index 00000000..c21d6f99
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmModelFormTypeEnum.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * BPM 模型的表单类型的枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmModelFormTypeEnum {
+
+ NORMAL(10, "流程表单"), // 对应 BpmFormDO
+ CUSTOM(20, "业务表单") // 业务自己定义的表单,自己进行数据的存储
+ ;
+
+ private final Integer type;
+ private final String desc;
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTaskAssignRuleTypeEnum.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTaskAssignRuleTypeEnum.java
new file mode 100644
index 00000000..de3c3516
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTaskAssignRuleTypeEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * BPM 任务分配规则的类型枚举
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmTaskAssignRuleTypeEnum {
+
+ ROLE(10, "角色"),
+
+ DEPT_MEMBER(20, "部门的成员"), // 包括负责人
+ DEPT_LEADER(21, "部门的负责人"),
+ POST(22, "岗位"),
+
+ USER(30, "用户"),
+
+ USER_GROUP(40, "用户组"),
+
+ SCRIPT(50, "自定义脚本"), // 例如说,发起人所在部门的领导、发起人所在部门的领导的领导
+ ;
+
+ /**
+ * 类型
+ */
+ private final Integer type;
+ /**
+ * 描述
+ */
+ private final String desc;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTaskRuleScriptEnum.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTaskRuleScriptEnum.java
new file mode 100644
index 00000000..7fc0e3b8
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmTaskRuleScriptEnum.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.bpm.enums.definition;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * BPM 任务规则的脚本枚举
+ * 目前暂时通过 TODO 芋艿:硬编码,未来可以考虑 Groovy 动态脚本的方式
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmTaskRuleScriptEnum {
+
+ START_USER(10L, "流程发起人"),
+
+ LEADER_X1(20L, "流程发起人的一级领导"),
+ LEADER_X2(21L, "流程发起人的二级领导");
+
+ /**
+ * 脚本编号
+ */
+ private final Long id;
+ /**
+ * 脚本描述
+ */
+ private final String desc;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java
new file mode 100644
index 00000000..79001fcc
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/message/BpmMessageEnum.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.bpm.enums.message;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Bpm 消息的枚举
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum BpmMessageEnum {
+
+ PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人
+ PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人
+ TASK_ASSIGNED("bpm_task_assigned"); // 任务被分配时,发送给审批人
+
+ /**
+ * 短信模板的标识
+ *
+ * 关联 SmsTemplateDO 的 code 属性
+ */
+ private final String smsTemplateCode;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceDeleteReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceDeleteReasonEnum.java
new file mode 100644
index 00000000..e41930bb
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceDeleteReasonEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.bpm.enums.task;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 流程实例的删除原因
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmProcessInstanceDeleteReasonEnum {
+
+ REJECT_TASK("不通过任务,原因:{}"), // 修改文案时,需要注意 isRejectReason 方法
+ CANCEL_TASK("主动取消任务,原因:{}");
+
+ private final String reason;
+
+ /**
+ * 格式化理由
+ *
+ * @param args 参数
+ * @return 理由
+ */
+ public String format(Object... args) {
+ return StrUtil.format(reason, args);
+ }
+
+ // ========== 逻辑 ==========
+
+ public static boolean isRejectReason(String reason) {
+ return StrUtil.startWith(reason, "不通过任务,原因:");
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java
new file mode 100644
index 00000000..39bf22ad
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.bpm.enums.task;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 流程实例的结果
+ *
+ * @author jason
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmProcessInstanceResultEnum {
+
+ PROCESS(1, "处理中"),
+ APPROVE(2, "通过"),
+ REJECT(3, "不通过"),
+ CANCEL(4, "已取消");
+
+ /**
+ * 结果
+ */
+ private final Integer result;
+ /**
+ * 描述
+ */
+ private final String desc;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java
new file mode 100644
index 00000000..70a31dd3
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceStatusEnum.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.bpm.enums.task;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 流程实例的状态
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum BpmProcessInstanceStatusEnum {
+
+ RUNNING(1, "进行中"),
+ FINISH(2, "已完成");
+
+ /**
+ * 状态
+ */
+ private final Integer status;
+ /**
+ * 描述
+ */
+ private final String desc;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCommonConfiguration.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCommonConfiguration.java
new file mode 100644
index 00000000..8915fc6b
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmCommonConfiguration.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.bpm.framework.bpm.config;
+
+import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * BPM 通用的 Configuration 配置类,提供给 Activiti 和 Flowable
+ */
+@Configuration
+public class BpmCommonConfiguration {
+
+ @Bean
+ public BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher(ApplicationEventPublisher publisher) {
+ return new BpmProcessInstanceResultEventPublisher(publisher);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEvent.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEvent.java
new file mode 100644
index 00000000..9fccbab8
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEvent.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.bpm.framework.bpm.core.event;
+
+import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
+import lombok.Data;
+import org.springframework.context.ApplicationEvent;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 流程实例的结果发生变化的 Event
+ * 定位:由于额外增加了 {@link BpmProcessInstanceExtDO#getResult()} 结果,所以增加该事件
+ *
+ * @author 芋道源码
+ */
+@SuppressWarnings("ALL")
+@Data
+public class BpmProcessInstanceResultEvent extends ApplicationEvent {
+
+ /**
+ * 流程实例的编号
+ */
+ @NotNull(message = "流程实例的编号不能为空")
+ private String id;
+ /**
+ * 流程实例的 key
+ */
+ @NotNull(message = "流程实例的 key 不能为空")
+ private String processDefinitionKey;
+ /**
+ * 流程实例的结果
+ */
+ @NotNull(message = "流程实例的结果不能为空")
+ private Integer result;
+ /**
+ * 流程实例对应的业务标识
+ * 例如说,请假
+ */
+ private String businessKey;
+
+ public BpmProcessInstanceResultEvent(Object source) {
+ super(source);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventListener.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventListener.java
new file mode 100644
index 00000000..c2b21539
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventListener.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.bpm.framework.bpm.core.event;
+
+import cn.hutool.core.util.StrUtil;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * {@link BpmProcessInstanceResultEvent} 的监听器
+ *
+ * @author 芋道源码
+ */
+public abstract class BpmProcessInstanceResultEventListener
+ implements ApplicationListener {
+
+ @Override
+ public final void onApplicationEvent(BpmProcessInstanceResultEvent event) {
+ if (!StrUtil.equals(event.getProcessDefinitionKey(), getProcessDefinitionKey())) {
+ return;
+ }
+ onEvent(event);
+ }
+
+ /**
+ * @return 返回监听的流程定义 Key
+ */
+ protected abstract String getProcessDefinitionKey();
+
+ /**
+ * 处理事件
+ *
+ * @param event 事件
+ */
+ protected abstract void onEvent(BpmProcessInstanceResultEvent event);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventPublisher.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventPublisher.java
new file mode 100644
index 00000000..dc5c21b7
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventPublisher.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.bpm.framework.bpm.core.event;
+
+import lombok.AllArgsConstructor;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.Valid;
+
+/**
+ * {@link BpmProcessInstanceResultEvent} 的生产者
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Validated
+public class BpmProcessInstanceResultEventPublisher {
+
+ private final ApplicationEventPublisher publisher;
+
+ public void sendProcessInstanceResultEvent(@Valid BpmProcessInstanceResultEvent event) {
+ publisher.publishEvent(event);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/package-info.java
new file mode 100644
index 00000000..c4a13112
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 自定义 Event 实现,提供方便业务接入的 Listener!
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.bpm.framework.bpm.core.event;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/package-info.java
new file mode 100644
index 00000000..b97cb4c8
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.bpm.framework.bpm.core;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/package-info.java
new file mode 100644
index 00000000..9a5e3ea9
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 提供给 Activiti 和 Flowable 的通用封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.bpm.framework.bpm;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java
new file mode 100644
index 00000000..52fdb7f9
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 属于 bpm 模块的 framework 封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.bpm.framework;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java
new file mode 100644
index 00000000..6d399cf2
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * bpm 包下,业务流程管理(Business Process Management),我们放工作流的功能。
+ * 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等
+ *
+ * bpm 解释:https://baike.baidu.com/item/BPM/1933
+ *
+ * 1. Controller URL:以 /bpm/ 开头,避免和其它 Module 冲突
+ * 2. DataObject 表名:以 bpm_ 开头,方便在数据库中区分
+ */
+package cn.iocoder.yudao.module.bpm;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormService.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormService.java
new file mode 100644
index 00000000..80355ef5
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormService.java
@@ -0,0 +1,99 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * 动态表单 Service 接口
+ *
+ * @author @风里雾里
+ */
+public interface BpmFormService {
+
+ /**
+ * 创建动态表单
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createForm(@Valid BpmFormCreateReqVO createReqVO);
+
+ /**
+ * 更新动态表单
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateForm(@Valid BpmFormUpdateReqVO updateReqVO);
+
+ /**
+ * 删除动态表单
+ *
+ * @param id 编号
+ */
+ void deleteForm(Long id);
+
+ /**
+ * 获得动态表单
+ *
+ * @param id 编号
+ * @return 动态表单
+ */
+ BpmFormDO getForm(Long id);
+
+ /**
+ * 获得动态表单列表
+ *
+ * @return 动态表单列表
+ */
+ List getFormList();
+
+ /**
+ * 获得动态表单列表
+ *
+ * @param ids 编号
+ * @return 动态表单列表
+ */
+ List getFormList(Collection ids);
+
+ /**
+ * 获得动态表单 Map
+ *
+ * @param ids 编号
+ * @return 动态表单 Map
+ */
+ default Map getFormMap(Collection ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyMap();
+ }
+ return CollectionUtils.convertMap(this.getFormList(ids), BpmFormDO::getId);
+ }
+
+ /**
+ * 获得动态表单分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 动态表单分页
+ */
+ PageResult getFormPage(BpmFormPageReqVO pageReqVO);
+
+ /**
+ * 校验流程表单已配置
+ *
+ * @param configStr configStr 字段
+ * @return 流程表单
+ */
+ BpmFormDO checkFormConfig(String configStr);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceImpl.java
new file mode 100644
index 00000000..d64684b8
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceImpl.java
@@ -0,0 +1,136 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.convert.definition.BpmFormConvert;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmFormMapper;
+import cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmFormFieldRespDTO;
+import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
+
+/**
+ * 动态表单 Service 实现类
+ *
+ * @author 风里雾里
+ */
+@Service
+@Validated
+public class BpmFormServiceImpl implements BpmFormService {
+
+ @Resource
+ private BpmFormMapper formMapper;
+
+ @Override
+ public Long createForm(BpmFormCreateReqVO createReqVO) {
+ this.checkFields(createReqVO.getFields());
+ // 插入
+ BpmFormDO form = BpmFormConvert.INSTANCE.convert(createReqVO);
+ formMapper.insert(form);
+ // 返回
+ return form.getId();
+ }
+
+ @Override
+ public void updateForm(BpmFormUpdateReqVO updateReqVO) {
+ this.checkFields(updateReqVO.getFields());
+ // 校验存在
+ this.validateFormExists(updateReqVO.getId());
+ // 更新
+ BpmFormDO updateObj = BpmFormConvert.INSTANCE.convert(updateReqVO);
+ formMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteForm(Long id) {
+ // 校验存在
+ this.validateFormExists(id);
+ // 删除
+ formMapper.deleteById(id);
+ }
+
+ private void validateFormExists(Long id) {
+ if (formMapper.selectById(id) == null) {
+ throw ServiceExceptionUtil.exception(ErrorCodeConstants.FORM_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public BpmFormDO getForm(Long id) {
+ return formMapper.selectById(id);
+ }
+
+ @Override
+ public List getFormList() {
+ return formMapper.selectList();
+ }
+
+ @Override
+ public List getFormList(Collection ids) {
+ return formMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public PageResult getFormPage(BpmFormPageReqVO pageReqVO) {
+ return formMapper.selectPage(pageReqVO);
+ }
+
+
+ @Override
+ public BpmFormDO checkFormConfig(String configStr) {
+ BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(configStr, 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 = getForm(metaInfo.getFormId());
+ if (form == null) {
+ throw exception(FORM_NOT_EXISTS);
+ }
+ return form;
+ }
+ return null;
+ }
+
+ private void checkKeyNCName(String key) {
+ if (!ValidationUtils.isXmlNCName(key)) {
+ throw exception(MODEL_KEY_VALID);
+ }
+ }
+ /**
+ * 校验 Field,避免 field 重复
+ *
+ * @param fields field 数组
+ */
+ private void checkFields(List fields) {
+ Map fieldMap = new HashMap<>(); // key 是 vModel,value 是 label
+ for (String field : fields) {
+ BpmFormFieldRespDTO fieldDTO = JsonUtils.parseObject(field, BpmFormFieldRespDTO.class);
+ Assert.notNull(fieldDTO);
+ String oldLabel = fieldMap.put(fieldDTO.getVModel(), fieldDTO.getLabel());
+ // 如果不存在,则直接返回
+ if (oldLabel == null) {
+ continue;
+ }
+ // 如果存在,则报错
+ throw ServiceExceptionUtil.exception(ErrorCodeConstants.FORM_FIELD_REPEAT, oldLabel, fieldDTO.getLabel(), fieldDTO.getVModel());
+ }
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupService.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupService.java
new file mode 100644
index 00000000..da185105
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupService.java
@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import java.util.*;
+import javax.validation.*;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+
+/**
+ * 用户组 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface BpmUserGroupService {
+
+ /**
+ * 创建用户组
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createUserGroup(@Valid BpmUserGroupCreateReqVO createReqVO);
+
+ /**
+ * 更新用户组
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateUserGroup(@Valid BpmUserGroupUpdateReqVO updateReqVO);
+
+ /**
+ * 删除用户组
+ *
+ * @param id 编号
+ */
+ void deleteUserGroup(Long id);
+
+ /**
+ * 获得用户组
+ *
+ * @param id 编号
+ * @return 用户组
+ */
+ BpmUserGroupDO getUserGroup(Long id);
+
+ /**
+ * 获得用户组列表
+ *
+ * @param ids 编号
+ * @return 用户组列表
+ */
+ List getUserGroupList(Collection ids);
+
+ /**
+ * 获得指定状态的用户组列表
+ *
+ * @param status 状态
+ * @return 用户组列表
+ */
+ List getUserGroupListByStatus(Integer status);
+
+ /**
+ * 获得用户组分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 用户组分页
+ */
+ PageResult getUserGroupPage(BpmUserGroupPageReqVO pageReqVO);
+
+ /**
+ * 校验用户组们是否有效。如下情况,视为无效:
+ * 1. 用户组编号不存在
+ * 2. 用户组被禁用
+ *
+ * @param ids 用户组编号数组
+ */
+ void validUserGroups(Set ids);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceImpl.java
new file mode 100644
index 00000000..e6a93b45
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceImpl.java
@@ -0,0 +1,111 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.convert.definition.BpmUserGroupConvert;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmUserGroupMapper;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
+
+/**
+ * 用户组 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class BpmUserGroupServiceImpl implements BpmUserGroupService {
+
+ @Resource
+ private BpmUserGroupMapper userGroupMapper;
+
+ @Override
+ public Long createUserGroup(BpmUserGroupCreateReqVO createReqVO) {
+ // 插入
+ BpmUserGroupDO userGroup = BpmUserGroupConvert.INSTANCE.convert(createReqVO);
+ userGroupMapper.insert(userGroup);
+ // 返回
+ return userGroup.getId();
+ }
+
+ @Override
+ public void updateUserGroup(BpmUserGroupUpdateReqVO updateReqVO) {
+ // 校验存在
+ this.validateUserGroupExists(updateReqVO.getId());
+ // 更新
+ BpmUserGroupDO updateObj = BpmUserGroupConvert.INSTANCE.convert(updateReqVO);
+ userGroupMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteUserGroup(Long id) {
+ // 校验存在
+ this.validateUserGroupExists(id);
+ // 删除
+ userGroupMapper.deleteById(id);
+ }
+
+ private void validateUserGroupExists(Long id) {
+ if (userGroupMapper.selectById(id) == null) {
+ throw ServiceExceptionUtil.exception(USER_GROUP_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public BpmUserGroupDO getUserGroup(Long id) {
+ return userGroupMapper.selectById(id);
+ }
+
+ @Override
+ public List getUserGroupList(Collection ids) {
+ return userGroupMapper.selectBatchIds(ids);
+ }
+
+
+ @Override
+ public List getUserGroupListByStatus(Integer status) {
+ return userGroupMapper.selectListByStatus(status);
+ }
+
+ @Override
+ public PageResult getUserGroupPage(BpmUserGroupPageReqVO pageReqVO) {
+ return userGroupMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public void validUserGroups(Set ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return;
+ }
+ // 获得用户组信息
+ List userGroups = userGroupMapper.selectBatchIds(ids);
+ Map userGroupMap = CollectionUtils.convertMap(userGroups, BpmUserGroupDO::getId);
+ // 校验
+ ids.forEach(id -> {
+ BpmUserGroupDO userGroup = userGroupMap.get(id);
+ if (userGroup == null) {
+ throw ServiceExceptionUtil.exception(USER_GROUP_NOT_EXISTS);
+ }
+ if (!CommonStatusEnum.ENABLE.getStatus().equals(userGroup.getStatus())) {
+ throw exception(USER_GROUP_IS_DISABLE, userGroup.getName());
+ }
+ });
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmFormFieldRespDTO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmFormFieldRespDTO.java
new file mode 100644
index 00000000..1606192c
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmFormFieldRespDTO.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.bpm.service.definition.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * Bpm 表单的 Field 表单项 Response DTO
+ * 字段的定义,可见 https://github.com/JakHuang/form-generator/issues/46 文档
+ *
+ * @author 芋道源码
+ */
+@Data
+public class BpmFormFieldRespDTO {
+
+ /**
+ * 表单标题
+ */
+ private String label;
+ /**
+ * 表单字段的属性名,可自定义
+ */
+ @JsonProperty(value = "vModel")
+ private String vModel;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java
new file mode 100644
index 00000000..3a36b0ee
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.bpm.service.definition.dto;
+
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import lombok.Data;
+
+/**
+ * BPM 流程 MetaInfo Response DTO
+ * 主要用于 { Model#setMetaInfo(String)} 的存储
+ *
+ * @author 芋道源码
+ */
+@Data
+public class BpmModelMetaInfoRespDTO {
+
+ /**
+ * 流程描述
+ */
+ private String description;
+ /**
+ * 表单类型
+ */
+ private Integer formType;
+ /**
+ * 表单编号
+ * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
+ */
+ private Long formId;
+ /**
+ * 自定义表单的提交路径,使用 Vue 的路由地址
+ * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
+ */
+ private String formCustomCreatePath;
+ /**
+ * 自定义表单的查看路径,使用 Vue 的路由地址
+ * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
+ */
+ private String formCustomViewPath;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmProcessDefinitionCreateReqDTO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmProcessDefinitionCreateReqDTO.java
new file mode 100644
index 00000000..8b2d22ad
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmProcessDefinitionCreateReqDTO.java
@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.bpm.service.definition.dto;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import lombok.Data;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 流程定义创建 Request DTO
+ */
+@Data
+public class BpmProcessDefinitionCreateReqDTO {
+
+ // ========== 模型相关 ==========
+
+ /**
+ * 流程模型的编号
+ */
+ @NotEmpty(message = "流程模型编号不能为空")
+ private String modelId;
+ /**
+ * 流程标识
+ */
+ @NotEmpty(message = "流程标识不能为空")
+ private String key;
+ /**
+ * 流程名称
+ */
+ @NotEmpty(message = "流程名称不能为空")
+ private String name;
+ /**
+ * 流程描述
+ */
+ private String description;
+ /**
+ * 流程分类
+ * 参见 bpm_model_category 数据字典
+ */
+ @NotEmpty(message = "流程分类不能为空")
+ private String category;
+ /**
+ * BPMN XML
+ */
+ @NotEmpty(message = "BPMN XML 不能为空")
+ private byte[] bpmnBytes;
+
+ // ========== 表单相关 ==========
+
+ /**
+ * 表单类型
+ */
+ @NotNull(message = "表单类型不能为空")
+ private Integer formType;
+ /**
+ * 动态表单编号
+ * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
+ */
+ private Long formId;
+ /**
+ * 表单的配置
+ * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
+ */
+ private String formConf;
+ /**
+ * 表单项的数组
+ * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
+ */
+ private List formFields;
+ /**
+ * 自定义表单的提交路径,使用 Vue 的路由地址
+ * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
+ */
+ private String formCustomCreatePath;
+ /**
+ * 自定义表单的查看路径,使用 Vue 的路由地址
+ * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
+ */
+ private String formCustomViewPath;
+
+ @AssertTrue(message = "流程表单信息不全")
+ public boolean isNormalFormTypeValid() {
+ // 如果非业务表单,则直接通过
+ if (!Objects.equals(formType, BpmModelFormTypeEnum.NORMAL.getType())) {
+ return true;
+ }
+ return formId != null && StrUtil.isNotEmpty(formConf) && CollUtil.isNotEmpty(formFields);
+ }
+
+ @AssertTrue(message = "业务表单信息不全")
+ public boolean isNormalCustomTypeValid() {
+ // 如果非业务表单,则直接通过
+ if (!Objects.equals(formType, BpmModelFormTypeEnum.CUSTOM.getType())) {
+ return true;
+ }
+ return StrUtil.isNotEmpty(formCustomCreatePath) && StrUtil.isNotEmpty(formCustomViewPath);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java
new file mode 100644
index 00000000..ff51ae30
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageService.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.bpm.service.message;
+
+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.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
+
+import javax.validation.Valid;
+
+/**
+ * BPM 消息 Service 接口
+ *
+ * TODO 芋艿:未来支持消息的可配置;不同的流程,在什么场景下,需要发送什么消息,消息的内容是什么;
+ *
+ * @author 芋道源码
+ */
+public interface BpmMessageService {
+
+ /**
+ * 发送流程实例被通过的消息
+ *
+ * @param reqDTO 发送信息
+ */
+ void sendMessageWhenProcessInstanceApprove(@Valid BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO);
+
+ /**
+ * 发送流程实例被不通过的消息
+ *
+ * @param reqDTO 发送信息
+ */
+ void sendMessageWhenProcessInstanceReject(@Valid BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO);
+
+ /**
+ * 发送任务被分配的消息
+ *
+ * @param reqDTO 发送信息
+ */
+ void sendMessageWhenTaskAssigned(@Valid BpmMessageSendWhenTaskCreatedReqDTO reqDTO);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
new file mode 100644
index 00000000..61b3c57d
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.bpm.service.message;
+
+import cn.iocoder.yudao.framework.web.config.WebProperties;
+import cn.iocoder.yudao.module.bpm.convert.message.BpmMessageConvert;
+import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum;
+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.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
+import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * BPM 消息 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+@Slf4j
+public class BpmMessageServiceImpl implements BpmMessageService {
+
+ @Resource
+ private SmsSendApi smsSendApi;
+
+ @Resource
+ private WebProperties webProperties;
+
+ @Override
+ public void sendMessageWhenProcessInstanceApprove(BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO) {
+ Map templateParams = new HashMap<>();
+ templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
+ templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
+ smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
+ BpmMessageEnum.PROCESS_INSTANCE_APPROVE.getSmsTemplateCode(), templateParams));
+ }
+
+ @Override
+ public void sendMessageWhenProcessInstanceReject(BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO) {
+ Map templateParams = new HashMap<>();
+ templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
+ templateParams.put("comment", reqDTO.getComment());
+ templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
+ smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
+ BpmMessageEnum.PROCESS_INSTANCE_REJECT.getSmsTemplateCode(), templateParams));
+ }
+
+ @Override
+ public void sendMessageWhenTaskAssigned(BpmMessageSendWhenTaskCreatedReqDTO reqDTO) {
+ Map templateParams = new HashMap<>();
+ templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
+ templateParams.put("taskName", reqDTO.getTaskName());
+ templateParams.put("startUserNickname", reqDTO.getStartUserNickname());
+ templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
+ smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
+ BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams));
+ }
+
+ private String getProcessInstanceDetailUrl(String taskId) {
+ return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceApproveReqDTO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceApproveReqDTO.java
new file mode 100644
index 00000000..7c37734f
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceApproveReqDTO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.bpm.service.message.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * BPM 发送流程实例被通过 Request DTO
+ */
+@Data
+public class BpmMessageSendWhenProcessInstanceApproveReqDTO {
+
+ /**
+ * 流程实例的编号
+ */
+ @NotEmpty(message = "流程实例的编号不能为空")
+ private String processInstanceId;
+ /**
+ * 流程实例的名字
+ */
+ @NotEmpty(message = "流程实例的名字不能为空")
+ private String processInstanceName;
+ @NotNull(message = "发起人的用户编号")
+ private Long startUserId;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceRejectReqDTO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceRejectReqDTO.java
new file mode 100644
index 00000000..0194d03a
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenProcessInstanceRejectReqDTO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.bpm.service.message.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * BPM 发送流程实例被不通过 Request DTO
+ */
+@Data
+public class BpmMessageSendWhenProcessInstanceRejectReqDTO {
+
+ /**
+ * 流程实例的编号
+ */
+ @NotEmpty(message = "流程实例的编号不能为空")
+ private String processInstanceId;
+ /**
+ * 流程实例的名字
+ */
+ @NotEmpty(message = "流程实例的名字不能为空")
+ private String processInstanceName;
+ @NotNull(message = "发起人的用户编号")
+ private Long startUserId;
+
+ /**
+ * 不通过理由
+ */
+ @NotEmpty(message = "不通过理由不能为空")
+ private String comment;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskCreatedReqDTO.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskCreatedReqDTO.java
new file mode 100644
index 00000000..7cb0e3d7
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/message/dto/BpmMessageSendWhenTaskCreatedReqDTO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.bpm.service.message.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * BPM 发送任务被分配 Request DTO
+ */
+@Data
+public class BpmMessageSendWhenTaskCreatedReqDTO {
+
+ /**
+ * 流程实例的编号
+ */
+ @NotEmpty(message = "流程实例的编号不能为空")
+ private String processInstanceId;
+ /**
+ * 流程实例的名字
+ */
+ @NotEmpty(message = "流程实例的名字不能为空")
+ private String processInstanceName;
+ @NotNull(message = "发起人的用户编号")
+ private Long startUserId;
+ @NotEmpty(message = "发起人的昵称")
+ private String startUserNickname;
+
+ /**
+ * 流程任务的编号
+ */
+ @NotEmpty(message = "流程任务的编号不能为空")
+ private String taskId;
+ /**
+ * 流程任务的名字
+ */
+ @NotEmpty(message = "流程任务的名字不能为空")
+ private String taskName;
+
+ /**
+ * 审批人的用户编号
+ */
+ @NotNull(message = "审批人的用户编号不能为空")
+ private Long assigneeUserId;
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveService.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveService.java
new file mode 100644
index 00000000..a851f403
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveService.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.bpm.service.oa;
+
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
+
+import javax.validation.Valid;
+
+/**
+ * 请假申请 Service 接口
+ *
+ * @author jason
+ * @author 芋道源码
+ */
+public interface BpmOALeaveService {
+
+ /**
+ * 创建请假申请
+ *
+ * @param userId 用户编号
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createLeave(Long userId, @Valid BpmOALeaveCreateReqVO createReqVO);
+
+ /**
+ * 更新请假申请的状态
+ *
+ * @param id 编号
+ * @param result 结果
+ */
+ void updateLeaveResult(Long id, Integer result);
+
+ /**
+ * 获得请假申请
+ *
+ * @param id 编号
+ * @return 请假申请
+ */
+ BpmOALeaveDO getLeave(Long id);
+
+ /**
+ * 获得请假申请分页
+ *
+ * @param userId 用户编号
+ * @param pageReqVO 分页查询
+ * @return 请假申请分页
+ */
+ PageResult getLeavePage(Long userId, BpmOALeavePageReqVO pageReqVO);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveServiceImpl.java
new file mode 100644
index 00000000..d0aadd95
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/BpmOALeaveServiceImpl.java
@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.bpm.service.oa;
+
+import cn.hutool.core.date.DateUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO;
+import cn.iocoder.yudao.module.bpm.convert.oa.BpmOALeaveConvert;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOALeaveMapper;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_LEAVE_NOT_EXISTS;
+
+/**
+ * OA 请假申请 Service 实现类
+ *
+ * @author jason
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class BpmOALeaveServiceImpl implements BpmOALeaveService {
+
+ /**
+ * OA 请假对应的流程定义 KEY
+ */
+ public static final String PROCESS_KEY = "oa_leave";
+
+ @Resource
+ private BpmOALeaveMapper leaveMapper;
+
+ @Resource
+ private BpmProcessInstanceApi processInstanceApi;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createLeave(Long userId, BpmOALeaveCreateReqVO createReqVO) {
+ // 插入 OA 请假单
+ long day = DateUtil.betweenDay(createReqVO.getStartTime(), createReqVO.getEndTime(), false);
+ BpmOALeaveDO leave = BpmOALeaveConvert.INSTANCE.convert(createReqVO).setUserId(userId).setDay(day)
+ .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
+ leaveMapper.insert(leave);
+
+ // 发起 BPM 流程
+ Map processInstanceVariables = new HashMap<>();
+ processInstanceVariables.put("day", day);
+ String processInstanceId = processInstanceApi.createProcessInstance(userId,
+ new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
+ .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(leave.getId())));
+
+ // 将工作流的编号,更新到 OA 请假单中
+ leaveMapper.updateById(new BpmOALeaveDO().setId(leave.getId()).setProcessInstanceId(processInstanceId));
+ return leave.getId();
+ }
+
+ @Override
+ public void updateLeaveResult(Long id, Integer result) {
+ validateLeaveExists(id);
+ leaveMapper.updateById(new BpmOALeaveDO().setId(id).setResult(result));
+ }
+
+ private void validateLeaveExists(Long id) {
+ if (leaveMapper.selectById(id) == null) {
+ throw exception(OA_LEAVE_NOT_EXISTS);
+ }
+ }
+
+ @Override
+ public BpmOALeaveDO getLeave(Long id) {
+ return leaveMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getLeavePage(Long userId, BpmOALeavePageReqVO pageReqVO) {
+ return leaveMapper.selectPage(userId, pageReqVO);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveResultListener.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveResultListener.java
new file mode 100644
index 00000000..2354070f
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveResultListener.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.bpm.service.oa.listener;
+
+import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent;
+import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventListener;
+import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveService;
+import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveServiceImpl;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * OA 请假单的结果的监听器实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class BpmOALeaveResultListener extends BpmProcessInstanceResultEventListener {
+
+ @Resource
+ private BpmOALeaveService leaveService;
+
+ @Override
+ protected String getProcessDefinitionKey() {
+ return BpmOALeaveServiceImpl.PROCESS_KEY;
+ }
+
+ @Override
+ protected void onEvent(BpmProcessInstanceResultEvent event) {
+ leaveService.updateLeaveResult(Long.parseLong(event.getBusinessKey()), event.getResult());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/task/package-info.java b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/task/package-info.java
new file mode 100644
index 00000000..eaa61530
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/main/java/cn/iocoder/yudao/module/bpm/service/task/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.bpm.service.task;
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceTest.java b/yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceTest.java
new file mode 100644
index 00000000..bdd88afe
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmFormServiceTest.java
@@ -0,0 +1,145 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmFormMapper;
+import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmFormFieldRespDTO;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.FORM_NOT_EXISTS;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link BpmFormServiceImpl} 的单元测试类
+ *
+ * @author 芋道源码
+ */
+@Import(BpmFormServiceImpl.class)
+public class BpmFormServiceTest extends BaseDbUnitTest {
+
+ @Resource
+ private BpmFormServiceImpl formService;
+
+ @Resource
+ private BpmFormMapper formMapper;
+
+ @Test
+ public void testCreateForm_success() {
+ // 准备参数
+ BpmFormCreateReqVO reqVO = randomPojo(BpmFormCreateReqVO.class, o -> {
+ o.setConf("{}");
+ o.setFields(randomFields());
+ });
+
+ // 调用
+ Long formId = formService.createForm(reqVO);
+ // 断言
+ assertNotNull(formId);
+ // 校验记录的属性是否正确
+ BpmFormDO form = formMapper.selectById(formId);
+ assertPojoEquals(reqVO, form);
+ }
+
+ @Test
+ public void testUpdateForm_success() {
+ // mock 数据
+ BpmFormDO dbForm = randomPojo(BpmFormDO.class, o -> {
+ o.setConf("{}");
+ o.setFields(randomFields());
+ });
+ formMapper.insert(dbForm);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ BpmFormUpdateReqVO reqVO = randomPojo(BpmFormUpdateReqVO.class, o -> {
+ o.setId(dbForm.getId()); // 设置更新的 ID
+ o.setConf("{'yudao': 'yuanma'}");
+ o.setFields(randomFields());
+ });
+
+ // 调用
+ formService.updateForm(reqVO);
+ // 校验是否更新正确
+ BpmFormDO form = formMapper.selectById(reqVO.getId()); // 获取最新的
+ assertPojoEquals(reqVO, form);
+ }
+
+ @Test
+ public void testUpdateForm_notExists() {
+ // 准备参数
+ BpmFormUpdateReqVO reqVO = randomPojo(BpmFormUpdateReqVO.class, o -> {
+ o.setConf("{'yudao': 'yuanma'}");
+ o.setFields(randomFields());
+ });
+
+ // 调用, 并断言异常
+ assertServiceException(() -> formService.updateForm(reqVO), FORM_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteForm_success() {
+ // mock 数据
+ BpmFormDO dbForm = randomPojo(BpmFormDO.class);
+ formMapper.insert(dbForm);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbForm.getId();
+
+ // 调用
+ formService.deleteForm(id);
+ // 校验数据不存在了
+ assertNull(formMapper.selectById(id));
+ }
+
+ @Test
+ public void testDeleteForm_notExists() {
+ // 准备参数
+ Long id = randomLongId();
+
+ // 调用, 并断言异常
+ assertServiceException(() -> formService.deleteForm(id), FORM_NOT_EXISTS);
+ }
+
+ @Test
+ public void testGetFormPage() {
+ // mock 数据
+ BpmFormDO dbForm = randomPojo(BpmFormDO.class, o -> { // 等会查询到
+ o.setName("芋道源码");
+ });
+ formMapper.insert(dbForm);
+ // 测试 name 不匹配
+ formMapper.insert(cloneIgnoreId(dbForm, o -> o.setName("源码")));
+ // 准备参数
+ BpmFormPageReqVO reqVO = new BpmFormPageReqVO();
+ reqVO.setName("芋道");
+
+ // 调用
+ PageResult pageResult = formService.getFormPage(reqVO);
+ // 断言
+ assertEquals(1, pageResult.getTotal());
+ assertEquals(1, pageResult.getList().size());
+ assertPojoEquals(dbForm, pageResult.getList().get(0));
+ }
+
+ private List randomFields() {
+ int size = RandomUtil.randomInt(1, 3);
+ return Stream.iterate(0, i -> i).limit(size)
+ .map(i -> JsonUtils.toJsonString(randomPojo(BpmFormFieldRespDTO.class)))
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceTest.java b/yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceTest.java
new file mode 100644
index 00000000..78e38870
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/test/java/cn/iocoder/yudao/module/bpm/service/definition/BpmUserGroupServiceTest.java
@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
+import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupCreateReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupUpdateReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmUserGroupMapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
+
+/**
+* {@link BpmUserGroupServiceImpl} 的单元测试类
+*
+* @author 芋道源码
+*/
+@Import(BpmUserGroupServiceImpl.class)
+public class BpmUserGroupServiceTest extends BaseDbUnitTest {
+
+ @Resource
+ private BpmUserGroupServiceImpl userGroupService;
+
+ @Resource
+ private BpmUserGroupMapper userGroupMapper;
+
+ @Test
+ public void testCreateUserGroup_success() {
+ // 准备参数
+ BpmUserGroupCreateReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupCreateReqVO.class);
+
+ // 调用
+ Long userGroupId = userGroupService.createUserGroup(reqVO);
+ // 断言
+ Assertions.assertNotNull(userGroupId);
+ // 校验记录的属性是否正确
+ BpmUserGroupDO userGroup = userGroupMapper.selectById(userGroupId);
+ AssertUtils.assertPojoEquals(reqVO, userGroup);
+ }
+
+ @Test
+ public void testUpdateUserGroup_success() {
+ // mock 数据
+ BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class);
+ userGroupMapper.insert(dbUserGroup);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ BpmUserGroupUpdateReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupUpdateReqVO.class, o -> {
+ o.setId(dbUserGroup.getId()); // 设置更新的 ID
+ });
+
+ // 调用
+ userGroupService.updateUserGroup(reqVO);
+ // 校验是否更新正确
+ BpmUserGroupDO userGroup = userGroupMapper.selectById(reqVO.getId()); // 获取最新的
+ AssertUtils.assertPojoEquals(reqVO, userGroup);
+ }
+
+ @Test
+ public void testUpdateUserGroup_notExists() {
+ // 准备参数
+ BpmUserGroupUpdateReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupUpdateReqVO.class);
+
+ // 调用, 并断言异常
+ AssertUtils.assertServiceException(() -> userGroupService.updateUserGroup(reqVO), USER_GROUP_NOT_EXISTS);
+ }
+
+ @Test
+ public void testDeleteUserGroup_success() {
+ // mock 数据
+ BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class);
+ userGroupMapper.insert(dbUserGroup);// @Sql: 先插入出一条存在的数据
+ // 准备参数
+ Long id = dbUserGroup.getId();
+
+ // 调用
+ userGroupService.deleteUserGroup(id);
+ // 校验数据不存在了
+ Assertions.assertNull(userGroupMapper.selectById(id));
+ }
+
+ @Test
+ public void testDeleteUserGroup_notExists() {
+ // 准备参数
+ Long id = RandomUtils.randomLongId();
+
+ // 调用, 并断言异常
+ AssertUtils.assertServiceException(() -> userGroupService.deleteUserGroup(id), USER_GROUP_NOT_EXISTS);
+ }
+
+ @Test
+ public void testGetUserGroupPage() {
+ // mock 数据
+ BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class, o -> { // 等会查询到
+ o.setName("芋道源码");
+ o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ o.setCreateTime(DateUtils.buildTime(2021, 11, 11));
+ });
+ userGroupMapper.insert(dbUserGroup);
+ // 测试 name 不匹配
+ userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setName("芋道")));
+ // 测试 status 不匹配
+ userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+ // 测试 createTime 不匹配
+ userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setCreateTime(DateUtils.buildTime(2021, 12, 12))));
+ // 准备参数
+ BpmUserGroupPageReqVO reqVO = new BpmUserGroupPageReqVO();
+ reqVO.setName("源码");
+ reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ reqVO.setBeginCreateTime(DateUtils.buildTime(2021, 11, 10));
+ reqVO.setEndCreateTime(DateUtils.buildTime(2021, 11, 12));
+
+ // 调用
+ PageResult pageResult = userGroupService.getUserGroupPage(reqVO);
+ // 断言
+ Assertions.assertEquals(1, pageResult.getTotal());
+ Assertions.assertEquals(1, pageResult.getList().size());
+ AssertUtils.assertPojoEquals(dbUserGroup, pageResult.getList().get(0));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/application-unit-test.yaml b/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/application-unit-test.yaml
new file mode 100644
index 00000000..c4a0f303
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/application-unit-test.yaml
@@ -0,0 +1,43 @@
+spring:
+ main:
+ lazy-initialization: true # 开启懒加载,加快速度
+ banner-mode: off # 单元测试,禁用 Banner
+
+--- #################### 数据库相关配置 ####################
+
+spring:
+ # 数据源配置项
+ datasource:
+ name: ruoyi-vue-pro
+ url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ druid:
+ async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
+ initial-size: 1 # 单元测试,配置为 1,提升启动速度
+ sql:
+ init:
+ schema-locations: classpath:/sql/create_tables.sql
+
+mybatis:
+ lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
+
+--- #################### 定时任务相关配置 ####################
+
+--- #################### 配置中心相关配置 ####################
+
+--- #################### 服务保障相关配置 ####################
+
+# Lock4j 配置项(单元测试,禁用 Lock4j)
+
+# Resilience4j 配置项
+
+--- #################### 监控相关配置 ####################
+
+--- #################### 芋道相关配置 ####################
+
+# 芋道配置项,设置当前项目所有自定义的配置
+yudao:
+ info:
+ base-package: cn.iocoder.yudao.module
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/logback.xml b/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/logback.xml
new file mode 100644
index 00000000..daf756bf
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/logback.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/clean.sql b/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/clean.sql
new file mode 100644
index 00000000..6e42d3cf
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/clean.sql
@@ -0,0 +1,2 @@
+DELETE FROM "bpm_form";
+DELETE FROM "bpm_user_group";
diff --git a/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/create_tables.sql b/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/create_tables.sql
new file mode 100644
index 00000000..20a939b7
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-base/src/test/resources/sql/create_tables.sql
@@ -0,0 +1,28 @@
+CREATE TABLE IF NOT EXISTS "bpm_user_group" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar(63) NOT NULL,
+ "description" varchar(255) NOT NULL,
+ "status" tinyint NOT NULL,
+ "member_user_ids" varchar(255) NOT NULL,
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '用户组';
+
+CREATE TABLE IF NOT EXISTS "bpm_form" (
+ "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+ "name" varchar(63) NOT NULL,
+ "status" tinyint NOT NULL,
+ "fields" varchar(255) NOT NULL,
+ "conf" varchar(255) NOT NULL,
+ "remark" varchar(255),
+ "creator" varchar(64) DEFAULT '',
+ "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updater" varchar(64) DEFAULT '',
+ "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "deleted" bit NOT NULL DEFAULT FALSE,
+ PRIMARY KEY ("id")
+) COMMENT '动态表单';
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/pom.xml b/yudao-module-bpm/yudao-module-bpm-impl-activiti/pom.xml
new file mode 100644
index 00000000..839150b3
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/pom.xml
@@ -0,0 +1,39 @@
+
+
+
+ cn.iocoder.boot
+ yudao-module-bpm
+ ${revision}
+
+ 4.0.0
+ yudao-module-bpm-impl-activiti
+ jar
+
+ ${project.artifactId}
+
+ bpm-activiti 模块,基于 Activiti 7 实现工作流
+
+
+
+
+ cn.iocoder.boot
+ yudao-module-bpm-base
+ ${revision}
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-activiti
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-test
+ test
+
+
+
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java
new file mode 100644
index 00000000..2137e220
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * bpm API 实现类,定义暴露给其它模块的 API
+ */
+package cn.iocoder.yudao.module.bpm.api;
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java
new file mode 100644
index 00000000..ff396602
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/api/task/BpmProcessInstanceApiImpl.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.bpm.api.task;
+
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * Activiti 流程实例 Api 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi {
+
+ @Resource
+ private BpmProcessInstanceService processInstanceService;
+
+ @Override
+ public String createProcessInstance(Long userId, BpmProcessInstanceCreateReqDTO reqDTO) {
+ return processInstanceService.createProcessInstance(userId, reqDTO);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
new file mode 100644
index 00000000..5d8b4bfc
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
@@ -0,0 +1,96 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition;
+
+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 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 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 bpmModelService;
+
+ @GetMapping("/page")
+ @ApiOperation(value = "获得模型分页")
+ public CommonResult> getModelPage(BpmModelPageReqVO pageVO) {
+ return success(bpmModelService.getModelPage(pageVO));
+ }
+
+ @GetMapping("/get")
+ @ApiOperation("获得模型")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
+ @PreAuthorize("@ss.hasPermission('bpm:model:query')")
+ public CommonResult getModel(@RequestParam("id") String id) {
+ BpmModelRespVO model = bpmModelService.getModel(id);
+ return success(model);
+ }
+
+ @PostMapping("/create")
+ @ApiOperation(value = "新建模型")
+ @PreAuthorize("@ss.hasPermission('bpm:model:create')")
+ public CommonResult createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) {
+ return success(bpmModelService.createModel(createRetVO, null));
+ }
+
+ @PostMapping("/import")
+ @ApiOperation(value = "导入模型")
+ @PreAuthorize("@ss.hasPermission('bpm:model:import')")
+ public CommonResult importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException {
+ BpmModelCreateReqVO createReqVO = BpmModelConvert.INSTANCE.convert(importReqVO);
+ // 读取文件
+ String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false);
+ return success(bpmModelService.createModel(createReqVO, bpmnXml));
+ }
+
+ @PutMapping("/update")
+ @ApiOperation(value = "修改模型")
+ @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+ public CommonResult updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) {
+ bpmModelService.updateModel(modelVO);
+ 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 deleteModel(@RequestParam("id") String id) {
+ bpmModelService.deleteModel(id);
+ return success(true);
+ }
+
+ @PostMapping("/deploy")
+ @ApiOperation(value = "部署模型")
+ @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
+ @PreAuthorize("@ss.hasPermission('bpm:model:deploy')")
+ public CommonResult deployModel(@RequestParam("id") String id) {
+ bpmModelService.deployModel(id);
+ return success(true);
+ }
+
+ @PutMapping("/update-state")
+ @ApiOperation(value = "修改模型的状态", notes = "实际更新的部署的流程定义的状态")
+ @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+ public CommonResult updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
+ bpmModelService.updateModelState(reqVO.getId(), reqVO.getState());
+ return success(true);
+ }
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.http b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.http
new file mode 100644
index 00000000..492960ed
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.http
@@ -0,0 +1,4 @@
+### 请求 /bpm/process-definition/list 接口 => 成功
+GET {{baseUrl}}/bpm/process-definition/list?suspensionState=1
+tenant-id: 1
+Authorization: Bearer {{token}}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
new file mode 100644
index 00000000..769e53ec
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.definition;
+
+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 cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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> getProcessDefinitionPage(
+ BpmProcessDefinitionPageReqVO pageReqVO) {
+ return success(bpmDefinitionService.getProcessDefinitionPage(pageReqVO));
+ }
+
+ @GetMapping ("/list")
+ @ApiOperation(value = "获得流程定义列表")
+ @PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
+ public CommonResult> 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 getProcessDefinitionBpmnXML(@RequestParam("id") String id) {
+ String bpmnXML = bpmDefinitionService.getProcessDefinitionBpmnXML(id);
+ return success(bpmnXML);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.http b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.http
new file mode 100644
index 00000000..e5d8fe96
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.http
@@ -0,0 +1,4 @@
+### 请求 /bpm/task-assign-rule/list 接口 => 成功
+GET {{baseUrl}}/bpm/task-assign-rule/list?processDefinitionId=leave:9:59689ba0-7284-11ec-965c-a2380e71991a
+tenant-id: 1
+Authorization: Bearer {{token}}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.java
new file mode 100644
index 00000000..b51a4661
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmTaskAssignRuleController.java
@@ -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.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> 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 createTaskAssignRule(@Valid @RequestBody BpmTaskAssignRuleCreateReqVO reqVO) {
+ return success(taskAssignRuleService.createTaskAssignRule(reqVO));
+ }
+
+ @PutMapping("/update")
+ @ApiOperation(value = "更新任务分配规则")
+ @PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:update')")
+ public CommonResult updateTaskAssignRule(@Valid @RequestBody BpmTaskAssignRuleUpdateReqVO reqVO) {
+ taskAssignRuleService.updateTaskAssignRule(reqVO);
+ return success(true);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java
new file mode 100644
index 00000000..224a942f
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
+import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+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 javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+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> getActivityList(
+ @RequestParam("processInstanceId") String processInstanceId) {
+ return success(activityService.getActivityListByProcessInstanceId(processInstanceId));
+ }
+
+ @GetMapping("/generate-highlight-diagram")
+ @ApiOperation(value = "生成指定流程实例的高亮流程图",
+ notes = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成")
+ @ApiImplicitParam(name = "processInstanceId", value = "流程实例的编号", required = true, dataTypeClass = String.class)
+ @PreAuthorize("@ss.hasPermission('bpm:task:query')")
+ public void generateHighlightDiagram(@RequestParam("processInstanceId") String processInstanceId,
+ HttpServletResponse response) throws IOException {
+ byte[] bytes = activityService.generateHighlightDiagram(processInstanceId);
+ ServletUtils.writeAttachment(response, StrUtil.format("流程图-{}.svg", processInstanceId), bytes);
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http
new file mode 100644
index 00000000..e9243ed6
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.http
@@ -0,0 +1,29 @@
+### 请求 /bpm/process-instance/create 接口 => 成功
+POST {{baseUrl}}/bpm/process-instance/create
+Content-Type: application/json
+tenant-id: 1
+Authorization: Bearer {{token}}
+
+{
+ "processDefinitionId": "gateway_test:2:00e52d8e-701b-11ec-aca9-a2380e71991a",
+ "variables": {
+ "a": 1,
+ "b": "2"
+ }
+}
+
+### 请求 /bpm/process-instance/cancel 接口 => 成功
+DELETE {{baseUrl}}/bpm/process-instance/cancel
+Content-Type: application/json
+tenant-id: 1
+Authorization: Bearer {{token}}
+
+{
+ "id": "b9220387-7088-11ec-bcae-a2380e71991a",
+ "reason": "我就取消"
+}
+
+### 请求 /bpm/process-instance/get 接口 => 成功
+GET {{baseUrl}}/bpm/process-instance/get?id=537cceb3-768c-11ec-afcd-a2380e71991a
+tenant-id: 1
+Authorization: Bearer {{token}}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
new file mode 100644
index 00000000..e859968e
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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;
+
+ @PostMapping("/create")
+ @ApiOperation("新建流程实例")
+ @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+ public CommonResult createProcessInstance(@Valid @RequestBody BpmProcessInstanceCreateReqVO createReqVO) {
+ return success(processInstanceService.createProcessInstance(getLoginUserId(), createReqVO));
+ }
+
+ @DeleteMapping("/cancel")
+ @ApiOperation(value = "取消流程实例", notes = "撤回发起的流程")
+ @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")
+ public CommonResult cancelProcessInstance(@Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) {
+ processInstanceService.cancelProcessInstance(getLoginUserId(), cancelReqVO);
+ return success(true);
+ }
+
+ @GetMapping("/my-page")
+ @ApiOperation(value = "获得我的实例分页列表", notes = "在【我的流程】菜单中,进行调用")
+ @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+ public CommonResult> getMyProcessInstancePage(
+ @Valid BpmProcessInstanceMyPageReqVO pageReqVO) {
+ return success(processInstanceService.getMyProcessInstancePage(getLoginUserId(), pageReqVO));
+ }
+
+ @GetMapping("/get")
+ @ApiOperation(value = "获得指定流程实例", notes = "在【流程详细】界面中,进行调用")
+ @ApiImplicitParam(name = "id", value = "流程实例的编号", required = true, dataTypeClass = String.class)
+ @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+ public CommonResult getProcessInstance(@RequestParam("id") String id) {
+ return success(processInstanceService.getProcessInstanceVO(id));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.http b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.http
new file mode 100644
index 00000000..ee4bf0c2
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.http
@@ -0,0 +1,14 @@
+### 请求 /bpm/task/todo-page 接口 => 成功
+GET {{baseUrl}}/bpm/task/todo-page
+tenant-id: 1
+Authorization: Bearer {{token}}
+
+### 请求 /bpm/task/done-page 接口 => 成功
+GET {{baseUrl}}/bpm/task/done-page?pageSize=100
+tenant-id: 1
+Authorization: Bearer {{token}}
+
+### 请求 /bpm/task/list-by-process-instance-id 接口 => 成功
+GET {{baseUrl}}/bpm/task/list-by-process-instance-id?processInstanceId=537cceb3-768c-11ec-afcd-a2380e71991a
+tenant-id: 1
+Authorization: Bearer {{token}}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
new file mode 100644
index 00000000..6b8f6dff
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.task;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
+import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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.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> getTodoTaskPage(@Valid BpmTaskTodoPageReqVO pageVO) {
+ return success(taskService.getTodoTaskPage(getLoginUserId(), pageVO));
+ }
+
+ @GetMapping("done-page")
+ @ApiOperation("获取 Done 已办任务分页")
+ @PreAuthorize("@ss.hasPermission('bpm:task:query')")
+ public CommonResult> getDoneTaskPage(@Valid BpmTaskDonePageReqVO pageVO) {
+ return success(taskService.getDoneTaskPage(getLoginUserId(), pageVO));
+ }
+
+ @PutMapping("/approve")
+ @ApiOperation("通过任务")
+ @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+ public CommonResult approveTask(@Valid @RequestBody BpmTaskApproveReqVO reqVO) {
+ taskService.approveTask(getLoginUserId(), reqVO);
+ return success(true);
+ }
+
+ @PutMapping("/reject")
+ @ApiOperation("不通过任务")
+ @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+ public CommonResult 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 updateTaskAssignee(@Valid @RequestBody BpmTaskUpdateAssigneeReqVO reqVO) {
+ taskService.updateTaskAssignee(getLoginUserId(), reqVO);
+ return success(true);
+ }
+
+ @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> getTaskListByProcessInstanceId(
+ @RequestParam("processInstanceId") String processInstanceId) {
+ return success(taskService.getTaskListByProcessInstanceId(processInstanceId));
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java
new file mode 100644
index 00000000..e8d285e3
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/app/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.yudao.module.bpm.controller.app;
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java
new file mode 100644
index 00000000..d1930bd6
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/controller/package-info.java
@@ -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;
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
new file mode 100644
index 00000000..171bbbb5
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
@@ -0,0 +1,142 @@
+package cn.iocoder.yudao.module.bpm.convert.definition;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+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.activiti.engine.impl.persistence.entity.SuspensionState;
+import org.activiti.engine.repository.Deployment;
+import org.activiti.engine.repository.Model;
+import org.activiti.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 convertList(List list, Map formMap,
+ Map deploymentMap,
+ Map 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);
+ }
+
+ void copyTo(BpmModelMetaInfoRespDTO from, @MappingTarget BpmModelBaseVO to);
+
+ 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);
+
+ 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);
+ }
+
+ BpmModelPageItemRespVO.ProcessDefinition convert(ProcessDefinition bean);
+
+ BpmModelCreateReqVO convert(BpmModeImportReqVO bean);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
new file mode 100644
index 00000000..b789bc00
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
@@ -0,0 +1,83 @@
+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.activiti.engine.impl.persistence.entity.SuspensionState;
+import org.activiti.engine.repository.Deployment;
+import org.activiti.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);
+
+ default List convertList(List list, Map deploymentMap,
+ Map processDefinitionDOMap, Map 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 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;
+ }
+
+ BpmProcessDefinitionPageItemRespVO convert(ProcessDefinition bean);
+
+ BpmProcessDefinitionExtDO convert2(BpmProcessDefinitionCreateReqDTO bean);
+
+ default List convertList3(List list,
+ Map 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();
+ }
+
+ @Mapping(source = "from.id", target = "to.id", ignore = true)
+ void copyTo(BpmProcessDefinitionExtDO from, @MappingTarget BpmProcessDefinitionRespVO to);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmTaskAssignRuleConvert.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmTaskAssignRuleConvert.java
new file mode 100644
index 00000000..4be54fe7
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmTaskAssignRuleConvert.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.bpm.convert.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 cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import org.activiti.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 convertList(List tasks, List rules) {
+ Map 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 convertList2(List list);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/package-info.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/package-info.java
new file mode 100644
index 00000000..6db6ebc4
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 提供 POJO 类的实体转换
+ *
+ * 目前使用 MapStruct 框架
+ */
+package cn.iocoder.yudao.module.bpm.convert;
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java
new file mode 100644
index 00000000..02bfaa24
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.bpm.convert.task;
+
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
+import org.activiti.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 convertList(List list);
+
+ @Mappings({
+ @Mapping(source = "activityId", target = "key"),
+ @Mapping(source = "activityType", target = "type")
+ })
+ BpmActivityRespVO convert(HistoricActivityInstance bean);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
new file mode 100644
index 00000000..abc94d89
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
@@ -0,0 +1,135 @@
+package cn.iocoder.yudao.module.bpm.convert.task;
+
+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 cn.iocoder.yudao.framework.common.pojo.PageResult;
+import org.activiti.engine.history.HistoricProcessInstance;
+import org.activiti.engine.repository.ProcessDefinition;
+import org.activiti.engine.runtime.ProcessInstance;
+import org.activiti.engine.task.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;
+
+/**
+ * 流程实例 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface BpmProcessInstanceConvert {
+
+ BpmProcessInstanceConvert INSTANCE = Mappers.getMapper(BpmProcessInstanceConvert.class);
+
+ default BpmProcessInstanceExtDO convert3(ProcessInstance instance, ProcessDefinition definition) {
+ BpmProcessInstanceExtDO ext = new BpmProcessInstanceExtDO();
+ copyTo(instance, ext);
+ copyTo(definition, ext);
+ return ext;
+ }
+
+ @Mappings({
+ @Mapping(source = "from.id", target = "id", ignore = true),
+ @Mapping(source = "from.startTime", target = "createTime"),
+ })
+ void copyTo(ProcessInstance from, @MappingTarget BpmProcessInstanceExtDO to);
+ @Mapping(source = "from.id", target = "id", ignore = true)
+ void copyTo(ProcessDefinition from, @MappingTarget BpmProcessInstanceExtDO to);
+
+ default PageResult convertPage(PageResult page,
+ Map> taskMap) {
+ List list = convertList(page.getList());
+ list.forEach(respVO -> respVO.setTasks(convertList2(taskMap.get(respVO.getId()))));
+ return new PageResult<>(list, page.getTotal());
+ }
+
+ List convertList(List list);
+
+ List convertList2(List tasks);
+
+ @Mapping(source = "processInstanceId", target = "id")
+ BpmProcessInstancePageItemRespVO convert(BpmProcessInstanceExtDO bean);
+
+ @Mappings({
+ @Mapping(source = "id", target = "processInstanceId"),
+ @Mapping(source = "id", target = "id", ignore = true),
+ @Mapping(source = "startDate", target = "createTime"),
+ @Mapping(source = "initiator", target = "startUserId"),
+ @Mapping(source = "status", target = "status", ignore = true)
+ })
+ BpmProcessInstanceExtDO convert(org.activiti.api.process.model.ProcessInstance bean);
+
+ 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, 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 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 BpmMessageSendWhenProcessInstanceRejectReqDTO convert(ProcessInstance processInstance, String comment) {
+ BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO = new BpmMessageSendWhenProcessInstanceRejectReqDTO();
+ copyTo(processInstance, reqDTO);
+ reqDTO.setComment(comment);
+ return reqDTO;
+ }
+ @Mapping(source = "name", target = "processInstanceName")
+ void copyTo(ProcessInstance from, @MappingTarget BpmMessageSendWhenProcessInstanceRejectReqDTO to);
+
+ @Mappings({
+ @Mapping(source = "id", target = "processInstanceId"),
+ @Mapping(source = "name", target = "processInstanceName"),
+ @Mapping(source = "initiator", target = "startUserId")
+ })
+ BpmMessageSendWhenProcessInstanceApproveReqDTO convert2(org.activiti.api.process.model.ProcessInstance processInstance);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
new file mode 100644
index 00000000..d74cc16b
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
@@ -0,0 +1,149 @@
+package cn.iocoder.yudao.module.bpm.convert.task;
+
+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 cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import org.activiti.engine.history.HistoricProcessInstance;
+import org.activiti.engine.history.HistoricTaskInstance;
+import org.activiti.engine.impl.persistence.entity.SuspensionState;
+import org.activiti.engine.runtime.ProcessInstance;
+import org.activiti.engine.task.Task;
+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 convertList1(List tasks, Map processInstanceMap,
+ Map 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 convertList2(List tasks, Map bpmTaskExtDOMap,
+ Map historicProcessInstanceMap,
+ Map 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 = "id", target = "taskId"),
+ @Mapping(source = "assignee", target = "assigneeUserId"),
+ @Mapping(source = "createdDate", target = "createTime"),
+ @Mapping(target = "id", ignore = true)
+ })
+ BpmTaskExtDO convert(org.activiti.api.task.model.Task bean);
+
+ default List convertList3(List tasks, Map bpmTaskExtDOMap,
+ HistoricProcessInstance processInstance, Map userMap,
+ Map 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(ProcessInstance processInstance, AdminUserRespDTO startUser);
+
+ @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 BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, org.activiti.api.task.model.Task task) {
+ BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
+ copyTo(processInstance, reqDTO);
+ copyTo(startUser, reqDTO);
+ copyTo(task, reqDTO);
+ return reqDTO;
+ }
+ @Mapping(source = "name", target = "processInstanceName")
+ void copyTo(ProcessInstance from, @MappingTarget BpmMessageSendWhenTaskCreatedReqDTO to);
+ @Mappings({
+ @Mapping(source = "id", target = "startUserId"),
+ @Mapping(source = "nickname", target = "startUserNickname")
+ })
+ void copyTo(AdminUserRespDTO from, @MappingTarget BpmMessageSendWhenTaskCreatedReqDTO to);
+ @Mappings({
+ @Mapping(source = "id", target = "taskId"),
+ @Mapping(source = "name", target = "taskName"),
+ @Mapping(source = "assignee", target = "assigneeUserId")
+ })
+ void copyTo(org.activiti.api.task.model.Task task, @MappingTarget BpmMessageSendWhenTaskCreatedReqDTO to);
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
new file mode 100644
index 00000000..8153487b
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
@@ -0,0 +1 @@
+
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/config/BpmActivitiConfiguration.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/config/BpmActivitiConfiguration.java
new file mode 100644
index 00000000..181cc2d5
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/config/BpmActivitiConfiguration.java
@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.config;
+
+import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.BpmActivityBehaviorFactory;
+import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
+import cn.iocoder.yudao.module.bpm.framework.activiti.core.identity.EmptyUserGroupManager;
+import cn.iocoder.yudao.module.bpm.framework.activiti.core.listener.BpmTackActivitiEventListener;
+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.activiti.api.runtime.shared.identity.UserGroupManager;
+import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.activiti.spring.boot.ProcessEngineAutoConfiguration.BEHAVIOR_FACTORY_MAPPING_CONFIGURER;
+
+/**
+ * BPM 模块的 Activiti 配置类
+ */
+@Configuration
+public class BpmActivitiConfiguration {
+
+ /**
+ * 空用户组的 Bean
+ */
+ @Bean
+ public UserGroupManager userGroupManager() {
+ return new EmptyUserGroupManager();
+ }
+
+ /**
+ * BPM 模块的 ProcessEngineConfigurationConfigurer 实现类,主要设置各种监听器
+ */
+ @Bean
+ public ProcessEngineConfigurationConfigurer bpmProcessEngineConfigurationConfigurer(
+ BpmTackActivitiEventListener taskActivitiEventListener) {
+ return configuration -> {
+ // 注册监听器,例如说 BpmActivitiEventListener
+ configuration.setEventListeners(Collections.singletonList(taskActivitiEventListener));
+ };
+ }
+
+ /**
+ * 用于设置自定义的 ActivityBehaviorFactory 实现的 ProcessEngineConfigurationConfigurer 实现类
+ *
+ * 目的:覆盖 {@link org.activiti.spring.boot.ProcessEngineAutoConfiguration} 的
+ * defaultActivityBehaviorFactoryMappingConfigurer 方法创建的 Bean
+ */
+ @Bean(name = BEHAVIOR_FACTORY_MAPPING_CONFIGURER)
+ public ProcessEngineConfigurationConfigurer defaultActivityBehaviorFactoryMappingConfigurer(
+ BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
+ return configuration -> {
+ // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义
+ configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
+ };
+ }
+
+ @Bean
+ public BpmActivityBehaviorFactory bpmActivityBehaviorFactory(BpmTaskAssignRuleService taskRuleService,
+ BpmUserGroupService userGroupService,
+ PermissionApi permissionApi,
+ DeptApi deptApi,
+ AdminUserApi adminUserApi,
+ List 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;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmActivityBehaviorFactory.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmActivityBehaviorFactory.java
new file mode 100644
index 00000000..76f0d97a
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmActivityBehaviorFactory.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior;
+
+import cn.iocoder.yudao.module.bpm.framework.activiti.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.activiti.bpmn.model.UserTask;
+import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
+import org.activiti.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 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;
+ }
+
+ // TODO 芋艿:并行任务 ParallelMultiInstanceBehavior
+
+ // TODO 芋艿:并行任务 SequentialMultiInstanceBehavior
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmUserTaskActivityBehavior.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmUserTaskActivityBehavior.java
new file mode 100644
index 00000000..ed95b4c8
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/BpmUserTaskActivityBehavior.java
@@ -0,0 +1,196 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+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.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.activiti.bpmn.model.UserTask;
+import org.activiti.engine.ActivitiException;
+import org.activiti.engine.delegate.DelegateExecution;
+import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
+import org.activiti.engine.impl.el.ExpressionManager;
+import org.activiti.engine.impl.persistence.entity.TaskEntity;
+import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
+
+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 scriptMap = Collections.emptyMap();
+
+ public BpmUserTaskActivityBehavior(UserTask userTask) {
+ super(userTask);
+ }
+
+ public void setScripts(List scripts) {
+ this.scriptMap = convertMap(scripts, script -> script.getEnum().getId());
+ }
+
+ @Override
+ protected void handleAssignments(TaskEntityManager taskEntityManager,
+ String assignee, String owner, List candidateUsers, List candidateGroups,
+ TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution) {
+ // 第一步,获得任务的规则
+ BpmTaskAssignRuleDO rule = getTaskRule(task);
+ // 第二步,获得任务的候选用户们
+ Set candidateUserIds = calculateTaskCandidateUsers(task, rule);
+ // 第三步,设置一个作为负责人
+ Long assigneeUserId = chooseTaskAssignee(candidateUserIds);
+ taskEntityManager.changeTaskAssignee(task, String.valueOf(assigneeUserId));
+ }
+
+ private BpmTaskAssignRuleDO getTaskRule(TaskEntity task) {
+ List taskRules = bpmTaskRuleService.getTaskAssignRuleListByProcessDefinitionId(task.getProcessDefinitionId(),
+ task.getTaskDefinitionKey());
+ if (CollUtil.isEmpty(taskRules)) {
+ throw new ActivitiException(StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则",
+ task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey()));
+ }
+ if (taskRules.size() > 1) {
+ throw new ActivitiException(StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})",
+ task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), taskRules.size()));
+ }
+ return taskRules.get(0);
+ }
+
+ private Long chooseTaskAssignee(Set candidateUserIds) {
+ // TODO 芋艿:未来可以优化下,改成轮询的策略
+ int index = RandomUtil.randomInt(candidateUserIds.size());
+ return CollUtil.get(candidateUserIds, index);
+ }
+
+ @VisibleForTesting
+ Set calculateTaskCandidateUsers(TaskEntity task, BpmTaskAssignRuleDO rule) {
+ Set 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 calculateTaskCandidateUsersByRole(TaskEntity task, BpmTaskAssignRuleDO rule) {
+ return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
+ }
+
+ private Set calculateTaskCandidateUsersByDeptMember(TaskEntity task, BpmTaskAssignRuleDO rule) {
+ List users = adminUserApi.getUsersByDeptIds(rule.getOptions());
+ return convertSet(users, AdminUserRespDTO::getId);
+ }
+
+ private Set calculateTaskCandidateUsersByDeptLeader(TaskEntity task, BpmTaskAssignRuleDO rule) {
+ List depts = deptApi.getDepts(rule.getOptions());
+ return convertSet(depts, DeptRespDTO::getLeaderUserId);
+ }
+
+ private Set calculateTaskCandidateUsersByPost(TaskEntity task, BpmTaskAssignRuleDO rule) {
+ List users = adminUserApi.getUsersByPostIds(rule.getOptions());
+ return convertSet(users, AdminUserRespDTO::getId);
+ }
+
+ private Set calculateTaskCandidateUsersByUser(TaskEntity task, BpmTaskAssignRuleDO rule) {
+ return rule.getOptions();
+ }
+
+ private Set calculateTaskCandidateUsersByUserGroup(TaskEntity task, BpmTaskAssignRuleDO rule) {
+ List userGroups = userGroupService.getUserGroupList(rule.getOptions());
+ Set userIds = new HashSet<>();
+ userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds()));
+ return userIds;
+ }
+
+ private Set calculateTaskCandidateUsersByScript(TaskEntity task, BpmTaskAssignRuleDO rule) {
+ // 获得对应的脚本
+ List 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 userIds = new HashSet<>();
+ scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(task)));
+ return userIds;
+ }
+
+ @VisibleForTesting
+ void removeDisableUsers(Set assigneeUserIds) {
+ if (CollUtil.isEmpty(assigneeUserIds)) {
+ return;
+ }
+ Map userMap = adminUserApi.getUserMap(assigneeUserIds);
+ assigneeUserIds.removeIf(id -> {
+ AdminUserRespDTO user = userMap.get(id);
+ return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
+ });
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/package-info.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/package-info.java
new file mode 100644
index 00000000..e627479a
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * 拓展 {@link org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior} 实现,基于 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO} 实现自定义的任务分配规则。
+ * 原因:BPMN 默认的 assign、candidateUsers、candidateGroups 拓展起来有一定的难度,所以选择放弃它们,使用自己定义的规则。
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior;
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/BpmTaskAssignScript.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/BpmTaskAssignScript.java
new file mode 100644
index 00000000..eee0d3f4
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/BpmTaskAssignScript.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script;
+
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
+import org.activiti.engine.impl.persistence.entity.TaskEntity;
+
+import java.util.Set;
+
+/**
+ * Bpm 任务分配的自定义 Script 脚本
+ * 使用场景:
+ * 1. 设置审批人为发起人
+ * 2. 设置审批人为发起人的 Leader
+ * 3. 甚至审批人为发起人的 Leader 的 Leader
+ *
+ * @author 芋道源码
+ */
+public interface BpmTaskAssignScript {
+
+ /**
+ * 基于流程任务,获得任务的候选用户们
+ *
+ * @param task 任务
+ * @return 候选人用户的编号数组
+ */
+ Set calculateTaskCandidateUsers(TaskEntity task);
+
+ /**
+ * 获得枚举值
+ *
+ * @return 枚举值
+ */
+ BpmTaskRuleScriptEnum getEnum();
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderAbstractScript.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderAbstractScript.java
new file mode 100644
index 00000000..c2756701
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderAbstractScript.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.impl;
+
+import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
+
+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.activiti.engine.impl.persistence.entity.TaskEntity;
+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;
+
+ protected Set calculateTaskCandidateUsers(TaskEntity task, int level) {
+ Assert.isTrue(level > 0, "level 必须大于 0");
+ // 获得发起人
+ Long startUserId = Long.parseLong(task.getProcessInstance().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());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX1Script.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX1Script.java
new file mode 100644
index 00000000..4b0a7c1e
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX1Script.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.impl;
+
+import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
+import org.activiti.engine.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 calculateTaskCandidateUsers(TaskEntity task) {
+ return calculateTaskCandidateUsers(task, 1);
+ }
+
+ @Override
+ public BpmTaskRuleScriptEnum getEnum() {
+ return BpmTaskRuleScriptEnum.LEADER_X1;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX2Script.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX2Script.java
new file mode 100644
index 00000000..41ee8254
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignLeaderX2Script.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.impl;
+
+import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
+import org.activiti.engine.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 calculateTaskCandidateUsers(TaskEntity task) {
+ return calculateTaskCandidateUsers(task, 2);
+ }
+
+ @Override
+ public BpmTaskRuleScriptEnum getEnum() {
+ return BpmTaskRuleScriptEnum.LEADER_X2;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignStartUserScript.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignStartUserScript.java
new file mode 100644
index 00000000..af8846cb
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/behavior/script/impl/BpmTaskAssignStartUserScript.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.impl;
+
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
+import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
+import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import org.activiti.engine.impl.persistence.entity.TaskEntity;
+import org.springframework.stereotype.Component;
+
+import java.util.Set;
+
+/**
+ * 分配给发起人审批的 Script 实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class BpmTaskAssignStartUserScript implements BpmTaskAssignScript {
+
+ @Override
+ public Set calculateTaskCandidateUsers(TaskEntity task) {
+ Long userId = Long.parseLong(task.getProcessInstance().getStartUserId());
+ return SetUtils.asSet(userId);
+ }
+
+ @Override
+ public BpmTaskRuleScriptEnum getEnum() {
+ return BpmTaskRuleScriptEnum.START_USER;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/identity/EmptyUserGroupManager.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/identity/EmptyUserGroupManager.java
new file mode 100644
index 00000000..22786da1
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/identity/EmptyUserGroupManager.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.identity;
+
+import org.activiti.api.runtime.shared.identity.UserGroupManager;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 空的 UserGroupManager 实现类,用于禁用 Activiti 自带的用户组实现。
+ * 原因是,我们使用了自己实现的任务分配规则,所以不需要 Activiti。
+ * 如果不去禁用,会存在一些场景下,会去查询用户所在的用户组,导致报错。
+ *
+ * @author 芋道源码
+ */
+public class EmptyUserGroupManager implements UserGroupManager {
+
+ @Override
+ public List getUserGroups(String s) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getUserRoles(String s) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getGroups() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getUsers() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmProcessInstanceEventListener.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmProcessInstanceEventListener.java
new file mode 100644
index 00000000..6ed7297d
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmProcessInstanceEventListener.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.listener;
+
+import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+import org.activiti.api.model.shared.event.RuntimeEvent;
+import org.activiti.api.process.model.ProcessInstance;
+import org.activiti.api.process.model.events.ProcessRuntimeEvent;
+import org.activiti.api.process.runtime.events.ProcessCancelledEvent;
+import org.activiti.api.process.runtime.events.listener.ProcessRuntimeEventListener;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 监听 {@link ProcessInstance} 的开始与完成,创建与更新对应的 {@link BpmProcessInstanceExtDO} 记录
+ *
+ * @author 芋道源码
+ */
+@Component
+public class BpmProcessInstanceEventListener>
+ implements ProcessRuntimeEventListener {
+
+ @Resource
+ @Lazy // 解决循环依赖
+ private BpmProcessInstanceService processInstanceService;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void onEvent(T rawEvent) {
+ // 由于 ProcessRuntimeEventListener 无法保证只监听 ProcessRuntimeEvent 事件,所以通过这样的方式
+ if (!(rawEvent instanceof ProcessRuntimeEvent)) {
+ return;
+ }
+ ProcessRuntimeEvent event = (ProcessRuntimeEvent) rawEvent;
+
+ // 创建时,插入拓展表
+ if (event.getEventType() == ProcessRuntimeEvent.ProcessEvents.PROCESS_CREATED) {
+ processInstanceService.createProcessInstanceExt(event.getEntity());
+ return;
+ }
+ // 取消时,更新拓展表为取消
+ if (event.getEventType() == ProcessRuntimeEvent.ProcessEvents.PROCESS_CANCELLED) {
+ processInstanceService.updateProcessInstanceExtCancel(event.getEntity(),
+ ((ProcessCancelledEvent) event).getCause());
+ return;
+ }
+ // 完成时,更新拓展表为已完成
+ if (event.getEventType() == ProcessRuntimeEvent.ProcessEvents.PROCESS_COMPLETED) {
+ processInstanceService.updateProcessInstanceExtComplete(event.getEntity());
+ return;
+ }
+
+ // 其它事件,进行更新拓展表
+ processInstanceService.updateProcessInstanceExt(event.getEntity());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTackActivitiEventListener.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTackActivitiEventListener.java
new file mode 100644
index 00000000..4b6c21cf
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTackActivitiEventListener.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.listener;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import org.activiti.api.task.runtime.events.listener.TaskEventListener;
+import org.activiti.engine.delegate.event.ActivitiEvent;
+import org.activiti.engine.delegate.event.ActivitiEventListener;
+import org.activiti.engine.delegate.event.ActivitiEventType;
+import org.activiti.engine.delegate.event.impl.ActivitiEntityEventImpl;
+import org.activiti.engine.impl.persistence.entity.TaskEntity;
+import org.activiti.engine.repository.ProcessDefinition;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 监听 {@link TaskEntity} 相关的事件,设置相关属性。
+ * 目的:解决 {@link TaskEventListener} 无法解决的场景
+ *
+ * @author 芋道源码
+ */
+@Component
+public class BpmTackActivitiEventListener implements ActivitiEventListener {
+
+ @Resource
+ @Lazy // 解决循环依赖
+ private BpmProcessDefinitionService processDefinitionService;
+
+ @Override
+ public void onEvent(ActivitiEvent event) {
+ // Task 创建时,设置其分类,解决 TaskService 未提供 name 的设置方法
+ if (ActivitiEventType.TASK_CREATED == event.getType()) {
+ TaskEntity task = ((TaskEntity) ((ActivitiEntityEventImpl) event).getEntity());
+ if (StrUtil.isNotEmpty(task.getCategory())) {
+ return;
+ }
+ // 设置 name
+ ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition2(task.getProcessDefinitionId());
+ if (processDefinition == null) {
+ return;
+ }
+ task.setCategory(processDefinition.getCategory());
+ }
+ }
+
+ @Override
+ public boolean isFailOnException() {
+ return true;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTaskEventListener.java
new file mode 100644
index 00000000..da8c6238
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/BpmTaskEventListener.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.listener;
+
+import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
+import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
+import org.activiti.api.model.shared.event.RuntimeEvent;
+import org.activiti.api.task.model.Task;
+import org.activiti.api.task.model.events.TaskRuntimeEvent;
+import org.activiti.api.task.runtime.events.listener.TaskRuntimeEventListener;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 监听 {@link Task} 的开始与完成,创建与更新对应的 {@link BpmTaskExtDO} 记录
+ *
+ * @author 芋道源码
+ */
+@Component
+public class BpmTaskEventListener>
+ implements TaskRuntimeEventListener {
+
+ @Resource
+ @Lazy // 解决循环依赖
+ private BpmTaskService taskService;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void onEvent(T rawEvent) {
+ // 由于 TaskRuntimeEventListener 无法保证只监听 TaskRuntimeEvent 事件,所以通过这样的方式
+ if (!(rawEvent instanceof TaskRuntimeEvent)) {
+ return;
+ }
+ TaskRuntimeEvent event = (TaskRuntimeEvent) rawEvent;
+
+ // 创建时,插入拓展表
+ if (event.getEventType() == TaskRuntimeEvent.TaskEvents.TASK_CREATED) {
+ taskService.createTaskExt(event.getEntity());
+ return;
+ }
+
+ // 取消时,更新拓展表为取消
+ if (event.getEventType() == TaskRuntimeEvent.TaskEvents.TASK_CANCELLED) {
+ taskService.updateTaskExtCancel(event.getEntity());
+ return;
+ }
+ // 完成时,更新拓展表为已完成。要注意,在调用 delete ProcessInstance 才会触发该逻辑
+ if (event.getEventType() == TaskRuntimeEvent.TaskEvents.TASK_COMPLETED) {
+ taskService.updateTaskExtComplete(event.getEntity());
+ return;
+ }
+
+ // 审核人修改时,进行拓展表,并额外发送通知
+ if (event.getEventType() == TaskRuntimeEvent.TaskEvents.TASK_ASSIGNED) {
+ taskService.updateTaskExtAssign(event.getEntity());
+ return;
+ }
+
+ // 其它事件,进行更新拓展表
+ taskService.updateTaskExt(event.getEntity());
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/package-info.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/package-info.java
new file mode 100644
index 00000000..eca41904
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/activiti/core/listener/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * 自定义各种 Activiti 的监听器,实现流程示例、流程任务的拓展表信息的同步
+ * 例如说,{@link org.activiti.api.task.model.Task} 新建时,我们也要新建对应的 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO} 记录
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.bpm.framework.activiti.core.listener;
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java
new file mode 100644
index 00000000..52fdb7f9
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/framework/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 属于 bpm 模块的 framework 封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.bpm.framework;
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java
new file mode 100644
index 00000000..5713e495
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/package-info.java
@@ -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;
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
new file mode 100644
index 00000000..fea5398a
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
@@ -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.activiti.bpmn.model.BpmnModel;
+
+import javax.validation.Valid;
+
+/**
+ * 流程模型接口
+ *
+ * @author yunlongn
+ */
+public interface BpmModelService {
+ /**
+ * 获得流程模型分页
+ *
+ * @param pageVO 分页查询
+ * @return 流程模型分页
+ */
+ PageResult 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);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
new file mode 100644
index 00000000..bbeadd6b
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
@@ -0,0 +1,282 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+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.enums.definition.BpmModelFormTypeEnum;
+import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
+import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils;
+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.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.activiti.bpmn.model.BpmnModel;
+import org.activiti.engine.RepositoryService;
+import org.activiti.engine.impl.persistence.entity.SuspensionState;
+import org.activiti.engine.repository.Deployment;
+import org.activiti.engine.repository.Model;
+import org.activiti.engine.repository.ModelQuery;
+import org.activiti.engine.repository.ProcessDefinition;
+import org.springframework.context.annotation.Lazy;
+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 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.*;
+
+/**
+ * 流程定义实现
+ * 主要进行 Activiti {@link Model} 的维护
+ *
+ * @author yunlongn
+ * @author 芋道源码
+ */
+@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 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 models = modelQuery.orderByCreateTime().desc()
+ .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
+
+ // 获得 Form Map
+ Set formIds = CollectionUtils.convertSet(models, model -> {
+ BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
+ return metaInfo != null ? metaInfo.getFormId() : null;
+ });
+ Map formMap = bpmFormService.getFormMap(formIds);
+
+ // 获得 Deployment Map
+ Set deploymentIds = new HashSet<>();
+ models.forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
+ Map deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
+ // 获得 ProcessDefinition Map
+ List processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
+ Map processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
+
+ // 拼接结果
+ long modelCount = modelQuery.count();
+ return new PageResult<>(BpmModelConvert.INSTANCE.convertList(models, formMap, deploymentMap, processDefinitionMap), modelCount);
+ }
+
+ @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) // 因为进行多个 activiti 操作,所以开启事务
+ public String createModel(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();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作,所以开启事务
+ public void updateModel(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());
+ }
+
+ private void saveModelBpmnXml(Model model, String bpmnXml) {
+ if (StrUtil.isEmpty(bpmnXml)) {
+ return;
+ }
+ repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作,所以开启事务
+ 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());
+ }
+
+ private void updateProcessDefinitionSuspended(String deploymentId) {
+ if (StrUtil.isEmpty(deploymentId)) {
+ return;
+ }
+ ProcessDefinition oldDefinition = processDefinitionService.getProcessDefinitionByDeploymentId(deploymentId);
+ if (oldDefinition == null) {
+ return;
+ }
+ if(oldDefinition.isSuspended()) {
+ return;
+ }
+ processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
+ }
+
+ @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;
+ }
+ return ActivitiUtils.buildBpmnModel(bpmnBytes);
+ }
+
+ private Model getModelByKey(String key) {
+ return repositoryService.createModelQuery().modelKey(key).singleResult();
+ }
+
+ 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;
+ }
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
new file mode 100644
index 00000000..46be4b64
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionService.java
@@ -0,0 +1,161 @@
+package cn.iocoder.yudao.module.bpm.service.definition;
+
+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.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
+import org.activiti.bpmn.model.BpmnModel;
+import org.activiti.engine.repository.Deployment;
+import org.activiti.engine.repository.ProcessDefinition;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 流程定义接口
+ *
+ * @author yunlong.li
+ * @author ZJQ
+ * @author 芋道源码
+ */
+public interface BpmProcessDefinitionService {
+
+ /**
+ * 获得流程定义分页
+ *
+ * @param pageReqVO 分页入参
+ * @return 流程定义 Page
+ */
+ PageResult getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageReqVO);
+
+ /**
+ * 创建流程定义
+ *
+ * @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);
+
+ /**
+ * 获得流程定义列表
+ *
+ * @param listReqVO 列表入参
+ * @return 流程定义列表
+ */
+ List getProcessDefinitionList(BpmProcessDefinitionListReqVO listReqVO);
+
+ /**
+ * 获得 Bpmn 模型
+ *
+ * @param processDefinitionId 流程定义的编号
+ * @return Bpmn 模型
+ */
+ BpmnModel getBpmnModel(String processDefinitionId);
+
+ /**
+ * 获得编号对应的 ProcessDefinition
+ *
+ * @param id 编号
+ * @return 流程定义
+ */
+ ProcessDefinition getProcessDefinition(String id);
+
+ /**
+ * 获得编号对应的 ProcessDefinition
+ *
+ * 相比 {@link #getProcessDefinition(String)} 方法,category 的取值是正确
+ *
+ * @param id 编号
+ * @return 流程定义
+ */
+ ProcessDefinition getProcessDefinition2(String id);
+
+ /**
+ * 获得流程定义标识对应的激活的流程定义
+ *
+ * @param key 流程定义的标识
+ * @return 流程定义
+ */
+ ProcessDefinition getActiveProcessDefinition(String key);
+
+ /**
+ * 获得 id 对应的 Deployment
+ *
+ * @param id 部署编号
+ * @return 流程部署
+ */
+ Deployment getDeployment(String id);
+
+ /**
+ * 获得 ids 对应的 Deployment 数组
+ *
+ * @param ids 部署编号的数组
+ * @return 流程部署的数组
+ */
+ List getDeployments(Set ids);
+
+ /**
+ * 获得 ids 对应的 Deployment Map
+ *
+ * @param ids 部署编号的数组
+ * @return 流程部署 Map
+ */
+ default Map getDeploymentMap(Set ids) {
+ return CollectionUtils.convertMap(getDeployments(ids), Deployment::getId);
+ }
+
+ /**
+ * 获得 deploymentId 对应的 ProcessDefinition
+ *
+ * @param deploymentId 部署编号
+ * @return 流程定义
+ */
+ ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId);
+
+ /**
+ * 获得 deploymentIds 对应的 ProcessDefinition 数组
+ *
+ * @param deploymentIds 部署编号的数组
+ * @return 流程定义的数组
+ */
+ List getProcessDefinitionListByDeploymentIds(Set deploymentIds);
+
+}
diff --git a/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
new file mode 100644
index 00000000..011a948b
--- /dev/null
+++ b/yudao-module-bpm/yudao-module-bpm-impl-activiti/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
@@ -0,0 +1,267 @@
+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.module.bpm.convert.definition.BpmProcessDefinitionConvert;
+import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.PageUtils;
+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.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.activiti.bpmn.model.BpmnModel;
+import org.activiti.engine.RepositoryService;
+import org.activiti.engine.impl.persistence.entity.SuspensionState;
+import org.activiti.engine.repository.Deployment;
+import org.activiti.engine.repository.ProcessDefinition;
+import org.activiti.engine.repository.ProcessDefinitionQuery;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+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.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;
+
+/**
+ * 流程定义实现
+ * 主要进行 Activiti {@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 BpmFormService formService;
+
+ @Resource
+ private BpmProcessDefinitionExtMapper processDefinitionMapper;
+
+ @Override
+ public PageResult getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) {
+ ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
+ if (StrUtil.isNotBlank(pageVO.getKey())) {
+ definitionQuery.processDefinitionKey(pageVO.getKey());
+ }
+ // 执行查询
+ List processDefinitions = definitionQuery.orderByProcessDefinitionVersion().desc()
+ .listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
+ if (CollUtil.isEmpty(processDefinitions)) {
+ return new PageResult<>(emptyList(), definitionQuery.count());
+ }
+
+ // 获得 Deployment Map
+ Set deploymentIds = new HashSet<>();
+ processDefinitions.forEach(definition -> addIfNotNull(deploymentIds, definition.getDeploymentId()));
+ Map deploymentMap = getDeploymentMap(deploymentIds);
+
+ // 获得 BpmProcessDefinitionDO Map
+ List processDefinitionDOs = processDefinitionMapper.selectListByProcessDefinitionIds(
+ convertList(processDefinitions, ProcessDefinition::getId));
+ Map processDefinitionDOMap = convertMap(processDefinitionDOs,
+ BpmProcessDefinitionExtDO::getProcessDefinitionId);
+
+ // 获得 Form Map
+ Set formIds = convertSet(processDefinitionDOs, BpmProcessDefinitionExtDO::getFormId);
+ Map formMap = formService.getFormMap(formIds);
+
+ // 拼接结果
+ long definitionCount = definitionQuery.count();
+ return new PageResult<>(BpmProcessDefinitionConvert.INSTANCE.convertList(processDefinitions, deploymentMap,
+ processDefinitionDOMap, formMap), definitionCount);
+ }
+
+ @Override
+ public List 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 processDefinitions = definitionQuery.list();
+ if (CollUtil.isEmpty(processDefinitions)) {
+ return Collections.emptyList();
+ }
+
+ // 获得 BpmProcessDefinitionDO Map
+ List