liyanbo 2 mesi fa
parent
commit
86f0d8e339
100 ha cambiato i file con 5851 aggiunte e 1 eliminazioni
  1. 15 1
      byzs-dependencies/pom.xml
  2. 77 0
      byzs-module-bpm/pom.xml
  3. 41 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/event/BpmProcessInstanceStatusEvent.java
  4. 34 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/event/BpmProcessInstanceStatusEventListener.java
  5. 4 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/package-info.java
  6. 24 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/BpmProcessInstanceApi.java
  7. 28 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/BpmProcessInstanceApiImpl.java
  8. 21 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/BpmProcessTaskApi.java
  9. 25 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/BpmProcessTaskApiImpl.java
  10. 44 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java
  11. 15 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java
  12. 4 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/base/package-info.java
  13. 22 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java
  14. 95 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmCategoryController.java
  15. 83 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmFormController.java
  16. 201 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmModelController.java
  17. 133 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java
  18. 73 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmProcessExpressionController.java
  19. 73 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmProcessListenerController.java
  20. 83 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmUserGroupController.java
  21. 32 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/category/BpmCategoryPageReqVO.java
  22. 33 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/category/BpmCategoryRespVO.java
  23. 37 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/category/BpmCategorySaveReqVO.java
  24. 33 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionPageReqVO.java
  25. 30 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java
  26. 27 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionSaveReqVO.java
  27. 24 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/form/BpmFormFieldVO.java
  28. 14 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java
  29. 39 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java
  30. 34 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/form/BpmFormSaveReqVO.java
  31. 28 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java
  32. 31 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java
  33. 30 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSaveReqVO.java
  34. 30 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerPageReqVO.java
  35. 36 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerRespVO.java
  36. 39 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerSaveReqVO.java
  37. 19 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java
  38. 181 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
  39. 57 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java
  40. 34 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java
  41. 20 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java
  42. 527 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
  43. 23 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java
  44. 14 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java
  45. 71 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java
  46. 12 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/BpmOALeaveController.http
  47. 62 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/BpmOALeaveController.java
  48. 5 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/package-info.java
  49. 43 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java
  50. 29 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java
  51. 36 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java
  52. 16 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/BpmProcessInstanceController.http
  53. 200 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/BpmProcessInstanceController.java
  54. 89 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java
  55. 240 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/BpmTaskController.java
  56. 25 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java
  57. 48 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java
  58. 40 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
  59. 112 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
  60. 43 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java
  61. 20 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java
  62. 23 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java
  63. 24 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java
  64. 45 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java
  65. 86 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java
  66. 30 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
  67. 23 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java
  68. 24 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java
  69. 28 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java
  70. 19 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java
  71. 128 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java
  72. 24 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java
  73. 29 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskSignCreateReqVO.java
  74. 20 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskSignDeleteReqVO.java
  75. 25 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskTransferReqVO.java
  76. 4 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/app/package-info.java
  77. 6 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/package-info.java
  78. 132 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/definition/BpmModelConvert.java
  79. 99 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/definition/BpmProcessDefinitionConvert.java
  80. 21 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/message/BpmMessageConvert.java
  81. 6 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/package-info.java
  82. 295 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/task/BpmProcessInstanceConvert.java
  83. 221 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/task/BpmTaskConvert.java
  84. 54 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmCategoryDO.java
  85. 60 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmFormDO.java
  86. 222 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
  87. 44 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java
  88. 74 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java
  89. 55 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java
  90. 77 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java
  91. 97 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java
  92. 46 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/category/BpmCategoryMapper.java
  93. 25 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmFormMapper.java
  94. 27 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java
  95. 26 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmProcessExpressionMapper.java
  96. 27 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmProcessListenerMapper.java
  97. 32 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java
  98. 29 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java
  99. 25 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java
  100. 61 0
      byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/redis/BpmProcessIdRedisDAO.java

+ 15 - 1
byzs-dependencies/pom.xml

@@ -74,6 +74,8 @@
         <justauth-starter.version>1.4.0</justauth-starter.version>
         <jimureport.version>1.9.4</jimureport.version>
         <weixin-java.version>4.7.5.B</weixin-java.version>
+        <!-- Flowable 工作流相关 -->
+        <flowable.version>7.0.1</flowable.version>
     </properties>
 
     <dependencyManagement>
@@ -616,6 +618,18 @@
                 <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
                 <version>${mqtt.version}</version>
             </dependency>
+            
+            <!-- Flowable 工作流相关 -->
+            <dependency>
+                <groupId>org.flowable</groupId>
+                <artifactId>flowable-spring-boot-starter-process</artifactId>
+                <version>${flowable.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.flowable</groupId>
+                <artifactId>flowable-spring-boot-starter-actuator</artifactId>
+                <version>${flowable.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
@@ -650,4 +664,4 @@
         </plugins>
     </build>
 
-</project>
+</project>

+ 77 - 0
byzs-module-bpm/pom.xml

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>byzs-bjdx</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>byzs-module-bpm</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-module-system</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+        <!-- 业务组件 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-spring-boot-starter-biz-data-permission</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-spring-boot-starter-biz-tenant</artifactId>
+        </dependency>
+
+        <!-- Web 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-spring-boot-starter-security</artifactId>
+        </dependency>
+
+        <!-- DB 相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-spring-boot-starter-mybatis</artifactId>
+        </dependency>
+
+        <!-- Test 测试相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-spring-boot-starter-test</artifactId>
+        </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>byzs-spring-boot-starter-excel</artifactId>
+        </dependency>
+
+        <!-- Flowable 工作流相关 -->
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-spring-boot-starter-process</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.flowable</groupId>
+            <artifactId>flowable-spring-boot-starter-actuator</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 41 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/event/BpmProcessInstanceStatusEvent.java

@@ -0,0 +1,41 @@
+package cn.iocoder.byzs.module.bpm.api.event;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * 流程实例的状态(结果)发生变化的 Event
+ *
+ * @author 博雅智算源码
+ */
+@SuppressWarnings("ALL")
+@Data
+public class BpmProcessInstanceStatusEvent extends ApplicationEvent {
+
+    /**
+     * 流程实例的编号
+     */
+    @NotNull(message = "流程实例的编号不能为空")
+    private String id;
+    /**
+     * 流程实例的 key
+     */
+    @NotNull(message = "流程实例的 key 不能为空")
+    private String processDefinitionKey;
+    /**
+     * 流程实例的结果
+     */
+    @NotNull(message = "流程实例的状态不能为空")
+    private Integer status;
+    /**
+     * 流程实例对应的业务标识
+     * 例如说,请假
+     */
+    private String businessKey;
+
+    public BpmProcessInstanceStatusEvent(Object source) {
+        super(source);
+    }
+
+}

+ 34 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/event/BpmProcessInstanceStatusEventListener.java

@@ -0,0 +1,34 @@
+package cn.iocoder.byzs.module.bpm.api.event;
+
+import cn.hutool.core.util.StrUtil;
+import org.springframework.context.ApplicationListener;
+
+/**
+ * {@link BpmProcessInstanceStatusEvent} 的监听器
+ *
+ * @author 博雅智算源码
+ */
+public abstract class BpmProcessInstanceStatusEventListener
+        implements ApplicationListener<BpmProcessInstanceStatusEvent> {
+
+    @Override
+    public final void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
+        if (!StrUtil.equals(event.getProcessDefinitionKey(), getProcessDefinitionKey())) {
+            return;
+        }
+        onEvent(event);
+    }
+
+    /**
+     * @return 返回监听的流程定义 Key
+     */
+    protected abstract String getProcessDefinitionKey();
+
+    /**
+     * 处理事件
+     *
+     * @param event 事件
+     */
+    protected abstract void onEvent(BpmProcessInstanceStatusEvent event);
+
+}

+ 4 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * bpm API 包,定义并实现提供给其它模块的 API
+ */
+package cn.iocoder.byzs.module.bpm.api;

+ 24 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/BpmProcessInstanceApi.java

@@ -0,0 +1,24 @@
+package cn.iocoder.byzs.module.bpm.api.task;
+
+import cn.iocoder.byzs.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import jakarta.validation.Valid;
+
+/**
+ * 流程实例 Api 接口
+ *
+ * @author 博雅智算源码
+ */
+public interface BpmProcessInstanceApi {
+
+    /**
+     * 创建流程实例(提供给内部)
+     *
+     * @param userId 用户编号
+     * @param reqDTO 创建信息
+     * @return 实例的编号
+     */
+    String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO);
+
+
+
+}

+ 28 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/BpmProcessInstanceApiImpl.java

@@ -0,0 +1,28 @@
+package cn.iocoder.byzs.module.bpm.api.task;
+
+import cn.iocoder.byzs.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.byzs.module.bpm.service.task.BpmProcessInstanceService;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * Flowable 流程实例 Api 实现类
+ *
+ * @author 博雅智算源码
+ * @author jason
+ */
+@Service
+@Validated
+public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi {
+
+    @Resource
+    private BpmProcessInstanceService processInstanceService;
+
+    @Override
+    public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) {
+        return processInstanceService.createProcessInstance(userId, reqDTO);
+    }
+
+}

+ 21 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/BpmProcessTaskApi.java

@@ -0,0 +1,21 @@
+package cn.iocoder.byzs.module.bpm.api.task;
+
+import jakarta.validation.constraints.NotEmpty;
+
+/**
+ * 流程任务 Api 接口
+ *
+ * @author jason
+ */
+public interface BpmProcessTaskApi {
+
+    /**
+     * 触发流程任务的执行
+     *
+     * @param processInstanceId 流程实例编号
+     * @param taskDefineKey 任务 Key
+     */
+    void triggerTask(@NotEmpty(message = "流程实例的编号不能为空") String processInstanceId,
+                     @NotEmpty(message = "任务 Key 不能为空") String taskDefineKey);
+
+}

+ 25 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/BpmProcessTaskApiImpl.java

@@ -0,0 +1,25 @@
+package cn.iocoder.byzs.module.bpm.api.task;
+
+import cn.iocoder.byzs.module.bpm.service.task.BpmTaskService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * 流程任务 Api 实现类
+ *
+ * @author jason
+ */
+@Service
+@Validated
+public class BpmProcessTaskApiImpl implements BpmProcessTaskApi {
+
+    @Resource
+    private BpmTaskService bpmTaskService;
+
+    @Override
+    public void triggerTask(String processInstanceId, String taskDefineKey) {
+        bpmTaskService.triggerTask(processInstanceId, taskDefineKey);
+    }
+
+}

+ 44 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java

@@ -0,0 +1,44 @@
+package cn.iocoder.byzs.module.bpm.api.task.dto;
+
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 流程实例的创建 Request DTO
+ *
+ * @author 博雅智算源码
+ */
+@Data
+public class BpmProcessInstanceCreateReqDTO {
+
+    /**
+     * 流程定义的标识
+     */
+    @NotEmpty(message = "流程定义的标识不能为空")
+    private String processDefinitionKey;
+    /**
+     * 变量实例(动态表单)
+     */
+    private Map<String, Object> variables;
+
+    /**
+     * 业务的唯一标识
+     *
+     * 例如说,请假申请的编号。通过它,可以查询到对应的实例
+     */
+    @NotEmpty(message = "业务的唯一标识")
+    private String businessKey;
+
+    /**
+     * 发起人自选审批人 Map
+     *
+     * key:taskKey 任务编码
+     * value:审批人的数组
+     * 例如:{ taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批
+     */
+    private Map<String, List<Long>> startUserSelectAssignees;
+
+}

+ 15 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/base/dept/DeptSimpleBaseVO.java

@@ -0,0 +1,15 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.base.dept;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "部门精简信息 VO")
+@Data
+public class DeptSimpleBaseVO {
+
+    @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+    @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "技术部")
+    private String name;
+
+}

+ 4 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/base/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 基础包,放一些通用的 VO 类
+ */
+package cn.iocoder.byzs.module.bpm.controller.admin.base;

+ 22 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/base/user/UserSimpleBaseVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.base.user;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户精简信息 VO")
+@Data
+public class UserSimpleBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+    @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "lyb")
+    private String nickname;
+    @Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
+    private String avatar;
+
+    @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long deptId;
+    @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
+    private String deptName;
+
+}

+ 95 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmCategoryController.java

@@ -0,0 +1,95 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.category.BpmCategoryRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmCategoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - BPM 流程分类")
+@RestController
+@RequestMapping("/bpm/category")
+@Validated
+public class BpmCategoryController {
+
+    @Resource
+    private BpmCategoryService categoryService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建流程分类")
+    @PreAuthorize("@ss.hasPermission('bpm:category:create')")
+    public CommonResult<Long> createCategory(@Valid @RequestBody BpmCategorySaveReqVO createReqVO) {
+        return success(categoryService.createCategory(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新流程分类")
+    @PreAuthorize("@ss.hasPermission('bpm:category:update')")
+    public CommonResult<Boolean> updateCategory(@Valid @RequestBody BpmCategorySaveReqVO updateReqVO) {
+        categoryService.updateCategory(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/update-sort-batch")
+    @Operation(summary = "批量更新流程分类的排序")
+    @Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3")
+    @PreAuthorize("@ss.hasPermission('bpm:category:update')")
+    public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) {
+        categoryService.updateCategorySortBatch(ids);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除流程分类")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:category:delete')")
+    public CommonResult<Boolean> deleteCategory(@RequestParam("id") Long id) {
+        categoryService.deleteCategory(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得流程分类")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:category:query')")
+    public CommonResult<BpmCategoryRespVO> getCategory(@RequestParam("id") Long id) {
+        BpmCategoryDO category = categoryService.getCategory(id);
+        return success(BeanUtils.toBean(category, BpmCategoryRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得流程分类分页")
+    @PreAuthorize("@ss.hasPermission('bpm:category:query')")
+    public CommonResult<PageResult<BpmCategoryRespVO>> getCategoryPage(@Valid BpmCategoryPageReqVO pageReqVO) {
+        PageResult<BpmCategoryDO> pageResult = categoryService.getCategoryPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, BpmCategoryRespVO.class));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "获取流程分类的精简信息列表", description = "只包含被开启的分类,主要用于前端的下拉选项")
+    public CommonResult<List<BpmCategoryRespVO>> getCategorySimpleList() {
+        List<BpmCategoryDO> list = categoryService.getCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        list.sort(Comparator.comparingInt(BpmCategoryDO::getSort));
+        return success(convertList(list, category -> new BpmCategoryRespVO().setId(category.getId())
+                .setName(category.getName()).setCode(category.getCode())));
+    }
+
+}

+ 83 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmFormController.java

@@ -0,0 +1,83 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition;
+
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.form.BpmFormRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmFormService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - 动态表单")
+@RestController
+@RequestMapping("/bpm/form")
+@Validated
+public class BpmFormController {
+
+    @Resource
+    private BpmFormService formService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建动态表单")
+    @PreAuthorize("@ss.hasPermission('bpm:form:create')")
+    public CommonResult<Long> createForm(@Valid @RequestBody BpmFormSaveReqVO createReqVO) {
+        return success(formService.createForm(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新动态表单")
+    @PreAuthorize("@ss.hasPermission('bpm:form:update')")
+    public CommonResult<Boolean> updateForm(@Valid @RequestBody BpmFormSaveReqVO updateReqVO) {
+        formService.updateForm(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除动态表单")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:form:delete')")
+    public CommonResult<Boolean> deleteForm(@RequestParam("id") Long id) {
+        formService.deleteForm(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得动态表单")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:form:query')")
+    public CommonResult<BpmFormRespVO> getForm(@RequestParam("id") Long id) {
+        BpmFormDO form = formService.getForm(id);
+        return success(BeanUtils.toBean(form, BpmFormRespVO.class));
+    }
+
+    @GetMapping({"/list-all-simple", "/simple-list"})
+    @Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框")
+    public CommonResult<List<BpmFormRespVO>> getFormSimpleList() {
+        List<BpmFormDO> list = formService.getFormList();
+        return success(convertList(list, formDO -> // 只返回 id、name 字段
+                new BpmFormRespVO().setId(formDO.getId()).setName(formDO.getName())));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得动态表单分页")
+    @PreAuthorize("@ss.hasPermission('bpm:form:query')")
+    public CommonResult<PageResult<BpmFormRespVO>> getFormPage(@Valid BpmFormPageReqVO pageVO) {
+        PageResult<BpmFormDO> pageResult = formService.getFormPage(pageVO);
+        return success(BeanUtils.toBean(pageResult, BpmFormRespVO.class));
+    }
+
+}

+ 201 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmModelController.java

@@ -0,0 +1,201 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.*;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.*;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
+import cn.iocoder.byzs.module.bpm.convert.definition.BpmModelConvert;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmFormService;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmModelService;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.byzs.module.system.api.dept.DeptApi;
+import cn.iocoder.byzs.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.byzs.module.system.api.user.AdminUserApi;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.Model;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.*;
+
+@Tag(name = "管理后台 - 流程模型")
+@RestController
+@RequestMapping("/bpm/model")
+@Validated
+public class BpmModelController {
+
+    @Resource
+    private BpmModelService modelService;
+    @Resource
+    private BpmFormService formService;
+    @Resource
+    private BpmCategoryService categoryService;
+    @Resource
+    private BpmProcessDefinitionService processDefinitionService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
+
+    @GetMapping("/list")
+    @Operation(summary = "获得模型分页")
+    @Parameter(name = "name", description = "模型名称", example = "lyb")
+    public CommonResult<List<BpmModelRespVO>> getModelList(@RequestParam(value = "name", required = false) String name) {
+        List<Model> list = modelService.getModelList(name);
+        if (CollUtil.isEmpty(list)) {
+            return success(Collections.emptyList());
+        }
+
+        // 获得 Form 表单
+        Set<Long> formIds = convertSet(list, model -> {
+            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
+            return metaInfo != null ? metaInfo.getFormId() : null;
+        });
+        Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
+        // 获得 Category Map
+        Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
+                convertSet(list, Model::getCategory));
+        // 获得 Deployment Map
+        Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
+                convertSet(list, Model::getDeploymentId));
+        // 获得 ProcessDefinition Map
+        List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
+                deploymentMap.keySet());
+        Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
+        // 获得 User Map、Dept Map
+        Set<Long> userIds = convertSetByFlatMap(list, model -> {
+            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
+            return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
+        });
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+        Set<Long> deptIds = convertSetByFlatMap(list, model -> {
+            BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
+            return metaInfo != null && metaInfo.getStartDeptIds() != null ? metaInfo.getStartDeptIds().stream() : Stream.empty();
+        });
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+        return success(BpmModelConvert.INSTANCE.buildModelList(list,
+                formMap, categoryMap, deploymentMap, processDefinitionMap, userMap, deptMap));
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得模型")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:model:query')")
+    public CommonResult<BpmModelRespVO> getModel(@RequestParam("id") String id) {
+        Model model = modelService.getModel(id);
+        if (model == null) {
+            return null;
+        }
+        byte[] bpmnBytes = modelService.getModelBpmnXML(id);
+        BpmSimpleModelNodeVO simpleModel = modelService.getSimpleModel(id);
+        return success(BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes, simpleModel));
+    }
+
+    @PostMapping("/create")
+    @Operation(summary = "新建模型")
+    @PreAuthorize("@ss.hasPermission('bpm:model:create')")
+    public CommonResult<String> createModel(@Valid @RequestBody BpmModelSaveReqVO createRetVO) {
+        return success(modelService.createModel(createRetVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "修改模型")
+    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+    public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelSaveReqVO modelVO) {
+        modelService.updateModel(getLoginUserId(), modelVO);
+        return success(true);
+    }
+
+    @PutMapping("/update-sort-batch")
+    @Operation(summary = "批量修改模型排序")
+    @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3")
+    public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) {
+        modelService.updateModelSortBatch(getLoginUserId(), ids);
+        return success(true);
+    }
+
+    @PostMapping("/deploy")
+    @Operation(summary = "部署模型")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:model:deploy')")
+    public CommonResult<Boolean> deployModel(@RequestParam("id") String id) {
+        modelService.deployModel(getLoginUserId(), id);
+        return success(true);
+    }
+
+    @PutMapping("/update-state")
+    @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态")
+    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+    public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
+        modelService.updateModelState(getLoginUserId(), reqVO.getId(), reqVO.getState());
+        return success(true);
+    }
+
+    @Deprecated
+    @PutMapping("/update-bpmn")
+    @Operation(summary = "修改模型的 BPMN")
+    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+    public CommonResult<Boolean> updateModelBpmn(@Valid @RequestBody BpmModeUpdateBpmnReqVO reqVO) {
+        modelService.updateModelBpmnXml(reqVO.getId(), reqVO.getBpmnXml());
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除模型")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:model:delete')")
+    public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) {
+        modelService.deleteModel(getLoginUserId(), id);
+        return success(true);
+    }
+
+    @DeleteMapping("/clean")
+    @Operation(summary = "清理模型")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:model:clean')")
+    public CommonResult<Boolean> cleanModel(@RequestParam("id") String id) {
+        modelService.cleanModel(getLoginUserId(), id);
+        return success(true);
+    }
+
+    // ========== 仿钉钉/飞书的精简模型 =========
+
+    @GetMapping("/simple/get")
+    @Operation(summary = "获得仿钉钉流程设计模型")
+    @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a")
+    public CommonResult<BpmSimpleModelNodeVO> getSimpleModel(@RequestParam("id") String modelId){
+        return success(modelService.getSimpleModel(modelId));
+    }
+
+    @Deprecated
+    @PostMapping("/simple/update")
+    @Operation(summary = "保存仿钉钉流程设计模型")
+    @PreAuthorize("@ss.hasPermission('bpm:model:update')")
+    public CommonResult<Boolean> updateSimpleModel(@Valid @RequestBody BpmSimpleModelUpdateReqVO reqVO) {
+        modelService.updateSimpleModel(getLoginUserId(), reqVO);
+        return success(Boolean.TRUE);
+    }
+
+}

+ 133 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmProcessDefinitionController.java

@@ -0,0 +1,133 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.byzs.module.bpm.convert.definition.BpmProcessDefinitionConvert;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmFormService;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmProcessDefinitionService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.common.engine.impl.db.SuspensionState;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.ProcessDefinition;
+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 java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - 流程定义")
+@RestController
+@RequestMapping("/bpm/process-definition")
+@Validated
+public class BpmProcessDefinitionController {
+
+    @Resource
+    private BpmProcessDefinitionService processDefinitionService;
+    @Resource
+    private BpmFormService formService;
+    @Resource
+    private BpmCategoryService categoryService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得流程定义分页")
+    @PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
+    public CommonResult<PageResult<BpmProcessDefinitionRespVO>> getProcessDefinitionPage(
+            BpmProcessDefinitionPageReqVO pageReqVO) {
+        PageResult<ProcessDefinition> pageResult = processDefinitionService.getProcessDefinitionPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+
+        // 获得 Category Map
+        Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
+                convertSet(pageResult.getList(), ProcessDefinition::getCategory));
+        // 获得 Deployment Map
+        Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
+                convertSet(pageResult.getList(), ProcessDefinition::getDeploymentId));
+        // 获得 BpmProcessDefinitionInfoDO Map
+        Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
+                convertSet(pageResult.getList(), ProcessDefinition::getId));
+        // 获得 Form Map
+        Map<Long, BpmFormDO> formMap = formService.getFormMap(
+               convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfoDO::getFormId));
+        return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionPage(
+                pageResult, deploymentMap, processDefinitionMap, formMap, categoryMap));
+    }
+
+    @GetMapping ("/list")
+    @Operation(summary = "获得流程定义列表")
+    @Parameter(name = "suspensionState", description = "挂起状态", required = true, example = "1") // 参见 Flowable SuspensionState 枚举
+    public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList(
+            @RequestParam("suspensionState") Integer suspensionState) {
+        // 1.1 获得开启的流程定义
+        List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState);
+        if (CollUtil.isEmpty(list)) {
+            return success(Collections.emptyList());
+        }
+        // 1.2 移除不可见的流程定义
+        Map<String, BpmProcessDefinitionInfoDO> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
+                convertSet(list, ProcessDefinition::getId));
+        Long userId = getLoginUserId();
+        list.removeIf(processDefinition -> {
+            BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionMap.get(processDefinition.getId());
+            return processDefinitionInfo == null // 不存在
+                    || Boolean.FALSE.equals(processDefinitionInfo.getVisible()) // visible 不可见
+                    || !processDefinitionService.canUserStartProcessDefinition(processDefinitionInfo, userId); // 无权限发起
+        });
+
+        // 2. 拼接 VO 返回
+        return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList(
+                list, null, processDefinitionMap, null, null));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "获得流程定义精简列表", description = "只包含未挂起的流程,主要用于前端的下拉选项")
+    public CommonResult<List<BpmProcessDefinitionRespVO>> getSimpleProcessDefinitionList() {
+        // 只查询未挂起的流程
+        List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(
+                SuspensionState.ACTIVE.getStateCode());
+        // 拼接 VO 返回,只返回 id、name、key
+        return success(convertList(list, definition -> new BpmProcessDefinitionRespVO()
+                .setId(definition.getId()).setName(definition.getName()).setKey(definition.getKey())));
+    }
+
+    @GetMapping ("/get")
+    @Operation(summary = "获得流程定义")
+    @Parameter(name = "id", description = "流程编号", required = true, example = "1024")
+    @Parameter(name = "key", description = "流程定义标识", required = true, example = "1024")
+    public CommonResult<BpmProcessDefinitionRespVO> getProcessDefinition(
+            @RequestParam(value = "id", required = false) String id,
+            @RequestParam(value = "key", required = false) String key) {
+        ProcessDefinition processDefinition = id != null ? processDefinitionService.getProcessDefinition(id)
+                : processDefinitionService.getActiveProcessDefinition(key);
+        if (processDefinition == null) {
+            return success(null);
+        }
+        BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());
+        BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
+        return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
+                processDefinition, null, processDefinitionInfo, null, null, bpmnModel));
+    }
+
+}

+ 73 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmProcessExpressionController.java

@@ -0,0 +1,73 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition;
+
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionPageReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionSaveReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessExpressionDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmProcessExpressionService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - BPM 流程表达式")
+@RestController
+@RequestMapping("/bpm/process-expression")
+@Validated
+public class BpmProcessExpressionController {
+
+    @Resource
+    private BpmProcessExpressionService processExpressionService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建流程表达式")
+    @PreAuthorize("@ss.hasPermission('bpm:process-expression:create')")
+    public CommonResult<Long> createProcessExpression(@Valid @RequestBody BpmProcessExpressionSaveReqVO createReqVO) {
+        return success(processExpressionService.createProcessExpression(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新流程表达式")
+    @PreAuthorize("@ss.hasPermission('bpm:process-expression:update')")
+    public CommonResult<Boolean> updateProcessExpression(@Valid @RequestBody BpmProcessExpressionSaveReqVO updateReqVO) {
+        processExpressionService.updateProcessExpression(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除流程表达式")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:process-expression:delete')")
+    public CommonResult<Boolean> deleteProcessExpression(@RequestParam("id") Long id) {
+        processExpressionService.deleteProcessExpression(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得流程表达式")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:process-expression:query')")
+    public CommonResult<BpmProcessExpressionRespVO> getProcessExpression(@RequestParam("id") Long id) {
+        BpmProcessExpressionDO processExpression = processExpressionService.getProcessExpression(id);
+        return success(BeanUtils.toBean(processExpression, BpmProcessExpressionRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得流程表达式分页")
+    @PreAuthorize("@ss.hasPermission('bpm:process-expression:query')")
+    public CommonResult<PageResult<BpmProcessExpressionRespVO>> getProcessExpressionPage(
+            @Valid BpmProcessExpressionPageReqVO pageReqVO) {
+        PageResult<BpmProcessExpressionDO> pageResult = processExpressionService.getProcessExpressionPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, BpmProcessExpressionRespVO.class));
+    }
+
+}

+ 73 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmProcessListenerController.java

@@ -0,0 +1,73 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition;
+
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerPageReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerSaveReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessListenerDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmProcessListenerService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - BPM 流程监听器")
+@RestController
+@RequestMapping("/bpm/process-listener")
+@Validated
+public class BpmProcessListenerController {
+
+    @Resource
+    private BpmProcessListenerService processListenerService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建流程监听器")
+    @PreAuthorize("@ss.hasPermission('bpm:process-listener:create')")
+    public CommonResult<Long> createProcessListener(@Valid @RequestBody BpmProcessListenerSaveReqVO createReqVO) {
+        return success(processListenerService.createProcessListener(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新流程监听器")
+    @PreAuthorize("@ss.hasPermission('bpm:process-listener:update')")
+    public CommonResult<Boolean> updateProcessListener(@Valid @RequestBody BpmProcessListenerSaveReqVO updateReqVO) {
+        processListenerService.updateProcessListener(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除流程监听器")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:process-listener:delete')")
+    public CommonResult<Boolean> deleteProcessListener(@RequestParam("id") Long id) {
+        processListenerService.deleteProcessListener(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得流程监听器")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:process-listener:query')")
+    public CommonResult<BpmProcessListenerRespVO> getProcessListener(@RequestParam("id") Long id) {
+        BpmProcessListenerDO processListener = processListenerService.getProcessListener(id);
+        return success(BeanUtils.toBean(processListener, BpmProcessListenerRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得流程监听器分页")
+    @PreAuthorize("@ss.hasPermission('bpm:process-listener:query')")
+    public CommonResult<PageResult<BpmProcessListenerRespVO>> getProcessListenerPage(
+            @Valid BpmProcessListenerPageReqVO pageReqVO) {
+        PageResult<BpmProcessListenerDO> pageResult = processListenerService.getProcessListenerPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, BpmProcessListenerRespVO.class));
+    }
+
+}

+ 83 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/BpmUserGroupController.java

@@ -0,0 +1,83 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.group.BpmUserGroupRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.group.BpmUserGroupSaveReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmUserGroupService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - 用户组")
+@RestController
+@RequestMapping("/bpm/user-group")
+@Validated
+public class BpmUserGroupController {
+
+    @Resource
+    private BpmUserGroupService userGroupService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建用户组")
+    @PreAuthorize("@ss.hasPermission('bpm:user-group:create')")
+    public CommonResult<Long> createUserGroup(@Valid @RequestBody BpmUserGroupSaveReqVO createReqVO) {
+        return success(userGroupService.createUserGroup(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新用户组")
+    @PreAuthorize("@ss.hasPermission('bpm:user-group:update')")
+    public CommonResult<Boolean> updateUserGroup(@Valid @RequestBody BpmUserGroupSaveReqVO updateReqVO) {
+        userGroupService.updateUserGroup(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除用户组")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:user-group:delete')")
+    public CommonResult<Boolean> deleteUserGroup(@RequestParam("id") Long id) {
+        userGroupService.deleteUserGroup(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得用户组")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:user-group:query')")
+    public CommonResult<BpmUserGroupRespVO> getUserGroup(@RequestParam("id") Long id) {
+        BpmUserGroupDO userGroup = userGroupService.getUserGroup(id);
+        return success(BeanUtils.toBean(userGroup, BpmUserGroupRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得用户组分页")
+    @PreAuthorize("@ss.hasPermission('bpm:user-group:query')")
+    public CommonResult<PageResult<BpmUserGroupRespVO>> getUserGroupPage(@Valid BpmUserGroupPageReqVO pageVO) {
+        PageResult<BpmUserGroupDO> pageResult = userGroupService.getUserGroupPage(pageVO);
+        return success(BeanUtils.toBean(pageResult, BpmUserGroupRespVO.class));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "获取用户组精简信息列表", description = "只包含被开启的用户组,主要用于前端的下拉选项")
+    public CommonResult<List<BpmUserGroupRespVO>> getUserGroupSimpleList() {
+        List<BpmUserGroupDO> list = userGroupService.getUserGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        return success(convertList(list, group -> new BpmUserGroupRespVO().setId(group.getId()).setName(group.getName())));
+    }
+
+}

+ 32 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/category/BpmCategoryPageReqVO.java

@@ -0,0 +1,32 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.category;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.byzs.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - BPM 流程分类分页 Request VO")
+@Data
+public class BpmCategoryPageReqVO extends PageParam {
+
+    @Schema(description = "分类名", example = "王五")
+    private String name;
+
+    @Schema(description = "分类标志", example = "OA")
+    private String code;
+
+    @Schema(description = "分类状态", example = "1")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 33 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/category/BpmCategoryRespVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.category;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - BPM 流程分类 Response VO")
+@Data
+public class BpmCategoryRespVO {
+
+    @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
+    private Long id;
+
+    @Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    private String name;
+
+    @Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA")
+    private String code;
+
+    @Schema(description = "分类描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜")
+    private String description;
+
+    @Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Integer sort;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 37 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/category/BpmCategorySaveReqVO.java

@@ -0,0 +1,37 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.category;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - BPM 流程分类新增/修改 Request VO")
+@Data
+public class BpmCategorySaveReqVO {
+
+    @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
+    private Long id;
+
+    @Schema(description = "分类名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+    @NotEmpty(message = "分类名不能为空")
+    private String name;
+
+    @Schema(description = "分类描述", example = "你猜")
+    private String description;
+
+    @Schema(description = "分类标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "OA")
+    @NotEmpty(message = "分类标志不能为空")
+    private String code;
+
+    @Schema(description = "分类状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "分类状态不能为空")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "分类排序不能为空")
+    private Integer sort;
+
+}

+ 33 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionPageReqVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.expression;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.byzs.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - BPM 流程表达式分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmProcessExpressionPageReqVO extends PageParam {
+
+    @Schema(description = "表达式名字", example = "李四")
+    private String name;
+
+    @Schema(description = "表达式状态", example = "1")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 30 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.expression;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - BPM 流程表达式 Response VO")
+@Data
+public class BpmProcessExpressionRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3870")
+    @ExcelProperty("编号")
+    private Long id;
+
+    @Schema(description = "表达式名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @ExcelProperty("表达式名字")
+    private String name;
+
+    @Schema(description = "表达式状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "表达式", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String expression;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 27 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionSaveReqVO.java

@@ -0,0 +1,27 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.expression;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - BPM 流程表达式新增/修改 Request VO")
+@Data
+public class BpmProcessExpressionSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3870")
+    private Long id;
+
+    @Schema(description = "表达式名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @NotEmpty(message = "表达式名字不能为空")
+    private String name;
+
+    @Schema(description = "表达式状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "表达式状态不能为空")
+    private Integer status;
+
+    @Schema(description = "表达式", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "表达式不能为空")
+    private String expression;
+
+}

+ 24 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/form/BpmFormFieldVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.form;
+
+import lombok.Data;
+
+/**
+ * 流程表单字段 VO
+ */
+@Data
+public class BpmFormFieldVO {
+
+    /**
+     * 字段类型
+     */
+    private String type;
+    /**
+     * 字段标识
+     */
+    private String field;
+    /**
+     * 字段标题
+     */
+    private String title;
+
+}

+ 14 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/form/BpmFormPageReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.form;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 动态表单分页 Request VO")
+@Data
+public class BpmFormPageReqVO extends PageParam {
+
+    @Schema(description = "表单名称", example = "博雅智算")
+    private String name;
+
+}

+ 39 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/form/BpmFormRespVO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.form;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 动态表单 Response VO")
+@Data
+public class BpmFormRespVO {
+
+    @Schema(description = "表单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "表单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    @NotNull(message = "表单名称不能为空")
+    private String name;
+
+    @Schema(description = "表单的配置-JSON 字符串", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "表单的配置不能为空")
+    private String conf;
+
+    @Schema(description = "表单项的数组-JSON 字符串的数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "表单项的数组不能为空")
+    private List<String> fields;
+
+    @Schema(description = "表单状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "表单状态不能为空")
+    private Integer status; // 参见 CommonStatusEnum 枚举
+
+    @Schema(description = "备注", example = "我是备注")
+    private String remark;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 34 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/form/BpmFormSaveReqVO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.form;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - 动态表单创建/更新 Request VO")
+@Data
+public class BpmFormSaveReqVO {
+
+    @Schema(description = "表单编号", example = "1024")
+    private Long id;
+
+    @Schema(description = "表单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    @NotNull(message = "表单名称不能为空")
+    private String name;
+
+    @Schema(description = "表单的配置-JSON 字符串", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "表单的配置不能为空")
+    private String conf;
+
+    @Schema(description = "表单项的数组-JSON 字符串的数组", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "表单项的数组不能为空")
+    private List<String> fields;
+
+    @Schema(description = "表单状态-参见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "表单状态不能为空")
+    private Integer status;
+
+    @Schema(description = "备注", example = "我是备注")
+    private String remark;
+
+}

+ 28 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/group/BpmUserGroupPageReqVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.group;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.util.date.DateUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 用户组分页 Request VO")
+@Data
+public class BpmUserGroupPageReqVO extends PageParam {
+
+    @Schema(description = "编号", example = "1024")
+    private Long id;
+
+    @Schema(description = "组名", example = "博雅智算")
+    private String name;
+
+    @Schema(description = "状态", example = "1")
+    private Integer status;
+
+    @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Schema(description = "创建时间")
+    private LocalDateTime[] createTime;
+
+}

+ 31 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/group/BpmUserGroupRespVO.java

@@ -0,0 +1,31 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.group;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+@Schema(description = "管理后台 - 用户组 Response VO")
+@Data
+public class BpmUserGroupRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "组名", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    private String name;
+
+    @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算源码")
+    private String description;
+
+    @Schema(description = "成员编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+    private Set<Long> userIds;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 30 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/group/BpmUserGroupSaveReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.group;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.util.Set;
+
+@Schema(description = "管理后台 - 用户组创建/修改 Request VO")
+@Data
+public class BpmUserGroupSaveReqVO {
+
+    @Schema(description = "编号", example = "1024")
+    private Long id;
+
+    @Schema(description = "组名", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    @NotNull(message = "组名不能为空")
+    private String name;
+
+    @Schema(description = "描述", example = "博雅智算源码")
+    private String description;
+
+    @Schema(description = "成员编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
+    @NotNull(message = "成员编号数组不能为空")
+    private Set<Long> userIds;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+}

+ 30 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.listener;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - BPM 流程监听器分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class BpmProcessListenerPageReqVO extends PageParam {
+
+    @Schema(description = "监听器名字", example = "赵六")
+    private String name;
+
+    @Schema(description = "监听器类型", example = "execution")
+    private String type;
+
+    @Schema(description = "监听事件", example = "start")
+    private String event;
+
+    @Schema(description = "状态", example = "1")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+}

+ 36 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerRespVO.java

@@ -0,0 +1,36 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.listener;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - BPM 流程监听器 Response VO")
+@Data
+public class BpmProcessListenerRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13089")
+    private Long id;
+
+    @Schema(description = "监听器名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    private String name;
+
+    @Schema(description = "监听器类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "execution")
+    private String type;
+
+    @Schema(description = "监听器状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "监听事件", requiredMode = Schema.RequiredMode.REQUIRED, example = "start")
+    private String event;
+
+    @Schema(description = "监听器值类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "class")
+    private String valueType;
+
+    @Schema(description = "监听器值", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String value;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 39 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/listener/BpmProcessListenerSaveReqVO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.listener;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - BPM 流程监听器新增/修改 Request VO")
+@Data
+public class BpmProcessListenerSaveReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13089")
+    private Long id;
+
+    @Schema(description = "监听器名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
+    @NotEmpty(message = "监听器名字不能为空")
+    private String name;
+
+    @Schema(description = "监听器类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "execution")
+    @NotEmpty(message = "监听器类型不能为空")
+    private String type;
+
+    @Schema(description = "监听器状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "监听器状态不能为空")
+    private Integer status;
+
+    @Schema(description = "监听事件", requiredMode = Schema.RequiredMode.REQUIRED, example = "start")
+    @NotEmpty(message = "监听事件不能为空")
+    private String event;
+
+    @Schema(description = "监听器值类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "class")
+    @NotEmpty(message = "监听器值类型不能为空")
+    private String valueType;
+
+    @Schema(description = "监听器值", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "监听器值不能为空")
+    private String value;
+
+}

+ 19 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModeUpdateBpmnReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 流程模型的更新 BPMN XML Request VO")
+@Data
+public class BpmModeUpdateBpmnReqVO {
+
+    @Schema(description = "流程编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "流程编号不能为空")
+    private String id;
+
+    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "BPMN XML 不能为空")
+    private String bpmnXml;
+
+}

+ 181 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java

@@ -0,0 +1,181 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model;
+
+import cn.iocoder.byzs.framework.common.core.KeyValue;
+import cn.iocoder.byzs.framework.common.validation.InEnum;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
+import cn.iocoder.byzs.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import cn.iocoder.byzs.module.bpm.enums.definition.BpmModelTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+
+import java.util.List;
+
+/**
+ * BPM 流程 MetaInfo Response DTO
+ * 主要用于 { Model#setMetaInfo(String)} 的存储
+ *
+ * 最终,它的字段和
+ * {@link BpmProcessDefinitionInfoDO}
+ * 是一致的
+ *
+ * @author 博雅智算源码
+ */
+@Data
+public class BpmModelMetaInfoVO {
+
+    @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
+    @URL(message = "流程图标格式不正确")
+    private String icon;
+
+    @Schema(description = "流程描述", example = "我是描述")
+    private String description;
+
+    @Schema(description = "流程类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @InEnum(BpmModelTypeEnum.class)
+    @NotNull(message = "流程类型不能为空")
+    private Integer type;
+
+    @Schema(description = "表单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @InEnum(BpmModelFormTypeEnum.class)
+    @NotNull(message = "表单类型不能为空")
+    private Integer formType;
+    @Schema(description = "表单编号", example = "1024")
+    private Long formId; // formType 为 NORMAL 使用,必须非空
+
+    @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
+    private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
+    @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
+    private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空
+
+    @Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    @NotNull(message = "是否可见不能为空")
+    private Boolean visible;
+
+    @Schema(description = "可发起用户编号数组", example = "[1,2,3]")
+    private List<Long> startUserIds;
+
+    @Schema(description = "可发起部门编号数组", example = "[2,4,6]")
+    private List<Long> startDeptIds;
+
+    @Schema(description = "可管理用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2,4,6]")
+    @NotEmpty(message = "可管理用户编号数组不能为空")
+    private List<Long> managerUserIds;
+
+    @Schema(description = "排序", example = "1")
+    private Long sort; // 创建时,后端自动生成
+
+    @Schema(description = "允许撤销审批中的申请", example = "true")
+    private Boolean allowCancelRunningProcess;
+
+    @Schema(description = "流程 ID 规则", example = "{}")
+    private ProcessIdRule processIdRule;
+
+    @Schema(description = "自动去重类型", example = "1")
+    @InEnum(BpmAutoApproveTypeEnum.class)
+    private Integer autoApprovalType;
+
+    @Schema(description = "标题设置", example = "{}")
+    private TitleSetting titleSetting;
+
+    @Schema(description = "摘要设置", example = "{}")
+    private SummarySetting summarySetting;
+
+    @Schema(description = "流程前置通知设置", example = "{}")
+    private HttpRequestSetting processBeforeTriggerSetting;
+
+    @Schema(description = "流程后置通知设置", example = "{}")
+    private HttpRequestSetting processAfterTriggerSetting;
+
+    @Schema(description = "任务前置通知设置", example = "{}")
+    private HttpRequestSetting taskBeforeTriggerSetting;
+
+    @Schema(description = "任务后置通知设置", example = "{}")
+    private HttpRequestSetting taskAfterTriggerSetting;
+
+    @Schema(description = "流程 ID 规则")
+    @Data
+    @Valid
+    public static class ProcessIdRule {
+
+        @Schema(description = "是否启用", example = "false")
+        @NotNull(message = "是否启用不能为空")
+        private Boolean enable;
+
+        @Schema(description = "前缀", example = "XX")
+        private String prefix;
+
+        @Schema(description = "中缀", example = "20250120")
+        private String infix; // 精确到日、精确到时、精确到分、精确到秒
+
+        @Schema(description = "后缀", example = "YY")
+        private String postfix;
+
+        @Schema(description = "序列长度", example = "5")
+        @NotNull(message = "序列长度不能为空")
+        private Integer length;
+
+    }
+
+    @Schema(description = "标题设置")
+    @Data
+    @Valid
+    public static class TitleSetting {
+
+        @Schema(description = "是否自定义", example = "false")
+        @NotNull(message = "是否自定义不能为空")
+        private Boolean enable;
+
+        @Schema(description = "标题", example = "流程标题")
+        private String title;
+
+    }
+
+    @Schema(description = "摘要设置")
+    @Data
+    @Valid
+    public static class SummarySetting {
+
+        @Schema(description = "是否自定义", example = "false")
+        @NotNull(message = "是否自定义不能为空")
+        private Boolean enable;
+
+        @Schema(description = "摘要字段数组", example = "[]")
+        private List<String> summary;
+
+    }
+
+    @Schema(description = "http 请求通知设置", example = "{}")
+    @Data
+    public static class HttpRequestSetting {
+
+        @Schema(description = "请求路径", example = "http://127.0.0.1")
+        @NotEmpty(message = "请求 URL 不能为空")
+        @URL(message = "请求 URL 格式不正确")
+        private String url;
+
+        @Schema(description = "请求头参数设置", example = "[]")
+        @Valid
+        private List<BpmSimpleModelNodeVO.HttpRequestParam> header;
+
+        @Schema(description = "请求头参数设置", example = "[]")
+        @Valid
+        private List<BpmSimpleModelNodeVO.HttpRequestParam> body;
+
+        /**
+         * 请求返回处理设置,用于修改流程表单值
+         * <p>
+         * key:表示要修改的流程表单字段名(name)
+         * value:接口返回的字段名
+         */
+        @Schema(description = "请求返回处理设置", example = "[]")
+        private List<KeyValue<String, String>> response;
+
+    }
+
+}

+ 57 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModelRespVO.java

@@ -0,0 +1,57 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model;
+
+import cn.iocoder.byzs.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 流程模型 Response VO")
+@Data
+public class BpmModelRespVO extends BpmModelMetaInfoVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String id;
+
+    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao")
+    private String key;
+
+    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    private String name;
+
+    @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
+    private String icon;
+
+    @Schema(description = "流程分类编号", example = "1")
+    private String category;
+    @Schema(description = "流程分类名字", example = "请假")
+    private String categoryName;
+
+    @Schema(description = "表单名字", example = "请假表单")
+    private String formName;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "可发起的用户数组")
+    private List<UserSimpleBaseVO> startUsers;
+
+    @Schema(description = "可发起的部门数组")
+    private List<DeptSimpleBaseVO> startDepts;
+
+    @Schema(description = "BPMN XML")
+    private String bpmnXml;
+
+    @Schema(description = "仿钉钉流程设计模型对象")
+    private BpmSimpleModelNodeVO simpleModel;
+
+    /**
+     * 最新部署的流程定义
+     */
+    private BpmProcessDefinitionRespVO processDefinition;
+
+}

+ 34 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModelSaveReqVO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model;
+
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 流程模型的保存 Request VO")
+@Data
+public class BpmModelSaveReqVO extends BpmModelMetaInfoVO {
+
+    @Schema(description = "编号", example = "1024")
+    private String id;
+
+    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao")
+    @NotEmpty(message = "流程标识不能为空")
+    private String key;
+
+    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    @NotEmpty(message = "流程名称不能为空")
+    private String name;
+
+    @Schema(description = "流程分类", example = "1")
+    private String category;
+
+    @Schema(description = "BPMN XML")
+    private String bpmnXml;
+
+    @Schema(description = "仿钉钉流程设计模型对象")
+    @Valid
+    private BpmSimpleModelNodeVO simpleModel;
+
+}

+ 20 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateStateReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 流程模型更新状态 Request VO")
+@Data
+public class BpmModelUpdateStateReqVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "编号不能为空")
+    private String id;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer state; // 参见 Flowable SuspensionState 枚举
+
+}

+ 527 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java

@@ -0,0 +1,527 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple;
+
+import cn.iocoder.byzs.framework.common.core.KeyValue;
+import cn.iocoder.byzs.framework.common.validation.InEnum;
+import cn.iocoder.byzs.module.bpm.enums.definition.*;
+import cn.iocoder.byzs.module.bpm.enums.definition.*;
+import cn.iocoder.byzs.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.flowable.bpmn.model.IOParameter;
+import org.hibernate.validator.constraints.URL;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO")
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class BpmSimpleModelNodeVO {
+
+    @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1")
+    @NotEmpty(message = "模型节点编号不能为空")
+    private String id;
+
+    @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "模型节点类型不能为空")
+    @InEnum(BpmSimpleModelNodeTypeEnum.class)
+    private Integer type;
+
+    @Schema(description = "模型节点名称", example = "领导审批")
+    private String name;
+
+    @Schema(description = "节点展示内容", example = "指定成员: 博雅智算源码")
+    private String showText;
+
+    @Schema(description = "子节点")
+    private BpmSimpleModelNodeVO childNode; // 补充说明:在该模型下,子节点有且仅有一个,不会有多个
+
+    @Schema(description = "候选人策略", example = "30")
+    @InEnum(BpmTaskCandidateStrategyEnum.class)
+    private Integer candidateStrategy; // 用于审批,抄送节点
+
+    @Schema(description = "候选人参数")
+    private String candidateParam; // 用于审批,抄送节点
+
+    @Schema(description = "审批节点类型", example = "1")
+    @InEnum(BpmUserTaskApproveTypeEnum.class)
+    private Integer approveType; // 用于审批节点
+
+    @Schema(description = "多人审批方式", example = "1")
+    @InEnum(BpmUserTaskApproveMethodEnum.class)
+    private Integer approveMethod; // 用于审批节点
+
+    @Schema(description = "通过比例", example = "100")
+    private Integer approveRatio; // 通过比例,当多人审批方式为:多人会签(按通过比例) 需要设置
+
+    @Schema(description = "表单权限", example = "[]")
+    private List<Map<String, String>> fieldsPermission;
+
+    @Schema(description = "操作按钮设置", example = "[]")
+    private List<OperationButtonSetting> buttonsSetting;  // 用于审批节点
+
+    @Schema(description = "是否需要签名", example = "false")
+    private Boolean signEnable;
+
+    @Schema(description = "是否填写审批意见", example = "false")
+    private Boolean reasonRequire;
+
+    /**
+     * 审批节点拒绝处理
+     */
+    private RejectHandler rejectHandler;
+
+    /**
+     * 审批节点超时处理
+     */
+    private TimeoutHandler timeoutHandler;
+
+    @Schema(description = "审批节点的审批人与发起人相同时,对应的处理类型", example = "1")
+    @InEnum(BpmUserTaskAssignStartUserHandlerTypeEnum.class)
+    private Integer assignStartUserHandlerType;
+
+    /**
+     * 空处理策略
+     */
+    private AssignEmptyHandler assignEmptyHandler;
+
+    /**
+     * 创建任务监听器
+     */
+    private ListenerHandler taskCreateListener;
+    /**
+     * 指派任务监听器
+     */
+    private ListenerHandler taskAssignListener;
+    /**
+     * 完成任务监听器
+     */
+    private ListenerHandler taskCompleteListener;
+
+    @Schema(description = "延迟器设置", example = "{}")
+    private DelaySetting delaySetting;
+
+    @Schema(description = "条件节点")
+    private List<BpmSimpleModelNodeVO> conditionNodes; // 补充说明:有且仅有条件、并行、包容分支会使用
+
+    /**
+     * 条件节点设置
+     */
+    private ConditionSetting conditionSetting; // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE
+
+    @Schema(description = "路由分支组", example = "[]")
+    private List<RouterSetting> routerGroups;
+
+    @Schema(description = "路由分支默认分支 ID", example = "Flow_xxx", hidden = true) // 由后端生成(不从前端传递),所以 hidden = true
+    @JsonIgnore
+    private String routerDefaultFlowId; // 仅用于路由分支节点 BpmSimpleModelNodeType.ROUTER_BRANCH_NODE
+
+    /**
+     * 触发器节点设置
+     */
+    private TriggerSetting triggerSetting;
+
+    @Schema(description = "附加节点 Id", example = "UserTask_xxx", hidden = true) // 由后端生成(不从前端传递),所以 hidden = true
+    @JsonIgnore
+    private String attachNodeId; // 目前用于触发器节点(HTTP 回调)。需要 UserTask 和 ReceiveTask(附加节点) 来完成
+
+    /**
+     * 子流程设置
+     */
+    private ChildProcessSetting childProcessSetting;
+
+    @Schema(description = "任务监听器")
+    @Valid
+    @Data
+    public static class ListenerHandler {
+
+        @Schema(description = "是否开启任务监听器", example = "false")
+        @NotNull(message = "是否开启任务监听器不能为空")
+        private Boolean enable;
+
+        @Schema(description = "请求路径", example = "http://xxxxx")
+        private String path;
+
+        @Schema(description = "请求头", example = "[]")
+        private List<HttpRequestParam> header;
+
+        @Schema(description = "请求体", example = "[]")
+        private List<HttpRequestParam> body;
+
+    }
+
+    @Schema(description = "HTTP 请求参数设置")
+    @Data
+    public static class HttpRequestParam {
+
+        @Schema(description = "值类型", example = "1")
+        @InEnum(BpmHttpRequestParamTypeEnum.class)
+        @NotNull(message = "值类型不能为空")
+        private Integer type;
+
+        @Schema(description = "键", example = "xxx")
+        @NotEmpty(message = "键不能为空")
+        private String key;
+
+        @Schema(description = "值", example = "xxx")
+        @NotEmpty(message = "值不能为空")
+        private String value;
+
+    }
+
+    @Schema(description = "审批节点拒绝处理策略")
+    @Data
+    public static class RejectHandler {
+
+        @Schema(description = "拒绝处理类型", example = "1")
+        @InEnum(BpmUserTaskRejectHandlerTypeEnum.class)
+        private Integer type;
+
+        @Schema(description = "任务拒绝后驳回的节点 Id", example = "Activity_1")
+        private String returnNodeId;
+    }
+
+    @Schema(description = "审批节点超时处理策略")
+    @Valid
+    @Data
+    public static class TimeoutHandler {
+
+        @Schema(description = "是否开启超时处理", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+        @NotNull(message = "是否开启超时处理不能为空")
+        private Boolean enable;
+
+        @Schema(description = "任务超时未处理的行为", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "任务超时未处理的行为不能为空")
+        @InEnum(BpmUserTaskTimeoutHandlerTypeEnum.class)
+        private Integer type;
+
+        @Schema(description = "超时时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "PT6H")
+        @NotEmpty(message = "超时时间不能为空")
+        private String timeDuration;
+
+        @Schema(description = "最大提醒次数", example = "1")
+        private Integer maxRemindCount;
+    }
+
+    @Schema(description = "空处理策略")
+    @Data
+    @Valid
+    public static class AssignEmptyHandler {
+
+        @Schema(description = "空处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "空处理类型不能为空")
+        @InEnum(BpmUserTaskAssignEmptyHandlerTypeEnum.class)
+        private Integer type;
+
+        @Schema(description = "指定人员审批的用户编号数组", example = "1")
+        private List<Long> userIds;
+    }
+
+    @Schema(description = "操作按钮设置")
+    @Data
+    @Valid
+    public static class OperationButtonSetting {
+
+        // TODO @jason:是不是按钮的标识?id 会和数据库的 id 自增有点模糊,key 标识会更合理一点点哈。
+        @Schema(description = "按钮 Id", example = "1")
+        private Integer id;
+
+        @Schema(description = "显示名称", example = "审批")
+        private String displayName;
+
+        @Schema(description = "是否启用", example = "true")
+        private Boolean enable;
+    }
+
+    @Schema(description = "条件设置")
+    @Data
+    @Valid
+    // 仅用于条件节点 BpmSimpleModelNodeTypeEnum.CONDITION_NODE
+    public static class ConditionSetting {
+
+        @Schema(description = "条件类型", example = "1")
+        @InEnum(BpmSimpleModeConditionTypeEnum.class)
+        private Integer conditionType;
+
+        @Schema(description = "条件表达式", example = "${day>3}")
+        private String conditionExpression;
+
+        @Schema(description = "是否默认条件", example = "true")
+        private Boolean defaultFlow;
+
+        /**
+         * 条件组
+         */
+        private ConditionGroups conditionGroups;
+    }
+
+    @Schema(description = "条件组")
+    @Data
+    @Valid
+    public static class ConditionGroups {
+
+        @Schema(description = "条件组下的条件关系是否为与关系", example = "true")
+        @NotNull(message = "条件关系不能为空")
+        private Boolean and;
+
+        @Schema(description = "条件组下的条件", example = "[]")
+        @NotEmpty(message = "条件不能为空")
+        private List<Condition> conditions;
+    }
+
+    @Schema(description = "条件")
+    @Data
+    @Valid
+    public static class Condition {
+
+        @Schema(description = "条件下的规则关系是否为与关系", example = "true")
+        @NotNull(message = "规则关系不能为空")
+        private Boolean and;
+
+        @Schema(description = "条件下的规则", example = "[]")
+        @NotEmpty(message = "规则不能为空")
+        private List<ConditionRule> rules;
+    }
+
+    @Schema(description = "条件规则")
+    @Data
+    @Valid
+    public static class ConditionRule {
+
+        @Schema(description = "运行符号", example = "==")
+        @NotEmpty(message = "运行符号不能为空")
+        private String opCode;
+
+        @Schema(description = "运算符左边的值,例如某个流程变量", example = "startUserId")
+        @NotEmpty(message = "运算符左边的值不能为空")
+        private String leftSide;
+
+        @Schema(description = "运算符右边的值", example = "1")
+        @NotEmpty(message = "运算符右边的值不能为空")
+        private String rightSide;
+    }
+
+    @Schema(description = "延迟器")
+    @Data
+    @Valid
+    public static class DelaySetting {
+
+        @Schema(description = "延迟时间类型", example = "1")
+        @NotNull(message = "延迟时间类型不能为空")
+        @InEnum(BpmDelayTimerTypeEnum.class)
+        private Integer delayType;
+
+        @Schema(description = "延迟时间表达式", example = "PT1H,2025-01-01T00:00:00")
+        @NotEmpty(message = "延迟时间表达式不能为空")
+        private String delayTime;
+    }
+
+    @Schema(description = "路由分支")
+    @Data
+    @Valid
+    public static class RouterSetting {
+
+        @Schema(description = "节点 Id", example = "Activity_xxx") // 跳转到该节点
+        @NotEmpty(message = "节点 Id 不能为空")
+        private String nodeId;
+
+        @Schema(description = "条件类型", example = "1")
+        @InEnum(BpmSimpleModeConditionTypeEnum.class)
+        @NotNull(message = "条件类型不能为空")
+        private Integer conditionType;
+
+        @Schema(description = "条件表达式", example = "${day>3}")
+        private String conditionExpression;
+
+        @Schema(description = "条件组", example = "{}")
+        private ConditionGroups conditionGroups;
+    }
+
+    @Schema(description = "触发器节点配置")
+    @Data
+    @Valid
+    public static class TriggerSetting {
+
+        @Schema(description = "触发器类型", example = "1")
+        @InEnum(BpmTriggerTypeEnum.class)
+        @NotNull(message = "触发器类型不能为空")
+        private Integer type;
+
+        /**
+         * http 请求触发器设置
+         */
+        @Valid
+        private HttpRequestTriggerSetting httpRequestSetting;
+
+        /**
+         * 流程表单触发器设置
+         */
+        private List<FormTriggerSetting> formSettings;
+
+        @Schema(description = "http 请求触发器设置", example = "{}")
+        @Data
+        public static class HttpRequestTriggerSetting {
+
+            @Schema(description = "请求路径", example = "http://127.0.0.1")
+            @NotEmpty(message = "请求 URL 不能为空")
+            @URL(message = "请求 URL 格式不正确")
+            private String url;
+
+            @Schema(description = "请求头参数设置", example = "[]")
+            @Valid
+            private List<HttpRequestParam> header;
+
+            @Schema(description = "请求头参数设置", example = "[]")
+            @Valid
+            private List<HttpRequestParam> body;
+
+            /**
+             * 请求返回处理设置,用于修改流程表单值
+             * <p>
+             * key:表示要修改的流程表单字段名(name)
+             * value:接口返回的字段名
+             */
+            @Schema(description = "请求返回处理设置", example = "[]")
+            private List<KeyValue<String, String>> response;
+
+            /**
+             * Http 回调请求,需要指定回调任务 Key,用于回调执行
+             */
+            @Schema(description = "回调任务 Key", example = "xxx", hidden = true)
+            private String callbackTaskDefineKey;
+
+        }
+
+        @Schema(description = "流程表单触发器设置", example = "{}")
+        @Data
+        public static class FormTriggerSetting {
+
+            @Schema(description = "条件类型", example = "1")
+            @InEnum(BpmSimpleModeConditionTypeEnum.class)
+            private Integer conditionType;
+
+            @Schema(description = "条件表达式", example = "${day>3}")
+            private String conditionExpression;
+
+            @Schema(description = "条件组", example = "{}")
+            private ConditionGroups conditionGroups;
+
+            @Schema(description = "修改的表单字段", example = "{}")
+            private Map<String, Object> updateFormFields;
+
+            @Schema(description = "删除表单字段", example = "[]")
+            private Set<String> deleteFields;
+        }
+    }
+
+    @Schema(description = "子流程节点配置")
+    @Data
+    @Valid
+    public static class ChildProcessSetting {
+
+        @Schema(description = "被调用流程", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx")
+        @NotEmpty(message = "被调用流程不能为空")
+        private String calledProcessDefinitionKey;
+
+        @Schema(description = "被调用流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx")
+        @NotEmpty(message = "被调用流程名称不能为空")
+        private String calledProcessDefinitionName;
+
+        @Schema(description = "是否异步", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+        @NotNull(message = "是否异步不能为空")
+        private Boolean async;
+
+        @Schema(description = "输入参数(主->子)", example = "[]")
+        private List<IOParameter> inVariables;
+
+        @Schema(description = "输出参数(子->主)", example = "[]")
+        private List<IOParameter> outVariables;
+
+        @Schema(description = "是否自动跳过子流程发起节点", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+        @NotNull(message = "是否自动跳过子流程发起节点不能为空")
+        private Boolean skipStartUserNode;
+
+        @Schema(description = "子流程发起人配置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
+        @NotNull(message = "子流程发起人配置不能为空")
+        private StartUserSetting startUserSetting;
+
+        @Schema(description = "超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
+        private TimeoutSetting timeoutSetting;
+
+        @Schema(description = "多实例设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
+        private MultiInstanceSetting multiInstanceSetting;
+
+        @Schema(description = "子流程发起人配置")
+        @Data
+        @Valid
+        public static class StartUserSetting {
+
+            @Schema(description = "子流程发起人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+            @NotNull(message = "子流程发起人类型")
+            @InEnum(BpmChildProcessStartUserTypeEnum.class)
+            private Integer type;
+
+            @Schema(description = "表单", example = "xxx")
+            private String formField;
+
+            @Schema(description = "当子流程发起人为空时类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+            @NotNull(message = "当子流程发起人为空时类型不能为空")
+            @InEnum(BpmChildProcessStartUserEmptyTypeEnum.class)
+            private Integer emptyType;
+
+        }
+
+        @Schema(description = "超时设置")
+        @Data
+        @Valid
+        public static class TimeoutSetting {
+
+            @Schema(description = "是否开启超时设置", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+            @NotNull(message = "是否开启超时设置不能为空")
+            private Boolean enable;
+
+            @Schema(description = "时间类型", example = "1")
+            @InEnum(BpmDelayTimerTypeEnum.class)
+            private Integer type;
+
+            @Schema(description = "时间表达式", example = "PT1H,2025-01-01T00:00:00")
+            private String timeExpression;
+
+        }
+
+        @Schema(description = "多实例设置")
+        @Data
+        @Valid
+        public static class MultiInstanceSetting {
+
+            @Schema(description = "是否开启多实例", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+            @NotNull(message = "是否开启多实例不能为空")
+            private Boolean enable;
+
+            @Schema(description = "是否串行", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
+            @NotNull(message = "是否串行不能为空")
+            private Boolean sequential;
+
+            @Schema(description = "完成比例", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+            @NotNull(message = "完成比例不能为空")
+            private Integer approveRatio;
+
+            @Schema(description = "多实例来源类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+            @NotNull(message = "多实例来源类型不能为空")
+            @InEnum(BpmChildProcessMultiInstanceSourceTypeEnum.class)
+            private Integer sourceType;
+
+            @Schema(description = "多实例来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+            @NotNull(message = "多实例来源不能为空")
+            private String source;
+
+        }
+
+    }
+}

+ 23 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelUpdateReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+// TODO @jason:需要考虑,如果某个节点的配置不正确,需要有提示;具体怎么实现,可以讨论下;
+@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO")
+@Data
+public class BpmSimpleModelUpdateReqVO {
+
+    @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotEmpty(message = "流程模型编号不能为空")
+    private String id; // 对应 Flowable act_re_model 表 ID_ 字段
+
+    @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "仿钉钉流程设计模型对象不能为空")
+    @Valid
+    private BpmSimpleModelNodeVO simpleModel;
+
+}

+ 14 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionPageReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 流程定义分页 Request VO")
+@Data
+public class BpmProcessDefinitionPageReqVO extends PageParam {
+
+    @Schema(description = "标识-精准匹配", example = "process1641042089407")
+    private String key;
+
+}

+ 71 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/definition/vo/process/BpmProcessDefinitionRespVO.java

@@ -0,0 +1,71 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process;
+
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 流程定义 Response VO")
+@Data
+public class BpmProcessDefinitionRespVO extends BpmModelMetaInfoVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String id;
+
+    @Schema(description = "版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer version;
+
+    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    private String name;
+
+    @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "youdao")
+    private String key;
+
+    @Schema(description = "流程分类", example = "1")
+    private String category;
+    @Schema(description = "流程分类名字", example = "请假")
+    private String categoryName;
+
+    @Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
+
+    @Schema(description = "流程模型的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC")
+    private String modelId;
+
+    @Schema(description = "表单的配置-JSON 字符串。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String formConf;
+    @Schema(description = "表单项的数组-JSON 字符串的数组。在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> formFields;
+    @Schema(description = "表单名字", example = "请假表单")
+    private String formName;
+
+    @Schema(description = "中断状态-参见 SuspensionState 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer suspensionState; // 参见 SuspensionState 枚举
+
+    @Schema(description = "部署时间")
+    private LocalDateTime deploymentTime; // 需要从对应的 Deployment 读取,非必须返回
+
+    @Schema(description = "BPMN XML")
+    private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回
+
+    @Schema(description = "SIMPLE 设计器模型数据 json 格式")
+    private String simpleModel; // 非必须返回
+
+    @Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long sort;
+
+    @Schema(description = "BPMN UserTask 用户任务")
+    @Data
+    public static class UserTask {
+
+        @Schema(description = "任务标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "sudo")
+        private String id;
+
+        @Schema(description = "任务名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+        private String name;
+
+    }
+
+}

+ 12 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/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": "我要请假啦啦啦!"
+}

+ 62 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/BpmOALeaveController.java

@@ -0,0 +1,62 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.oa;
+
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.oa.vo.BpmOALeaveCreateReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.oa.vo.BpmOALeaveRespVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
+import cn.iocoder.byzs.module.bpm.service.oa.BpmOALeaveService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * OA 请假申请 Controller,用于演示自己存储数据,接入工作流的例子
+ *
+ * @author jason
+ * @author 博雅智算源码
+ */
+@Tag(name = "管理后台 - OA 请假申请")
+@RestController
+@RequestMapping("/bpm/oa/leave")
+@Validated
+public class BpmOALeaveController {
+
+    @Resource
+    private BpmOALeaveService leaveService;
+
+    @PostMapping("/create")
+    @PreAuthorize("@ss.hasPermission('bpm:oa-leave:create')")
+    @Operation(summary = "创建请求申请")
+    public CommonResult<Long> createLeave(@Valid @RequestBody BpmOALeaveCreateReqVO createReqVO) {
+        return success(leaveService.createLeave(getLoginUserId(), createReqVO));
+    }
+
+    @GetMapping("/get")
+    @PreAuthorize("@ss.hasPermission('bpm:oa-leave:query')")
+    @Operation(summary = "获得请假申请")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    public CommonResult<BpmOALeaveRespVO> getLeave(@RequestParam("id") Long id) {
+        BpmOALeaveDO leave = leaveService.getLeave(id);
+        return success(BeanUtils.toBean(leave, BpmOALeaveRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @PreAuthorize("@ss.hasPermission('bpm:oa-leave:query')")
+    @Operation(summary = "获得请假申请分页")
+    public CommonResult<PageResult<BpmOALeaveRespVO>> getLeavePage(@Valid BpmOALeavePageReqVO pageVO) {
+        PageResult<BpmOALeaveDO> pageResult = leaveService.getLeavePage(getLoginUserId(), pageVO);
+        return success(BeanUtils.toBean(pageResult, BpmOALeaveRespVO.class));
+    }
+
+}

+ 5 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/package-info.java

@@ -0,0 +1,5 @@
+/**
+ * OA 示例,用于演示外部业务接入 BPM 工作流的示例
+ * 一般的接入方式,只需要调用 接口,后续 Admin 用户在管理后台的【待办事务】进行审批
+ */
+package cn.iocoder.byzs.module.bpm.controller.admin.oa;

+ 43 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/vo/BpmOALeaveCreateReqVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.oa.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.AssertTrue;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.byzs.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 请假申请创建 Request VO")
+@Data
+public class BpmOALeaveCreateReqVO {
+
+    @Schema(description = "请假的开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "开始时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime startTime;
+
+    @Schema(description = "请假的结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "结束时间不能为空")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime endTime;
+
+    @Schema(description = "请假类型-参见 bpm_oa_type 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+    @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读博雅智算源码")
+    private String reason;
+
+    @Schema(description = "发起人自选审批人 Map", example = "{taskKey1: [1, 2]}")
+    private Map<String, List<Long>> startUserSelectAssignees;
+
+    @AssertTrue(message = "结束时间,需要在开始时间之后")
+    public boolean isEndTimeValid() {
+        return !getEndTime().isBefore(getStartTime());
+    }
+
+}

+ 29 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/vo/BpmOALeavePageReqVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.oa.vo;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.byzs.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 请假申请分页 Request VO")
+@Data
+public class BpmOALeavePageReqVO extends PageParam {
+
+    @Schema(description = "状态", example = "1")
+    private Integer status; // 参见 BpmProcessInstanceResultEnum 枚举
+
+    @Schema(description = "请假类型,参见 bpm_oa_type", example = "1")
+    private Integer type;
+
+    @Schema(description = "原因,模糊匹配", example = "阅读博雅智算源码")
+    private String reason;
+
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    @Schema(description = "申请时间")
+    private LocalDateTime[] createTime;
+
+}

+ 36 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/oa/vo/BpmOALeaveRespVO.java

@@ -0,0 +1,36 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.oa.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 请假申请 Response VO")
+@Data
+public class BpmOALeaveRespVO {
+
+    @Schema(description = "请假表单主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "请假类型,参见 bpm_oa_type 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+    @Schema(description = "原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阅读博雅智算源码")
+    private String reason;
+
+    @Schema(description = "申请时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "请假的开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime startTime;
+
+    @Schema(description = "请假的结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime endTime;
+
+    @Schema(description = "流程编号")
+    private String processInstanceId;
+
+    @Schema(description = "审批结果", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
+
+}

+ 16 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/BpmProcessInstanceController.http

@@ -0,0 +1,16 @@
+### 请求 /bpm/process-instance/get-bpmn 接口 => 成功
+GET {{baseUrl}}/bpm/process-instance/get-bpmn-model-view?id=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
+Content-Type: application/json
+tenant-id: 1
+Authorization: Bearer {{token}}
+
+### 请求 /bpm/process-instance/get-bpmn 接口 => 失败
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932
+#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b
+GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b
+Content-Type: application/json
+tenant-id: 1
+Authorization: Bearer {{token}}

+ 200 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/BpmProcessInstanceController.java

@@ -0,0 +1,200 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.json.JsonUtils;
+import cn.iocoder.byzs.framework.common.util.number.NumberUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance.*;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance.*;
+import cn.iocoder.byzs.module.bpm.convert.task.BpmProcessInstanceConvert;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.byzs.module.bpm.service.task.BpmProcessInstanceService;
+import cn.iocoder.byzs.module.bpm.service.task.BpmTaskService;
+import cn.iocoder.byzs.module.system.api.dept.DeptApi;
+import cn.iocoder.byzs.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.byzs.module.system.api.user.AdminUserApi;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.task.api.Task;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.*;
+
+@Tag(name = "管理后台 - 流程实例") // 流程实例,通过流程定义创建的一次“申请”
+@RestController
+@RequestMapping("/bpm/process-instance")
+@Validated
+public class BpmProcessInstanceController {
+
+    @Resource
+    private BpmProcessInstanceService processInstanceService;
+    @Resource
+    private BpmTaskService taskService;
+    @Resource
+    private BpmProcessDefinitionService processDefinitionService;
+    @Resource
+    private BpmCategoryService categoryService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
+
+    @GetMapping("/my-page")
+    @Operation(summary = "获得我的实例分页列表", description = "在【我的流程】菜单中,进行调用")
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+    public CommonResult<PageResult<BpmProcessInstanceRespVO>> getProcessInstanceMyPage(
+            @Valid BpmProcessInstancePageReqVO pageReqVO) {
+        PageResult<HistoricProcessInstance> pageResult = processInstanceService.getProcessInstancePage(
+                getLoginUserId(), pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+
+        // 拼接返回
+        Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(
+                convertList(pageResult.getList(), HistoricProcessInstance::getId));
+        Map<String, ProcessDefinition> processDefinitionMap = processDefinitionService.getProcessDefinitionMap(
+                convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId));
+        Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
+                convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory));
+        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
+                convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId));
+        Set<Long> userIds = convertSet(pageResult.getList(), processInstance -> NumberUtils.parseLong(processInstance.getStartUserId()));
+        userIds.addAll(convertSetByFlatMap(taskMap.values(),
+                tasks -> tasks.stream().map(Task::getAssignee).filter(StrUtil::isNotBlank).map(Long::parseLong)));
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+                convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+        return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult,
+                processDefinitionMap, categoryMap, taskMap, userMap, deptMap, processDefinitionInfoMap));
+    }
+
+    @GetMapping("/manager-page")
+    @Operation(summary = "获得管理流程实例的分页列表", description = "在【流程实例】菜单中,进行调用")
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:manager-query')")
+    public CommonResult<PageResult<BpmProcessInstanceRespVO>> getProcessInstanceManagerPage(
+            @Valid BpmProcessInstancePageReqVO pageReqVO) {
+        PageResult<HistoricProcessInstance> pageResult = processInstanceService.getProcessInstancePage(
+                null, pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+
+        // 拼接返回
+        Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(
+                convertList(pageResult.getList(), HistoricProcessInstance::getId));
+        Map<String, ProcessDefinition> processDefinitionMap = processDefinitionService.getProcessDefinitionMap(
+                convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId));
+        Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
+                convertSet(processDefinitionMap.values(), ProcessDefinition::getCategory));
+        // 发起人信息
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(pageResult.getList(), processInstance -> NumberUtils.parseLong(processInstance.getStartUserId())));
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+                convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
+                convertSet(pageResult.getList(), HistoricProcessInstance::getProcessDefinitionId));
+        return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePage(pageResult,
+                processDefinitionMap, categoryMap, taskMap, userMap, deptMap, processDefinitionInfoMap));
+    }
+
+    @PostMapping("/create")
+    @Operation(summary = "新建流程实例")
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+    public CommonResult<String> createProcessInstance(@Valid @RequestBody BpmProcessInstanceCreateReqVO createReqVO) {
+        return success(processInstanceService.createProcessInstance(getLoginUserId(), createReqVO));
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得指定流程实例", description = "在【流程详细】界面中,进行调用")
+    @Parameter(name = "id", description = "流程实例的编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+    public CommonResult<BpmProcessInstanceRespVO> getProcessInstance(@RequestParam("id") String id) {
+        HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(id);
+        if (processInstance == null) {
+            return success(null);
+        }
+
+        // 拼接返回
+        ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
+                processInstance.getProcessDefinitionId());
+        BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(
+                processInstance.getProcessDefinitionId());
+        AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId()));
+        DeptRespDTO dept = null;
+        if (startUser != null && startUser.getDeptId() != null) {
+            dept = deptApi.getDept(startUser.getDeptId());
+        }
+        return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance,
+                processDefinition, processDefinitionInfo, startUser, dept));
+    }
+
+    @DeleteMapping("/cancel-by-start-user")
+    @Operation(summary = "用户取消流程实例", description = "取消发起的流程")
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")
+    public CommonResult<Boolean> cancelProcessInstanceByStartUser(
+            @Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) {
+        processInstanceService.cancelProcessInstanceByStartUser(getLoginUserId(), cancelReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/cancel-by-admin")
+    @Operation(summary = "管理员取消流程实例", description = "管理员撤回流程")
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel-by-admin')")
+    public CommonResult<Boolean> cancelProcessInstanceByManager(
+            @Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) {
+        processInstanceService.cancelProcessInstanceByAdmin(getLoginUserId(), cancelReqVO);
+        return success(true);
+    }
+
+    @GetMapping("/get-approval-detail")
+    @Operation(summary = "获得审批详情")
+    @Parameter(name = "id", description = "流程实例的编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+    @SuppressWarnings("unchecked")
+    public CommonResult<BpmApprovalDetailRespVO> getApprovalDetail(@Valid BpmApprovalDetailReqVO reqVO) {
+        if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
+            reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
+        }
+        return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
+    }
+
+    @GetMapping("/get-next-approval-nodes")
+    @Operation(summary = "获取下一个执行的流程节点")
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
+    @SuppressWarnings("unchecked")
+    public CommonResult<List<BpmApprovalDetailRespVO.ActivityNode>> getNextApprovalNodes(@Valid BpmApprovalDetailReqVO reqVO) {
+        if (StrUtil.isNotEmpty(reqVO.getProcessVariablesStr())) {
+            reqVO.setProcessVariables(JsonUtils.parseObject(reqVO.getProcessVariablesStr(), Map.class));
+        }
+        return success(processInstanceService.getNextApprovalNodes(getLoginUserId(), reqVO));
+    }
+
+    @GetMapping("/get-bpmn-model-view")
+    @Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
+    @Parameter(name = "id", description = "流程实例的编号", required = true)
+    public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) {
+        return success(processInstanceService.getProcessInstanceBpmnModelView(id));
+    }
+
+}

+ 89 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/BpmProcessInstanceCopyController.java

@@ -0,0 +1,89 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.collection.MapUtils;
+import cn.iocoder.byzs.framework.common.util.date.DateUtils;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
+import cn.iocoder.byzs.module.bpm.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.byzs.module.bpm.service.task.BpmProcessInstanceCopyService;
+import cn.iocoder.byzs.module.bpm.service.task.BpmProcessInstanceService;
+import cn.iocoder.byzs.module.system.api.user.AdminUserApi;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.flowable.engine.history.HistoricProcessInstance;
+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.RestController;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.*;
+
+@Tag(name = "管理后台 - 流程实例抄送")
+@RestController
+@RequestMapping("/bpm/process-instance/copy")
+@Validated
+public class BpmProcessInstanceCopyController {
+
+    @Resource
+    private BpmProcessInstanceCopyService processInstanceCopyService;
+    @Resource
+    private BpmProcessInstanceService processInstanceService;
+    @Resource
+    private BpmProcessDefinitionService processDefinitionService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得抄送流程分页列表")
+    @PreAuthorize("@ss.hasPermission('bpm:process-instance-cc:query')")
+    public CommonResult<PageResult<BpmProcessInstanceCopyRespVO>> getProcessInstanceCopyPage(
+            @Valid BpmProcessInstanceCopyPageReqVO pageReqVO) {
+        PageResult<BpmProcessInstanceCopyDO> pageResult = processInstanceCopyService.getProcessInstanceCopyPage(
+                getLoginUserId(), pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(new PageResult<>(pageResult.getTotal()));
+        }
+
+        // 拼接返回
+        Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
+                convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId));
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
+                copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator()))));
+        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
+                convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessDefinitionId));
+        return success(convertPage(pageResult, copy -> {
+            BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class);
+            MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()),
+                    user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
+            MapUtils.findAndThen(userMap, copy.getStartUserId(),
+                    user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
+            MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(),
+                    processInstance -> {
+                        copyVO.setSummary(FlowableUtils.getSummary(
+                                processDefinitionInfoMap.get(processInstance.getProcessDefinitionId()),
+                                processInstance.getProcessVariables()));
+                        copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime()));
+                    });
+            return copyVO;
+        }));
+    }
+
+}

+ 240 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/BpmTaskController.java

@@ -0,0 +1,240 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task;
+
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.*;
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.byzs.framework.common.pojo.CommonResult;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.number.NumberUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task.*;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task.*;
+import cn.iocoder.byzs.module.bpm.convert.task.BpmTaskConvert;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmFormService;
+import cn.iocoder.byzs.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.byzs.module.bpm.service.task.BpmProcessInstanceService;
+import cn.iocoder.byzs.module.bpm.service.task.BpmTaskService;
+import cn.iocoder.byzs.module.system.api.dept.DeptApi;
+import cn.iocoder.byzs.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.byzs.module.system.api.user.AdminUserApi;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.flowable.bpmn.model.UserTask;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.task.api.Task;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static cn.iocoder.byzs.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.byzs.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - 流程任务实例")
+@RestController
+@RequestMapping("/bpm/task")
+@Validated
+public class BpmTaskController {
+
+    @Resource
+    private BpmTaskService taskService;
+    @Resource
+    private BpmProcessInstanceService processInstanceService;
+    @Resource
+    private BpmFormService formService;
+    @Resource
+    private BpmProcessDefinitionService processDefinitionService;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
+
+    @GetMapping("todo-page")
+    @Operation(summary = "获取 Todo 待办任务分页")
+    @PreAuthorize("@ss.hasPermission('bpm:task:query')")
+    public CommonResult<PageResult<BpmTaskRespVO>> getTaskTodoPage(@Valid BpmTaskPageReqVO pageVO) {
+        PageResult<Task> pageResult = taskService.getTaskTodoPage(getLoginUserId(), pageVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty());
+        }
+
+        // 拼接数据
+        Map<String, ProcessInstance> processInstanceMap = processInstanceService.getProcessInstanceMap(
+                convertSet(pageResult.getList(), Task::getProcessInstanceId));
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
+        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
+                convertSet(pageResult.getList(), Task::getProcessDefinitionId));
+        return success(BpmTaskConvert.INSTANCE.buildTodoTaskPage(pageResult, processInstanceMap, userMap, processDefinitionInfoMap));
+    }
+
+    @GetMapping("done-page")
+    @Operation(summary = "获取 Done 已办任务分页")
+    @PreAuthorize("@ss.hasPermission('bpm:task:query')")
+    public CommonResult<PageResult<BpmTaskRespVO>> getTaskDonePage(@Valid BpmTaskPageReqVO pageVO) {
+        PageResult<HistoricTaskInstance> pageResult = taskService.getTaskDonePage(getLoginUserId(), pageVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty());
+        }
+
+        // 拼接数据
+        Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
+                convertSet(pageResult.getList(), HistoricTaskInstance::getProcessInstanceId));
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
+                convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
+        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
+                convertSet(pageResult.getList(), HistoricTaskInstance::getProcessDefinitionId));
+        return success(BpmTaskConvert.INSTANCE.buildTaskPage(pageResult, processInstanceMap, userMap, null, processDefinitionInfoMap));
+    }
+
+    @GetMapping("manager-page")
+    @Operation(summary = "获取全部任务的分页", description = "用于【流程任务】菜单")
+    @PreAuthorize("@ss.hasPermission('bpm:task:mananger-query')")
+    public CommonResult<PageResult<BpmTaskRespVO>> getTaskManagerPage(@Valid BpmTaskPageReqVO pageVO) {
+        PageResult<HistoricTaskInstance> pageResult = taskService.getTaskPage(getLoginUserId(), pageVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty());
+        }
+
+        // 拼接数据
+        Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
+                convertSet(pageResult.getList(), HistoricTaskInstance::getProcessInstanceId));
+        // 获得 User 和 Dept Map
+        Set<Long> userIds = convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId()));
+        userIds.addAll(convertSet(pageResult.getList(), task -> NumberUtils.parseLong(task.getAssignee())));
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+                convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
+                convertSet(pageResult.getList(), HistoricTaskInstance::getProcessDefinitionId));
+        return success(BpmTaskConvert.INSTANCE.buildTaskPage(pageResult, processInstanceMap, userMap, deptMap, processDefinitionInfoMap));
+    }
+
+    @GetMapping("/list-by-process-instance-id")
+    @Operation(summary = "获得指定流程实例的任务列表", description = "包括完成的、未完成的")
+    @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:task:query')")
+    public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
+            @RequestParam("processInstanceId") String processInstanceId) {
+        List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true);
+        if (CollUtil.isEmpty(taskList)) {
+            return success(Collections.emptyList());
+        }
+
+        // 拼接数据
+        Set<Long> userIds = convertSetByFlatMap(taskList, task ->
+                Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+                convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+        // 获得 Form Map
+        Map<Long, BpmFormDO> formMap = formService.getFormMap(
+                convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey())));
+        return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList,
+                formMap, userMap, deptMap));
+    }
+
+    @PutMapping("/approve")
+    @Operation(summary = "通过任务")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> approveTask(@Valid @RequestBody BpmTaskApproveReqVO reqVO) {
+        taskService.approveTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @PutMapping("/reject")
+    @Operation(summary = "不通过任务")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> rejectTask(@Valid @RequestBody BpmTaskRejectReqVO reqVO) {
+        taskService.rejectTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @GetMapping("/list-by-return")
+    @Operation(summary = "获取所有可退回的节点", description = "用于【流程详情】的【退回】按钮")
+    @Parameter(name = "taskId", description = "当前任务ID", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) {
+        List<UserTask> userTaskList = taskService.getUserTaskListByReturn(id);
+        return success(convertList(userTaskList, userTask -> // 只返回 id 和 name
+                new BpmTaskRespVO().setName(userTask.getName()).setTaskDefinitionKey(userTask.getId())));
+    }
+
+    @PutMapping("/return")
+    @Operation(summary = "退回任务", description = "用于【流程详情】的【退回】按钮")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
+        taskService.returnTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @PutMapping("/delegate")
+    @Operation(summary = "委派任务", description = "用于【流程详情】的【委派】按钮")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> delegateTask(@Valid @RequestBody BpmTaskDelegateReqVO reqVO) {
+        taskService.delegateTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @PutMapping("/transfer")
+    @Operation(summary = "转派任务", description = "用于【流程详情】的【转派】按钮")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> transferTask(@Valid @RequestBody BpmTaskTransferReqVO reqVO) {
+        taskService.transferTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @PutMapping("/create-sign")
+    @Operation(summary = "加签", description = "before 前加签,after 后加签")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> createSignTask(@Valid @RequestBody BpmTaskSignCreateReqVO reqVO) {
+        taskService.createSignTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete-sign")
+    @Operation(summary = "减签")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> deleteSignTask(@Valid @RequestBody BpmTaskSignDeleteReqVO reqVO) {
+        taskService.deleteSignTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @PutMapping("/copy")
+    @Operation(summary = "抄送任务")
+    @PreAuthorize("@ss.hasPermission('bpm:task:update')")
+    public CommonResult<Boolean> copyTask(@Valid @RequestBody BpmTaskCopyReqVO reqVO) {
+        taskService.copyTask(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @GetMapping("/list-by-parent-task-id")
+    @Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
+    @Parameter(name = "parentTaskId", description = "父级任务编号", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:task:query')")
+    public CommonResult<List<BpmTaskRespVO>> getTaskListByParentTaskId(@RequestParam("parentTaskId") String parentTaskId) {
+        List<Task> taskList = taskService.getTaskListByParentTaskId(parentTaskId);
+        if (CollUtil.isEmpty(taskList)) {
+            return success(Collections.emptyList());
+        }
+        // 拼接数据
+        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(taskList,
+                user -> Stream.of(NumberUtils.parseLong(user.getAssignee()), NumberUtils.parseLong(user.getOwner()))));
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
+                convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
+        return success(BpmTaskConvert.INSTANCE.buildTaskListByParentTaskId(taskList, userMap, deptMap));
+    }
+
+}

+ 25 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/activity/BpmActivityRespVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.activity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 流程活动的 Response VO")
+@Data
+public class BpmActivityRespVO {
+
+    @Schema(description = "流程活动的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String key;
+    @Schema(description = "流程活动的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent")
+    private String type;
+
+    @Schema(description = "流程活动的开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime startTime;
+    @Schema(description = "流程活动的结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime endTime;
+
+    @Schema(description = "关联的流程任务的编号", example = "2048")
+    private String taskId; // 关联的流程任务,只有 UserTask 等类型才有
+
+}

+ 48 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/cc/BpmProcessInstanceCopyRespVO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.cc;
+
+import cn.iocoder.byzs.framework.common.core.KeyValue;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 流程实例抄送的分页 Item Response VO")
+@Data
+public class BpmProcessInstanceCopyRespVO {
+
+    @Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED)
+    private UserSimpleBaseVO startUser;
+
+    @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233")
+    private String processInstanceId;
+    @Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试")
+    private String processInstanceName;
+    @Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime processInstanceStartTime;
+
+    @Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String activityId;
+    @Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String activityName;
+
+    @Schema(description = "流程活动的编号")
+    private String taskId;
+
+    @Schema(description = "抄送人意见")
+    private String reason;
+
+    @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
+    private UserSimpleBaseVO createUser;
+
+    @Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "流程摘要", example = "[]")
+    private List<KeyValue<String, String>> summary;
+
+}

+ 40 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance;
+
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.AssertTrue;
+import lombok.Data;
+
+import java.util.Map;
+
+@Schema(description = "管理后台 - 审批详情 Request VO")
+@Data
+public class BpmApprovalDetailReqVO {
+
+    @Schema(description = "流程定义的编号", example = "1024")
+    private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID
+
+    @Schema(description = "流程变量")
+    private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId,用于流程预测
+
+    @Schema(description = "流程变量")
+    private String processVariablesStr; // 解决 GET 无法传递对象的问题,最终转换成 processVariables 变量
+
+    @Schema(description = "流程实例的编号", example = "1024")
+    private String processInstanceId;  // 使用场景:流程已发起时候传流程实例 ID
+
+    // TODO @lyb:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。
+    @Schema(description = "流程活动编号", example = "StartUserNode")
+    private String activityId; // 用于获取表单权限。1)发起流程时,传“发起人节点” activityId 可获取发起人的表单权限;2)从抄送列表界面进来时,传抄送的 activityId 可获取抄送人的表单权限;
+
+    @Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
+    private String taskId; // 用于获取表单权限。1)从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限
+
+    @AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
+    @JsonIgnore
+    public boolean isValidProcessParam() {
+        return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId);
+    }
+
+}

+ 112 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java

@@ -0,0 +1,112 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance;
+
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+
+@Schema(description = "管理后台 - 审批详情 Response VO")
+@Data
+public class BpmApprovalDetailRespVO {
+
+    @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
+
+    @Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<ActivityNode> activityNodes;
+
+    @Schema(description = "表单字段权限")
+    private Map<String, String> formFieldsPermission;
+
+    @Schema(description = "待办任务")
+    private BpmTaskRespVO todoTask;
+
+    /**
+     * 所属流程定义信息
+     */
+    private BpmProcessDefinitionRespVO processDefinition;
+
+    /**
+     * 所属流程实例信息
+     */
+    private BpmProcessInstanceRespVO processInstance;
+
+    @Schema(description = "活动节点信息")
+    @Data
+    public static class ActivityNode {
+
+        @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode")
+        private String id;
+
+        @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "发起人")
+        private String name;
+
+        @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Integer nodeType; // 参见 BpmSimpleModelNodeType 枚举
+
+        @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+        private Integer status; // 参见 BpmTaskStatusEnum 枚举
+
+        @Schema(description = "节点的开始时间")
+        private LocalDateTime startTime;
+        @Schema(description = "节点的结束时间")
+        private LocalDateTime endTime;
+
+        @Schema(description = "审批节点的任务信息")
+        private List<ActivityNodeTask> tasks;
+
+        @Schema(description = "候选人策略", example = "35")
+        private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选
+
+        @Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
+        @JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers
+        private List<Long> candidateUserIds;
+
+        @Schema(description = "候选人用户列表")
+        private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
+
+        @Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf")
+        private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 processInstanceId 字段)
+
+    }
+
+    @Schema(description = "活动节点的任务信息")
+    @Data
+    public static class ActivityNodeTask {
+
+        @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private String id;
+
+        @Schema(description = "任务所属人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
+        @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
+        private Long owner;
+
+        @Schema(description = "任务所属人", example = "1024")
+        private UserSimpleBaseVO ownerUser;
+
+        @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
+        @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
+        private Long assignee;
+
+        @Schema(description = "任务分配人", example = "2048")
+        private UserSimpleBaseVO assigneeUser;
+
+        @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Integer status;  // 参见 BpmTaskStatusEnum 枚举
+
+        @Schema(description = "审批意见", example = "同意")
+        private String reason;
+
+        @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png")
+        private String signPicUrl;
+
+    }
+
+}

+ 43 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceBpmnModelViewRespVO.java

@@ -0,0 +1,43 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance;
+
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Set;
+
+@Schema(description = "管理后台 - 流程示例的 BPMN 视图 Response VO")
+@Data
+public class BpmProcessInstanceBpmnModelViewRespVO {
+
+    // ========== 基本信息 ==========
+
+    @Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED)
+    private BpmProcessInstanceRespVO processInstance;
+
+    @Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<BpmTaskRespVO> tasks;
+
+    @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String bpmnXml;
+
+    @Schema(description = "SIMPLE 模型")
+    private BpmSimpleModelNodeVO simpleModel;
+
+    // ========== 进度信息 ==========
+
+    @Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Set<String> unfinishedTaskActivityIds; // 只包括 UserTask
+
+    @Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Set<String> finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow
+
+    @Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Set<String> finishedSequenceFlowActivityIds; // 只包括 SequenceFlow
+
+    @Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Set<String> rejectedTaskActivityIds; // 只包括 UserTask
+
+}

+ 20 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCancelReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - 流程实例的取消 Request VO")
+@Data
+public class BpmProcessInstanceCancelReqVO {
+
+    @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "流程实例的编号不能为空")
+    private String id;
+
+    @Schema(description = "取消原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "不请假了!")
+    @NotEmpty(message = "取消原因不能为空")
+    private String reason;
+
+}

+ 23 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCopyPageReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.byzs.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 流程实例抄送的分页 Request VO")
+@Data
+public class BpmProcessInstanceCopyPageReqVO extends PageParam {
+
+    @Schema(description = "流程名称", example = "博雅智算")
+    private String processInstanceName;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 24 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 流程实例的创建 Request VO")
+@Data
+public class BpmProcessInstanceCreateReqVO {
+
+    @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "流程定义编号不能为空")
+    private String processDefinitionId;
+
+    @Schema(description = "变量实例(动态表单)")
+    private Map<String, Object> variables;
+
+    @Schema(description = "发起人自选审批人 Map", example = "{taskKey1: [1, 2]}")
+    private Map<String, List<Long>> startUserSelectAssignees;
+
+}

+ 45 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstancePageReqVO.java

@@ -0,0 +1,45 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.validation.InEnum;
+import cn.iocoder.byzs.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.byzs.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 流程实例分页 Request VO")
+@Data
+public class BpmProcessInstancePageReqVO extends PageParam {
+
+    @Schema(description = "流程名称", example = "博雅智算")
+    private String name;
+
+    @Schema(description = "流程定义的标识", example = "2048")
+    private String processDefinitionKey; // 精准匹配
+
+    @Schema(description = "流程实例的状态", example = "1")
+    @InEnum(BpmProcessInstanceStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "流程分类", example = "1")
+    private String category;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "结束时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] endTime;
+
+    @Schema(description = "发起用户编号", example = "1024")
+    private Long startUserId; // 注意,只有在【流程实例】菜单,才使用该参数
+
+    @Schema(description = "动态表单字段查询 JSON Str", example = "{}")
+    private String formFieldsParams; // SpringMVC 在 get 请求下,无法方便的定义 Map 类型的参数,所以通过 String 接收后,逻辑里面转换
+
+}

+ 86 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceRespVO.java

@@ -0,0 +1,86 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance;
+
+import cn.iocoder.byzs.framework.common.core.KeyValue;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 流程实例的 Response VO")
+@Data
+public class BpmProcessInstanceRespVO {
+
+    @Schema(description = "流程实例的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String id;
+
+    @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    private String name;
+
+    @Schema(description = "流程摘要")
+    private List<KeyValue<String, String>> summary; // 只有流程表单,才有摘要!
+
+    @Schema(description = "流程分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private String category;
+    @Schema(description = "流程分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "请假")
+    private String categoryName;
+
+    @Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
+
+    @Schema(description = "发起时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime startTime;
+
+    @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime endTime;
+
+    @Schema(description = "持续时间", example = "1000")
+    private Long durationInMillis;
+
+    @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Map<String, Object> formVariables;
+
+    @Schema(description = "业务的唯一标识-例如说,请假申请的编号", example = "1")
+    private String businessKey;
+
+    /**
+     * 发起流程的用户
+     */
+    private UserSimpleBaseVO startUser;
+
+    @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    private String processDefinitionId;
+    /**
+     * 流程定义
+     */
+    private BpmProcessDefinitionRespVO processDefinition;
+
+    /**
+     * 当前审批中的任务
+     */
+    private List<Task> tasks; // 仅在流程实例分页才返回
+
+    @Schema(description = "流程任务")
+    @Data
+    public static class Task {
+
+        @Schema(description = "流程任务的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+        private String id;
+
+        @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+        private String name;
+
+        @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
+        @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
+        private Long assignee;
+
+        @Schema(description = "任务分配人", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
+        private UserSimpleBaseVO assigneeUser;
+
+    }
+
+}

+ 30 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 通过流程任务的 Request VO")
+@Data
+public class BpmTaskApproveReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "审批意见", example = "不错不错!")
+    private String reason;
+
+    @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png")
+    private String signPicUrl;
+
+    @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Map<String, Object> variables;
+
+    @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}")
+    private Map<String, List<Long>> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况
+
+}

+ 23 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskCopyReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.util.Collection;
+
+@Schema(description = "管理后台 - 抄送流程任务的 Request VO")
+@Data
+public class BpmTaskCopyReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]")
+    @NotEmpty(message = "抄送用户不能为空")
+    private Collection<Long> copyUserIds;
+
+    @Schema(description = "抄送意见", example = "帮忙看看!")
+    private String reason;
+}

+ 24 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskDelegateReqVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 委派流程任务的 Request VO")
+@Data
+public class BpmTaskDelegateReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "被委派人 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "被委派人 ID 不能为空")
+    private Long delegateUserId;
+
+    @Schema(description = "委派原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "做不了决定,需要你先帮忙瞅瞅")
+    @NotEmpty(message = "委派原因不能为空")
+    private String reason;
+
+}

+ 28 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskPageReqVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import cn.iocoder.byzs.framework.common.pojo.PageParam;
+import cn.iocoder.byzs.framework.common.util.date.DateUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 流程任务的的分页 Request VO") // 待办、已办,都使用该分页
+@Data
+public class BpmTaskPageReqVO extends PageParam {
+
+    @Schema(description = "流程任务名", example = "博雅智算")
+    private String name;
+
+    @Schema(description = "流程分类", example = "1")
+    private String category;
+
+    @Schema(description = "流程定义的标识", example = "2048")
+    private String processDefinitionKey; // 精准匹配
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 19 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskRejectReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - 不通过流程任务的 Request VO")
+@Data
+public class BpmTaskRejectReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "审批意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "不错不错!")
+    private String reason;
+
+}

+ 128 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java

@@ -0,0 +1,128 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import cn.iocoder.byzs.framework.common.core.KeyValue;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 流程任务 Response VO")
+@Data
+public class BpmTaskRespVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String id;
+
+    @Schema(description = "任务名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+    private String name;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime endTime;
+
+    @Schema(description = "持续时间", example = "1000")
+    private Long durationInMillis;
+
+    @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private Integer status; // 参见 BpmTaskStatusEnum 枚举
+
+    @Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private String reason;
+
+    @Schema(description = "任务负责人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
+    @JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
+    private Long owner;
+    /**
+     * 负责人的用户信息
+     */
+    private UserSimpleBaseVO ownerUser;
+
+    @Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
+    @JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
+    private Long assignee;
+    /**
+     * 审核的用户信息
+     */
+    private UserSimpleBaseVO assigneeUser;
+
+    @Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
+    private String taskDefinitionKey;
+
+    @Schema(description = "所属流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8888")
+    private String processInstanceId;
+    /**
+     * 所属流程实例
+     */
+    private ProcessInstance processInstance;
+
+    @Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String parentTaskId;
+    @Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask")
+    private List<BpmTaskRespVO> children; // 由加签生成,包含多层子任务
+
+    @Schema(description = "表单编号", example = "1024")
+    private Long formId;
+    @Schema(description = "表单名字", example = "请假表单")
+    private String formName;
+    @Schema(description = "表单的配置,JSON 字符串")
+    private String formConf;
+    @Schema(description = "表单项的数组")
+    private List<String> formFields;
+    @Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Map<String, Object> formVariables;
+    @Schema(description = "操作按钮设置值")
+    private Map<Integer, OperationButtonSetting> buttonsSetting;
+
+    @Schema(description = "是否需要签名", example = "false")
+    private Boolean signEnable;
+
+    @Schema(description = "是否填写审批意见", example = "false")
+    private Boolean reasonRequire;
+
+    @Schema(description = "节点类型", example = "10")
+    private Integer nodeType; // 参见 BpmSimpleModelNodeTypeEnum 枚举。
+
+    @Data
+    @Schema(description = "流程实例")
+    public static class ProcessInstance {
+
+        @Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+        private String id;
+
+        @Schema(description = "流程实例名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "博雅智算")
+        private String name;
+
+        @Schema(description = "提交时间", requiredMode = Schema.RequiredMode.REQUIRED)
+        private LocalDateTime createTime;
+
+        @Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+        private String processDefinitionId;
+
+        @Schema(description = "流程摘要", example = "[]")
+        private List<KeyValue<String, String>> summary; // 只有流程表单,才有摘要!
+
+        /**
+         * 发起人的用户信息
+         */
+        private UserSimpleBaseVO startUser;
+
+    }
+
+    @Data
+    @Schema(description = "操作按钮设置")
+    public static class OperationButtonSetting {
+
+        @Schema(description = "显示名称", example = "审批")
+        private String displayName;
+
+        @Schema(description = "是否启用", example = "true")
+        private Boolean enable;
+    }
+
+}

+ 24 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - 退回流程任务的 Request VO")
+@Data
+public class BpmTaskReturnReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "退回到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotEmpty(message = "退回到的任务 Key 不能为空")
+    private String targetTaskDefinitionKey;
+
+    @Schema(description = "退回意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回")
+    @NotEmpty(message = "退回意见不能为空")
+    private String reason;
+
+}

+ 29 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskSignCreateReqVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+import java.util.Set;
+
+@Schema(description = "管理后台 - 加签任务的创建(加签) Request VO")
+@Data
+public class BpmTaskSignCreateReqVO {
+
+    @Schema(description = "需要加签的任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "加签的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
+    @NotEmpty(message = "加签用户不能为空")
+    private Set<Long> userIds;
+
+    @Schema(description = "加签类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "before")
+    @NotEmpty(message = "加签类型不能为空")
+    private String type; // 参见 BpmTaskSignTypeEnum 枚举
+
+    @Schema(description = "加签原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "需要加签")
+    @NotEmpty(message = "加签原因不能为空")
+    private String reason;
+
+}

+ 20 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskSignDeleteReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - 加签任务的删除(减签) Request VO")
+@Data
+public class BpmTaskSignDeleteReqVO {
+
+    @Schema(description = "被减签的任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "加签原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "需要减签")
+    @NotEmpty(message = "加签原因不能为空")
+    private String reason;
+
+}

+ 25 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/admin/task/vo/task/BpmTaskTransferReqVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 流程任务的转办 Request VO")
+@Data
+public class BpmTaskTransferReqVO {
+
+    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "任务编号不能为空")
+    private String id;
+
+    @Schema(description = "新审批人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
+    @NotNull(message = "新审批人的用户编号不能为空")
+    private Long assigneeUserId;
+
+    @Schema(description = "转办原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "做不了决定,需要你先帮忙瞅瞅")
+    @NotEmpty(message = "转办原因不能为空")
+    private String reason;
+
+}

+ 4 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/app/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package cn.iocoder.byzs.module.bpm.controller.app;

+ 6 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/controller/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 提供 RESTful API 给前端:
+ * 1. admin 包:提供给管理后台 byzs-ui-admin 前端项目
+ * 2. app 包:提供给用户 APP byzs-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分
+ */
+package cn.iocoder.byzs.module.bpm.controller;

+ 132 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/definition/BpmModelConvert.java

@@ -0,0 +1,132 @@
+package cn.iocoder.byzs.module.bpm.convert.definition;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.byzs.framework.common.util.date.DateUtils;
+import cn.iocoder.byzs.framework.common.util.json.JsonUtils;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.dept.DeptSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.BpmModelRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.byzs.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.byzs.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import org.flowable.common.engine.impl.db.SuspensionState;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.Model;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertList;
+
+/**
+ * 流程模型 Convert
+ *
+ * @author yunlongn
+ */
+@Mapper
+public interface BpmModelConvert {
+
+    BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class);
+
+    default List<BpmModelRespVO> buildModelList(List<Model> list,
+                                                Map<Long, BpmFormDO> formMap,
+                                                Map<String, BpmCategoryDO> categoryMap,
+                                                Map<String, Deployment> deploymentMap,
+                                                Map<String, ProcessDefinition> processDefinitionMap,
+                                                Map<Long, AdminUserRespDTO> userMap,
+                                                Map<Long, DeptRespDTO> deptMap) {
+        List<BpmModelRespVO> result = convertList(list, model -> {
+            BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
+            BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
+            BpmCategoryDO category = categoryMap.get(model.getCategory());
+            Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null;
+            ProcessDefinition processDefinition = model.getDeploymentId() != null ?
+                    processDefinitionMap.get(model.getDeploymentId()) : null;
+            List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null;
+            List<DeptRespDTO> startDepts = metaInfo != null ? convertList(metaInfo.getStartDeptIds(), deptMap::get) : null;
+            return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers, startDepts);
+        });
+        // 排序
+        result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort));
+        return result;
+    }
+
+    default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes, BpmSimpleModelNodeVO simpleModel) {
+        BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
+        BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null, null);
+        if (ArrayUtil.isNotEmpty(bpmnBytes)) {
+            modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes));
+        }
+        modelVO.setSimpleModel(simpleModel);
+        return modelVO;
+    }
+
+    default BpmModelRespVO buildModel0(Model model,
+                                       BpmModelMetaInfoVO metaInfo, BpmFormDO form, BpmCategoryDO category,
+                                       Deployment deployment, ProcessDefinition processDefinition,
+                                       List<AdminUserRespDTO> startUsers, List<DeptRespDTO> startDepts) {
+        BpmModelRespVO modelRespVO = new BpmModelRespVO().setId(model.getId()).setName(model.getName())
+                .setKey(model.getKey()).setCategory(model.getCategory())
+                .setCreateTime(DateUtils.of(model.getCreateTime()));
+        // Form
+        BeanUtils.copyProperties(metaInfo, modelRespVO);
+        if (form != null) {
+            modelRespVO.setFormName(form.getName());
+        }
+        // Category
+        if (category != null) {
+            modelRespVO.setCategoryName(category.getName());
+        }
+        // ProcessDefinition
+        if (processDefinition != null) {
+            modelRespVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
+            modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ?
+                    SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
+            if (deployment != null) {
+                modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime()));
+            }
+        }
+        // User、Dept
+        modelRespVO.setStartUsers(BeanUtils.toBean(startUsers, UserSimpleBaseVO.class))
+                .setStartDepts(BeanUtils.toBean(startDepts, DeptSimpleBaseVO.class));
+        return modelRespVO;
+    }
+
+    default void copyToModel(Model model, BpmModelSaveReqVO reqVO) {
+        model.setName(reqVO.getName());
+        model.setKey(reqVO.getKey());
+        model.setCategory(reqVO.getCategory());
+        model.setMetaInfo(JsonUtils.toJsonString(BeanUtils.toBean(reqVO, BpmModelMetaInfoVO.class)));
+    }
+
+    default BpmModelMetaInfoVO parseMetaInfo(Model model) {
+        BpmModelMetaInfoVO vo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoVO.class);
+        if (vo == null) {
+            return null;
+        }
+        if (vo.getManagerUserIds() == null) {
+            vo.setManagerUserIds(Collections.emptyList());
+        }
+        if (vo.getStartUserIds() == null) {
+            vo.setStartUserIds(Collections.emptyList());
+        }
+        // 如果为空,兜底处理,使用 createTime 创建时间
+        if (vo.getSort() == null) {
+            vo.setSort(model.getCreateTime().getTime());
+        }
+        return vo;
+    }
+
+}

+ 99 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/definition/BpmProcessDefinitionConvert.java

@@ -0,0 +1,99 @@
+package cn.iocoder.byzs.module.bpm.convert.definition;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.common.engine.impl.db.SuspensionState;
+import org.flowable.engine.repository.Deployment;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.factory.Mappers;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Bpm 流程定义的 Convert
+ *
+ * @author yunlong.li
+ */
+@Mapper
+public interface BpmProcessDefinitionConvert {
+
+    BpmProcessDefinitionConvert INSTANCE = Mappers.getMapper(BpmProcessDefinitionConvert.class);
+
+    default PageResult<BpmProcessDefinitionRespVO> buildProcessDefinitionPage(PageResult<ProcessDefinition> page,
+                                                                              Map<String, Deployment> deploymentMap,
+                                                                              Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap,
+                                                                              Map<Long, BpmFormDO> formMap,
+                                                                              Map<String, BpmCategoryDO> categoryMap) {
+        List<BpmProcessDefinitionRespVO> list = buildProcessDefinitionList(page.getList(), deploymentMap, processDefinitionInfoMap, formMap, categoryMap);
+        return new PageResult<>(list, page.getTotal());
+    }
+
+    default List<BpmProcessDefinitionRespVO> buildProcessDefinitionList(List<ProcessDefinition> list,
+                                                                        Map<String, Deployment> deploymentMap,
+                                                                        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap,
+                                                                        Map<Long, BpmFormDO> formMap,
+                                                                        Map<String, BpmCategoryDO> categoryMap) {
+        List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> {
+            Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class);
+            BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class);
+            BpmFormDO form = null;
+            if (processDefinitionInfo != null) {
+                form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class);
+            }
+            BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class);
+            return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null);
+        });
+        // 排序
+        result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort));
+        return result;
+    }
+
+    default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition,
+                                                              Deployment deployment,
+                                                              BpmProcessDefinitionInfoDO processDefinitionInfo,
+                                                              BpmFormDO form,
+                                                              BpmCategoryDO category,
+                                                              BpmnModel bpmnModel) {
+        BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class);
+        respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
+        // Deployment
+        if (deployment != null) {
+            respVO.setDeploymentTime(LocalDateTimeUtil.of(deployment.getDeploymentTime()));
+        }
+        // BpmProcessDefinitionInfoDO
+        if (processDefinitionInfo != null) {
+            copyTo(processDefinitionInfo, respVO);
+            // Form
+            if (form != null) {
+                respVO.setFormName(form.getName());
+            }
+        }
+        // Category
+        if (category != null) {
+            respVO.setCategoryName(category.getName());
+        }
+        // BpmnModel
+        if (bpmnModel != null) {
+            respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
+        }
+        return respVO;
+    }
+
+    @Mapping(source = "from.id", target = "to.id", ignore = true)
+    void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to);
+
+}

+ 21 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/message/BpmMessageConvert.java

@@ -0,0 +1,21 @@
+package cn.iocoder.byzs.module.bpm.convert.message;
+
+import cn.iocoder.byzs.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+import java.util.Map;
+
+@Mapper
+public interface BpmMessageConvert {
+
+    BpmMessageConvert INSTANCE = Mappers.getMapper(BpmMessageConvert.class);
+
+    @Mapping(target = "mobile", ignore = true)
+    @Mapping(source = "userId", target = "userId")
+    @Mapping(source = "templateCode", target = "templateCode")
+    @Mapping(source = "templateParams", target = "templateParams")
+    SmsSendSingleToUserReqDTO convert(Long userId, String templateCode, Map<String, Object> templateParams);
+
+}

+ 6 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 提供 POJO 类的实体转换
+ *
+ * 目前使用 MapStruct 框架
+ */
+package cn.iocoder.byzs.module.bpm.convert;

+ 295 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/task/BpmProcessInstanceConvert.java

@@ -0,0 +1,295 @@
+package cn.iocoder.byzs.module.bpm.convert.task;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.collection.MapUtils;
+import cn.iocoder.byzs.framework.common.util.collection.SetUtils;
+import cn.iocoder.byzs.framework.common.util.number.NumberUtils;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import cn.iocoder.byzs.module.bpm.convert.definition.BpmProcessDefinitionConvert;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.byzs.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.byzs.module.bpm.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.byzs.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
+import cn.iocoder.byzs.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
+import cn.iocoder.byzs.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import org.flowable.bpmn.model.BpmnModel;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.task.api.Task;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.factory.Mappers;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertSet;
+
+/**
+ * 流程实例 Convert
+ *
+ * @author 博雅智算源码
+ */
+@Mapper
+public interface BpmProcessInstanceConvert {
+
+    BpmProcessInstanceConvert INSTANCE = Mappers.getMapper(BpmProcessInstanceConvert.class);
+
+    default PageResult<BpmProcessInstanceRespVO> buildProcessInstancePage(PageResult<HistoricProcessInstance> pageResult,
+                                                                          Map<String, ProcessDefinition> processDefinitionMap,
+                                                                          Map<String, BpmCategoryDO> categoryMap,
+                                                                          Map<String, List<Task>> taskMap,
+                                                                          Map<Long, AdminUserRespDTO> userMap,
+                                                                          Map<Long, DeptRespDTO> deptMap,
+                                                                          Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap) {
+        PageResult<BpmProcessInstanceRespVO> vpPageResult = BeanUtils.toBean(pageResult, BpmProcessInstanceRespVO.class);
+        for (int i = 0; i < pageResult.getList().size(); i++) {
+            BpmProcessInstanceRespVO respVO = vpPageResult.getList().get(i);
+            respVO.setStatus(FlowableUtils.getProcessInstanceStatus(pageResult.getList().get(i)));
+            MapUtils.findAndThen(processDefinitionMap, respVO.getProcessDefinitionId(),
+                    processDefinition -> respVO.setCategory(processDefinition.getCategory())
+                            .setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class)));
+            MapUtils.findAndThen(categoryMap, respVO.getCategory(), category -> respVO.setCategoryName(category.getName()));
+            respVO.setTasks(BeanUtils.toBean(taskMap.get(respVO.getId()), BpmProcessInstanceRespVO.Task.class));
+            // user
+            if (userMap != null) {
+                AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId()));
+                if (startUser != null) {
+                    respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
+                    MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName()));
+                }
+                if (CollUtil.isNotEmpty(respVO.getTasks())) {
+                    respVO.getTasks().forEach(task -> {
+                        AdminUserRespDTO assigneeUser = userMap.get(task.getAssignee());
+                        if (assigneeUser!= null) {
+                            task.setAssigneeUser(BeanUtils.toBean(assigneeUser, UserSimpleBaseVO.class));
+                            MapUtils.findAndThen(deptMap, assigneeUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName()));
+                        }
+                    });
+                }
+            }
+            // 摘要
+            respVO.setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(respVO.getProcessDefinitionId()),
+                    pageResult.getList().get(i).getProcessVariables()));
+            // 表单
+            respVO.setFormVariables(pageResult.getList().get(i).getProcessVariables());
+        }
+        return vpPageResult;
+    }
+
+    default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance,
+                                                          ProcessDefinition processDefinition,
+                                                          BpmProcessDefinitionInfoDO processDefinitionInfo,
+                                                          AdminUserRespDTO startUser,
+                                                          DeptRespDTO dept) {
+        BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class);
+        respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))
+                .setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
+        // definition
+        respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
+        copyTo(processDefinitionInfo, respVO.getProcessDefinition());
+        // user
+        if (startUser != null) {
+            respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
+            if (dept != null) {
+                respVO.getStartUser().setDeptName(dept.getName());
+            }
+        }
+        return respVO;
+    }
+
+    @Mapping(source = "from.id", target = "to.id", ignore = true)
+    void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to);
+
+    default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {
+        return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status)
+                .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey());
+    }
+
+    default BpmMessageSendWhenProcessInstanceApproveReqDTO buildProcessInstanceApproveMessage(ProcessInstance instance) {
+        return new BpmMessageSendWhenProcessInstanceApproveReqDTO()
+                .setStartUserId(NumberUtils.parseLong(instance.getStartUserId()))
+                .setProcessInstanceId(instance.getId())
+                .setProcessInstanceName(instance.getName());
+    }
+
+    default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) {
+        return new BpmMessageSendWhenProcessInstanceRejectReqDTO()
+            .setProcessInstanceName(instance.getName())
+            .setProcessInstanceId(instance.getId())
+            .setReason(reason)
+            .setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
+    }
+
+    default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance,
+                                                                                    List<HistoricTaskInstance> taskInstances,
+                                                                                    BpmnModel bpmnModel,
+                                                                                    BpmSimpleModelNodeVO simpleModel,
+                                                                                    Set<String> unfinishedTaskActivityIds,
+                                                                                    Set<String> finishedTaskActivityIds,
+                                                                                    Set<String> finishedSequenceFlowActivityIds,
+                                                                                    Set<String> rejectTaskActivityIds,
+                                                                                    Map<Long, AdminUserRespDTO> userMap,
+                                                                                    Map<Long, DeptRespDTO> deptMap) {
+        BpmProcessInstanceBpmnModelViewRespVO respVO = new BpmProcessInstanceBpmnModelViewRespVO();
+        // 基本信息
+        respVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class, o -> o
+                        .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)))
+                        .setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap)));
+        respVO.setTasks(convertList(taskInstances, task -> BeanUtils.toBean(task, BpmTaskRespVO.class)
+                .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task))
+                .setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap))
+                .setOwnerUser(buildUser(task.getOwner(), userMap, deptMap))));
+        respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
+        respVO.setSimpleModel(simpleModel);
+        // 进度信息
+        respVO.setUnfinishedTaskActivityIds(unfinishedTaskActivityIds)
+                .setFinishedTaskActivityIds(finishedTaskActivityIds)
+                .setFinishedSequenceFlowActivityIds(finishedSequenceFlowActivityIds)
+                .setRejectedTaskActivityIds(rejectTaskActivityIds);
+        return respVO;
+    }
+
+    default UserSimpleBaseVO buildUser(String userIdStr,
+                                       Map<Long, AdminUserRespDTO> userMap,
+                                       Map<Long, DeptRespDTO> deptMap) {
+        if (StrUtil.isEmpty(userIdStr)) {
+            return null;
+        }
+        Long userId = NumberUtils.parseLong(userIdStr);
+        return buildUser(userId, userMap, deptMap);
+    }
+
+    default UserSimpleBaseVO buildUser(Long userId,
+                                                    Map<Long, AdminUserRespDTO> userMap,
+                                                    Map<Long, DeptRespDTO> deptMap) {
+        if (userId == null) {
+            return null;
+        }
+        AdminUserRespDTO user = userMap.get(userId);
+        if (user == null) {
+            return null;
+        }
+        UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class);
+        DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null;
+        if (dept != null) {
+            userVO.setDeptName(dept.getName());
+        }
+        return userVO;
+    }
+
+    default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) {
+        if (task == null) {
+            return null;
+        }
+        return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class)
+                .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task))
+                .setSignPicUrl(FlowableUtils.getTaskSignPicUrl(task));
+    }
+
+    default Set<Long> parseUserIds(HistoricProcessInstance processInstance,
+                                   List<BpmApprovalDetailRespVO.ActivityNode> activityNodes,
+                                   BpmTaskRespVO todoTask) {
+        Set<Long> userIds = new HashSet<>();
+        if (processInstance != null) {
+            userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
+        }
+        for (BpmApprovalDetailRespVO.ActivityNode activityNode : activityNodes) {
+            CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getAssignee));
+            CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getOwner));
+            CollUtil.addAll(userIds, activityNode.getCandidateUserIds());
+        }
+        if (todoTask != null) {
+            CollUtil.addIfAbsent(userIds, todoTask.getAssignee());
+            CollUtil.addIfAbsent(userIds, todoTask.getOwner());
+            if (CollUtil.isNotEmpty(todoTask.getChildren())) {
+                CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getAssignee));
+                CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getOwner));
+            }
+        }
+        return userIds;
+    }
+
+    default Set<Long> parseUserIds02(HistoricProcessInstance processInstance,
+                                     List<HistoricTaskInstance> tasks) {
+        Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
+        tasks.forEach(task -> {
+            CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee())));
+            CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner())));
+        });
+        return userIds;
+    }
+
+    default BpmApprovalDetailRespVO buildApprovalDetail(BpmnModel bpmnModel,
+                                                        ProcessDefinition processDefinition,
+                                                        BpmProcessDefinitionInfoDO processDefinitionInfo,
+                                                        HistoricProcessInstance processInstance,
+                                                        Integer processInstanceStatus,
+                                                        List<BpmApprovalDetailRespVO.ActivityNode> activityNodes,
+                                                        BpmTaskRespVO todoTask,
+                                                        Map<String, String> formFieldsPermission,
+                                                        Map<Long, AdminUserRespDTO> userMap,
+                                                        Map<Long, DeptRespDTO> deptMap) {
+        // 1.1 流程实例
+        BpmProcessInstanceRespVO processInstanceResp = null;
+        if (processInstance != null) {
+            AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
+            DeptRespDTO dept = startUser != null ? deptMap.get(startUser.getDeptId()) : null;
+            processInstanceResp = buildProcessInstance(processInstance, null, null, startUser, dept);
+        }
+
+        // 1.2 流程定义
+        BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
+                processDefinition, null, processDefinitionInfo, null, null, bpmnModel);
+
+        // 1.3 流程节点
+        activityNodes.forEach(approveNode -> {
+            if (approveNode.getTasks() != null) {
+                approveNode.getTasks().forEach(task -> {
+                    task.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap));
+                    task.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap));
+                });
+            }
+            approveNode.setCandidateUsers(convertList(approveNode.getCandidateUserIds(), userId -> buildUser(userId, userMap, deptMap)));
+        });
+
+        // 1.4 待办任务
+        if (todoTask != null) {
+            todoTask.setAssigneeUser(buildUser(todoTask.getAssignee(), userMap, deptMap));
+            todoTask.setOwnerUser(buildUser(todoTask.getOwner(), userMap, deptMap));
+            if (CollUtil.isNotEmpty(todoTask.getChildren())) {
+                todoTask.getChildren().forEach(childTask -> {
+                    childTask.setAssigneeUser(buildUser(childTask.getAssignee(), userMap, deptMap));
+                    childTask.setOwnerUser(buildUser(childTask.getOwner(), userMap, deptMap));
+                });
+            }
+        }
+
+        // 2. 拼接起来
+        return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus)
+                .setProcessDefinition(definitionResp)
+                .setProcessInstance(processInstanceResp)
+                .setFormFieldsPermission(formFieldsPermission)
+                .setTodoTask(todoTask)
+                .setActivityNodes(activityNodes);
+    }
+
+}

+ 221 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/convert/task/BpmTaskConvert.java

@@ -0,0 +1,221 @@
+package cn.iocoder.byzs.module.bpm.convert.task;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.byzs.framework.common.util.date.DateUtils;
+import cn.iocoder.byzs.framework.common.util.number.NumberUtils;
+import cn.iocoder.byzs.framework.common.util.object.BeanUtils;
+import cn.iocoder.byzs.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.byzs.module.bpm.framework.flowable.core.util.FlowableUtils;
+import cn.iocoder.byzs.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
+import cn.iocoder.byzs.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import org.flowable.engine.history.HistoricProcessInstance;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.task.api.Task;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.byzs.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.byzs.framework.common.util.collection.MapUtils.findAndThen;
+
+/**
+ * Bpm 任务 Convert
+ *
+ * @author 博雅智算源码
+ */
+@Mapper
+public interface BpmTaskConvert {
+
+    BpmTaskConvert INSTANCE = Mappers.getMapper(BpmTaskConvert.class);
+
+    default PageResult<BpmTaskRespVO> buildTodoTaskPage(PageResult<Task> pageResult,
+                                                        Map<String, ProcessInstance> processInstanceMap,
+                                                        Map<Long, AdminUserRespDTO> userMap,
+                                                        Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap) {
+        return BeanUtils.toBean(pageResult, BpmTaskRespVO.class, taskVO -> {
+            ProcessInstance processInstance = processInstanceMap.get(taskVO.getProcessInstanceId());
+            if (processInstance == null) {
+                return;
+            }
+            taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
+            AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
+            taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
+            taskVO.getProcessInstance().setCreateTime(DateUtils.of(processInstance.getStartTime()));
+            // 摘要
+            taskVO.getProcessInstance().setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(processInstance.getProcessDefinitionId()),
+                    processInstance.getProcessVariables()));
+        });
+    }
+
+    default PageResult<BpmTaskRespVO> buildTaskPage(PageResult<HistoricTaskInstance> pageResult,
+                                                    Map<String, HistoricProcessInstance> processInstanceMap,
+                                                    Map<Long, AdminUserRespDTO> userMap,
+                                                    Map<Long, DeptRespDTO> deptMap,
+                                                    Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap) {
+        List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(pageResult.getList(), task -> {
+            BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
+            taskVO.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
+            // 用户信息
+            AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
+            if (assignUser != null) {
+                taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
+                findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName()));
+            }
+            // 流程实例
+            HistoricProcessInstance processInstance = processInstanceMap.get(taskVO.getProcessInstanceId());
+            if (processInstance != null) {
+                AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
+                taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
+                taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
+                // 摘要
+                taskVO.getProcessInstance().setSummary(FlowableUtils.getSummary(processDefinitionInfoMap.get(processInstance.getProcessDefinitionId()),
+                        processInstance.getProcessVariables()));
+            }
+            return taskVO;
+        });
+        return new PageResult<>(taskVOList, pageResult.getTotal());
+    }
+
+    default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList,
+                                                                 Map<Long, BpmFormDO> formMap,
+                                                                 Map<Long, AdminUserRespDTO> userMap,
+                                                                 Map<Long, DeptRespDTO> deptMap) {
+        return CollectionUtils.convertList(taskList, task -> {
+            // 特殊:已取消的任务,不返回
+            BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
+            Integer taskStatus = FlowableUtils.getTaskStatus(task);
+            if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) {
+                return null;
+            }
+            taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task));
+            // 表单信息
+            BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class);
+            if (form != null) {
+                taskVO.setFormId(form.getId()).setFormName(form.getName()).setFormConf(form.getConf())
+                        .setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task));
+            }
+            // 用户信息
+            buildTaskAssignee(taskVO, task.getAssignee(), userMap, deptMap);
+            buildTaskOwner(taskVO, task.getOwner(), userMap, deptMap);
+            return taskVO;
+        });
+    }
+
+    default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList,
+                                                            Map<Long, AdminUserRespDTO> userMap,
+                                                            Map<Long, DeptRespDTO> deptMap) {
+        return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> {
+            AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
+            if (assignUser != null) {
+                taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
+                DeptRespDTO dept = deptMap.get(assignUser.getDeptId());
+                if (dept != null) {
+                    taskVO.getAssigneeUser().setDeptName(dept.getName());
+                }
+            }
+            AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
+            if (ownerUser != null) {
+                taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
+                findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
+            }
+        }));
+    }
+
+    default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks,
+                                        Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting,
+                                        BpmFormDO form) {
+        BpmTaskRespVO bpmTaskRespVO = BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
+                .setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask))
+                .setButtonsSetting(buttonsSetting)
+                .setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class)
+                        .setStatus(FlowableUtils.getTaskStatus(childTask))));
+        if (form != null) {
+            bpmTaskRespVO.setFormId(form.getId()).setFormName(form.getName())
+                    .setFormConf(form.getConf()).setFormFields(form.getFields());
+        }
+        return bpmTaskRespVO;
+    }
+
+    default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
+                                                        Task task) {
+        BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
+        reqDTO.setProcessInstanceId(processInstance.getProcessInstanceId())
+                .setProcessInstanceName(processInstance.getName()).setStartUserId(startUser.getId())
+                .setStartUserNickname(startUser.getNickname()).setTaskId(task.getId()).setTaskName(task.getName())
+                .setAssigneeUserId(NumberUtils.parseLong(task.getAssignee()));
+        return reqDTO;
+    }
+
+    default void buildTaskOwner(BpmTaskRespVO task, String taskOwner,
+                                Map<Long, AdminUserRespDTO> userMap,
+                                Map<Long, DeptRespDTO> deptMap) {
+        AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner));
+        if (ownerUser != null) {
+            task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
+            findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName()));
+        }
+    }
+
+    default void buildTaskChildren(BpmTaskRespVO task, Map<String, List<Task>> childrenTaskMap,
+                                   Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
+        List<Task> childTasks = childrenTaskMap.get(task.getId());
+        if (CollUtil.isNotEmpty(childTasks)) {
+            task.setChildren(
+                    convertList(childTasks, childTask -> {
+                        BpmTaskRespVO childTaskVO = BeanUtils.toBean(childTask, BpmTaskRespVO.class);
+                        childTaskVO.setStatus(FlowableUtils.getTaskStatus(childTask));
+                        buildTaskOwner(childTaskVO, childTask.getOwner(), userMap, deptMap);
+                        buildTaskAssignee(childTaskVO, childTask.getAssignee(), userMap, deptMap);
+                        return childTaskVO;
+                    })
+            );
+        }
+    }
+
+    default void buildTaskAssignee(BpmTaskRespVO task, String taskAssignee,
+                                   Map<Long, AdminUserRespDTO> userMap,
+                                   Map<Long, DeptRespDTO> deptMap) {
+        AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee));
+        if (assignUser != null) {
+            task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
+            findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName()));
+        }
+    }
+
+    /**
+     * 将父任务的属性,拷贝到子任务(加签任务)
+     * <p>
+     * 为什么不使用 mapstruct 映射?因为 TaskEntityImpl 还有很多其他属性,这里我们只设置我们需要的。
+     * 使用 mapstruct 会将里面嵌套的各个属性值都设置进去,会出现意想不到的问题。
+     *
+     * @param parentTask 父任务
+     * @param childTask  加签任务
+     */
+    default void copyTo(TaskEntityImpl parentTask, TaskEntityImpl childTask) {
+        childTask.setName(parentTask.getName());
+        childTask.setDescription(parentTask.getDescription());
+        childTask.setCategory(parentTask.getCategory());
+        childTask.setParentTaskId(parentTask.getId());
+        childTask.setProcessDefinitionId(parentTask.getProcessDefinitionId());
+        childTask.setProcessInstanceId(parentTask.getProcessInstanceId());
+        childTask.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
+        childTask.setTaskDefinitionId(parentTask.getTaskDefinitionId());
+        childTask.setPriority(parentTask.getPriority());
+        childTask.setCreateTime(new Date());
+        childTask.setTenantId(parentTask.getTenantId());
+    }
+
+}

+ 54 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmCategoryDO.java

@@ -0,0 +1,54 @@
+package cn.iocoder.byzs.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * BPM 流程分类 DO
+ *
+ * @author 博雅智算源码
+ */
+@TableName("bpm_category")
+@KeySequence("bpm_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmCategoryDO extends BaseDO {
+
+    /**
+     * 分类编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 分类名
+     */
+    private String name;
+    /**
+     * 分类标志
+     */
+    private String code;
+    /**
+     * 分类描述
+     */
+    private String description;
+    /**
+     * 分类状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 分类排序
+     */
+    private Integer sort;
+
+}

+ 60 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmFormDO.java

@@ -0,0 +1,60 @@
+package cn.iocoder.byzs.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+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.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * BPM 工作流的表单定义
+ * 用于工作流的申请表单,需要动态配置的场景
+ *
+ * @author 博雅智算源码
+ */
+@TableName(value = "bpm_form", autoResultMap = true)
+@KeySequence("bpm_form_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@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<String> fields;
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 222 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java

@@ -0,0 +1,222 @@
+package cn.iocoder.byzs.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.byzs.framework.mybatis.core.type.LongListTypeHandler;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
+import cn.iocoder.byzs.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
+import cn.iocoder.byzs.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import cn.iocoder.byzs.module.bpm.enums.definition.BpmModelTypeEnum;
+import cn.iocoder.byzs.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+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.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.flowable.engine.repository.Model;
+import org.flowable.engine.repository.ProcessDefinition;
+
+import java.util.List;
+
+/**
+ * BPM 流程定义的拓信息
+ * 主要解决 Flowable {@link org.flowable.engine.repository.ProcessDefinition} 不支持拓展字段,所以新建该表
+ *
+ * @author 博雅智算源码
+ */
+@TableName(value = "bpm_process_definition_info", autoResultMap = true)
+@KeySequence("bpm_process_definition_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmProcessDefinitionInfoDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 流程定义的编号
+     *
+     * 关联 {@link ProcessDefinition#getId()} 属性
+     */
+    private String processDefinitionId;
+    /**
+     * 流程模型的编号
+     *
+     * 关联 {@link Model#getId()} 属性
+     */
+    private String modelId;
+    /**
+     * 流程模型的类型
+     *
+     * 枚举 {@link BpmModelTypeEnum}
+     */
+    private Integer modelType;
+
+    /**
+     * 流程分类的编码
+     *
+     * 关联 {@link BpmCategoryDO#getCode()}
+     *
+     * 为什么要存储?原因是,{@link ProcessDefinition#getCategory()} 无法设置
+     */
+    private String category;
+    /**
+     * 图标
+     */
+    private String icon;
+    /**
+     * 描述
+     */
+    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<String> formFields;
+    /**
+     * 自定义表单的提交路径,使用 Vue 的路由地址
+     *
+     * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
+     */
+    private String formCustomCreatePath;
+    /**
+     * 自定义表单的查看路径,使用 Vue 的路由地址
+     *
+     * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
+     */
+    private String formCustomViewPath;
+
+    /**
+     * SIMPLE 设计器模型数据 json 格式
+     *
+     * 目的:当使用仿钉钉设计器时。流程模型发布的时候,需要保存流程模型设计器的快照数据。
+     */
+    private String simpleModel;
+    /**
+     * 是否可见
+     *
+     * 目的:如果 false 不可见,则不展示在“发起流程”的列表里
+     */
+    private Boolean visible;
+    /**
+     * 排序值
+     */
+    private Long sort;
+
+    /**
+     * 可发起用户编号数组
+     *
+     * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
+     *
+     * 如果为空,则表示“全部可以发起”!
+     *
+     * 它和 {@link #visible} 的区别在于:
+     * 1. {@link #visible} 只是决定是否可见。即使不可见,还是可以发起
+     * 2. startUserIds 决定某个用户是否可以发起。如果该用户不可发起,则他也是不可见的
+     */
+    @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
+    private List<Long> startUserIds;
+
+    /**
+     * 可发起部门编号数组
+     *
+     * 关联 {@link AdminUserRespDTO#getDeptId()} 字段的数组
+     */
+    @TableField(typeHandler = LongListTypeHandler.class)
+    private List<Long> startDeptIds;
+
+    /**
+     * 可管理用户编号数组
+     *
+     * 关联 {@link AdminUserRespDTO#getId()} 字段的数组
+     */
+    @TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
+    private List<Long> managerUserIds;
+
+    /**
+     * 是否允许撤销审批中的申请
+     */
+    private Boolean allowCancelRunningProcess;
+
+    /**
+     * 流程 ID 规则
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.ProcessIdRule processIdRule;
+
+    /**
+     * 自动去重类型
+     *
+     * 枚举 {@link BpmAutoApproveTypeEnum}
+     */
+    private Integer autoApprovalType;
+
+    /**
+     * 标题设置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.TitleSetting titleSetting;
+    /**
+     * 摘要设置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.SummarySetting summarySetting;
+
+    /**
+     * 流程前置通知设置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.HttpRequestSetting processBeforeTriggerSetting;
+    /**
+     * 流程后置通知设置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.HttpRequestSetting processAfterTriggerSetting;
+
+    /**
+     * 任务前置通知设置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.HttpRequestSetting taskBeforeTriggerSetting;
+
+    /**
+     * 任务后置通知设置
+     */
+    @TableField(typeHandler = JacksonTypeHandler.class)
+    private BpmModelMetaInfoVO.HttpRequestSetting taskAfterTriggerSetting;
+
+}

+ 44 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmProcessExpressionDO.java

@@ -0,0 +1,44 @@
+package cn.iocoder.byzs.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * BPM 流程表达式 DO
+ *
+ * @author 博雅智算源码
+ */
+@TableName("bpm_process_expression")
+@KeySequence("bpm_process_expression_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmProcessExpressionDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 表达式名字
+     */
+    private String name;
+    /**
+     * 表达式状态
+     *
+     * 枚举 {@link TODO common_status 对应的类}
+     */
+    private Integer status;
+    /**
+     * 表达式
+     */
+    private String expression;
+
+}

+ 74 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmProcessListenerDO.java

@@ -0,0 +1,74 @@
+package cn.iocoder.byzs.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.byzs.module.bpm.enums.definition.BpmProcessListenerTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * BPM 流程监听器 DO
+ *
+ * 目的:本质上它是流程监听器的模版,用于 BPMN 在设计时,直接选择这些模版
+ *
+ * @author 博雅智算源码
+ */
+@TableName(value = "bpm_process_listener")
+@KeySequence("bpm_process_listener_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmProcessListenerDO extends BaseDO {
+
+    /**
+     * 主键 ID,自增
+     */
+    @TableId
+    private Long id;
+    /**
+     * 监听器名字
+     */
+    private String name;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 监听类型
+     *
+     * 枚举 {@link BpmProcessListenerTypeEnum}
+     *
+     * 1. execution:ExecutionListener <a href="https://tkjohn.github.io/flowable-userguide/#executionListeners">执行监听器</a>
+     * 2. task:TaskListener <a href="https://tkjohn.github.io/flowable-userguide/#taskListeners">任务监听器</a>
+     */
+    private String type;
+    /**
+     * 监听事件
+     *
+     * execution 时:start、end
+     * task 时:create 创建、assignment 指派、complete 完成、delete 删除、update 更新、timeout 超时
+     */
+    private String event;
+
+    /**
+     * 值类型
+     *
+     * 1. class:Java 类,ExecutionListener 需要 {@link org.flowable.engine.delegate.JavaDelegate},TaskListener 需要 {@link org.flowable.engine.delegate.TaskListener}
+     * 2. delegateExpression:委托表达式,在 class 的基础上,需要注册到 Spring 容器里,后续表达式通过 Spring Bean 名称即可
+     * 3. expression:表达式,一个普通类的普通方法,将这个普通类注册到 Spring 容器中,然后表达式中还可以执行这个类中的方法
+     */
+    private String valueType;
+    /**
+     * 值
+     */
+    private String value;
+
+}

+ 55 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/definition/BpmUserGroupDO.java

@@ -0,0 +1,55 @@
+package cn.iocoder.byzs.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.byzs.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+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.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Set;
+
+/**
+ * BPM 用户组
+ *
+ * @author 博雅智算源码
+ */
+@TableName(value = "bpm_user_group", autoResultMap = true)
+@KeySequence("bpm_user_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@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 = JacksonTypeHandler.class)
+    private Set<Long> userIds;
+
+}

+ 77 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/oa/BpmOALeaveDO.java

@@ -0,0 +1,77 @@
+package cn.iocoder.byzs.module.bpm.dal.dataobject.oa;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.byzs.module.bpm.enums.task.BpmTaskStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+/**
+ * OA 请假申请 DO
+ *
+ * {@link #day} 请假天数,目前先简单做。一般是分成请假上午和下午,可以是 1 整天,可以是 0.5 半天
+ *
+ * @author jason
+ * @author 博雅智算源码
+ */
+@TableName("bpm_oa_leave")
+@KeySequence("bpm_oa_leave_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmOALeaveDO extends BaseDO {
+
+    /**
+     * 请假表单主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 申请人的用户编号
+     *
+     * 关联 AdminUserDO 的 id 属性
+     */
+    private Long userId;
+    /**
+     * 请假类型
+     */
+    private String type;
+    /**
+     * 原因
+     */
+    private String reason;
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+    /**
+     * 结束时间
+     */
+    private LocalDateTime endTime;
+    /**
+     * 请假天数
+     */
+    private Long day;
+    /**
+     * 审批结果
+     *
+     * 枚举 {@link BpmTaskStatusEnum}
+     * 考虑到简单,所以直接复用了 BpmProcessInstanceStatusEnum 枚举,也可以自己定义一个枚举哈
+     */
+    private Integer status;
+
+    /**
+     * 对应的流程编号
+     *
+     * 关联 ProcessInstance 的 id 属性
+     */
+    private String processInstanceId;
+
+}

+ 97 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/dataobject/task/BpmProcessInstanceCopyDO.java

@@ -0,0 +1,97 @@
+package cn.iocoder.byzs.module.bpm.dal.dataobject.task;
+
+import cn.iocoder.byzs.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.flowable.bpmn.model.FlowNode;
+import org.flowable.task.api.history.HistoricTaskInstance;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 流程抄送 DO
+ *
+ * @author kyle
+ * @since 2024-01-22
+ */
+@TableName(value = "bpm_process_instance_copy", autoResultMap = true)
+@KeySequence("bpm_process_instance_copy_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmProcessInstanceCopyDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 发起人 Id
+     *
+     * 冗余 ProcessInstance 的 startUserId 字段
+     */
+    private Long startUserId;
+    /**
+     * 流程名
+     *
+     * 冗余 ProcessInstance 的 name 字段
+     */
+    private String processInstanceName;
+    /**
+     * 流程实例的编号
+     *
+     * 关联 ProcessInstance 的 id 属性
+     */
+    private String processInstanceId;
+    /**
+     * 流程实例的流程定义编号
+     *
+     * 关联 ProcessInstance 的 processDefinitionId 属性
+     */
+    private String processDefinitionId;
+    /**
+     * 流程分类
+     *
+     * 冗余 ProcessInstance 的 category 字段
+     */
+    private String category;
+    /**
+     * 流程活动的编号
+     * <p/>
+     *
+     * 冗余 {@link FlowNode#getId()},对应 BPMN XML 节点编号
+     * 原因:用于查询抄送节点的表单字段权限。因为仿钉钉/飞书的抄送节点 (ServiceTask),没有 taskId,只有 activityId
+     */
+    private String activityId;
+    /**
+     * 流程活动的名字
+     *
+     * 冗余 {@link FlowNode#getName()}
+     */
+    private String activityName;
+    /**
+     * 流程活动的编号
+     *
+     * 关联 {@link HistoricTaskInstance#getId()}
+     */
+    private String taskId;
+
+    /**
+     * 用户编号(被抄送的用户编号)
+     *
+     * 关联 system_users 的 id 属性
+     */
+    private Long userId;
+
+    /**
+     * 抄送意见
+     */
+    private String reason;
+
+}

+ 46 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/category/BpmCategoryMapper.java

@@ -0,0 +1,46 @@
+package cn.iocoder.byzs.module.bpm.dal.mysql.category;
+
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmCategoryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * BPM 流程分类 Mapper
+ *
+ * @author 博雅智算源码
+ */
+@Mapper
+public interface BpmCategoryMapper extends BaseMapperX<BpmCategoryDO> {
+
+    default PageResult<BpmCategoryDO> selectPage(BpmCategoryPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BpmCategoryDO>()
+                .likeIfPresent(BpmCategoryDO::getName, reqVO.getName())
+                .likeIfPresent(BpmCategoryDO::getCode, reqVO.getCode())
+                .eqIfPresent(BpmCategoryDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(BpmCategoryDO::getCreateTime, reqVO.getCreateTime())
+                .orderByAsc(BpmCategoryDO::getSort));
+    }
+
+    default BpmCategoryDO selectByName(String name) {
+        return selectOne(BpmCategoryDO::getName, name);
+    }
+
+    default BpmCategoryDO selectByCode(String code) {
+        return selectOne(BpmCategoryDO::getCode, code);
+    }
+
+    default List<BpmCategoryDO> selectListByCode(Collection<String> codes) {
+        return selectList(BpmCategoryDO::getCode, codes);
+    }
+
+    default List<BpmCategoryDO> selectListByStatus(Integer status) {
+        return selectList(BpmCategoryDO::getStatus, status);
+    }
+
+}

+ 25 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmFormMapper.java

@@ -0,0 +1,25 @@
+package cn.iocoder.byzs.module.bpm.dal.mysql.definition;
+
+
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmFormDO;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.QueryWrapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 动态表单 Mapper
+ *
+ * @author 风里雾里
+ */
+@Mapper
+public interface BpmFormMapper extends BaseMapperX<BpmFormDO> {
+
+    default PageResult<BpmFormDO> selectPage(BpmFormPageReqVO reqVO) {
+        return selectPage(reqVO, new QueryWrapperX<BpmFormDO>()
+                .likeIfPresent("name", reqVO.getName())
+                .orderByDesc("id"));
+    }
+
+}

+ 27 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmProcessDefinitionInfoMapper.java

@@ -0,0 +1,27 @@
+package cn.iocoder.byzs.module.bpm.dal.mysql.definition;
+
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+@Mapper
+public interface BpmProcessDefinitionInfoMapper extends BaseMapperX<BpmProcessDefinitionInfoDO> {
+
+    default List<BpmProcessDefinitionInfoDO> selectListByProcessDefinitionIds(Collection<String> processDefinitionIds) {
+        return selectList(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionIds);
+    }
+
+    default BpmProcessDefinitionInfoDO selectByProcessDefinitionId(String processDefinitionId) {
+        return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId);
+    }
+
+    default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) {
+        update(updateObj,
+                new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId));
+    }
+
+}

+ 26 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmProcessExpressionMapper.java

@@ -0,0 +1,26 @@
+package cn.iocoder.byzs.module.bpm.dal.mysql.definition;
+
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.expression.BpmProcessExpressionPageReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessExpressionDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * BPM 流程表达式 Mapper
+ *
+ * @author 博雅智算源码
+ */
+@Mapper
+public interface BpmProcessExpressionMapper extends BaseMapperX<BpmProcessExpressionDO> {
+
+    default PageResult<BpmProcessExpressionDO> selectPage(BpmProcessExpressionPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BpmProcessExpressionDO>()
+                .likeIfPresent(BpmProcessExpressionDO::getName, reqVO.getName())
+                .eqIfPresent(BpmProcessExpressionDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(BpmProcessExpressionDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(BpmProcessExpressionDO::getId));
+    }
+
+}

+ 27 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmProcessListenerMapper.java

@@ -0,0 +1,27 @@
+package cn.iocoder.byzs.module.bpm.dal.mysql.definition;
+
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.listener.BpmProcessListenerPageReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmProcessListenerDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * BPM 流程监听器 Mapper
+ *
+ * @author 博雅智算源码
+ */
+@Mapper
+public interface BpmProcessListenerMapper extends BaseMapperX<BpmProcessListenerDO> {
+
+    default PageResult<BpmProcessListenerDO> selectPage(BpmProcessListenerPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BpmProcessListenerDO>()
+                .likeIfPresent(BpmProcessListenerDO::getName, reqVO.getName())
+                .eqIfPresent(BpmProcessListenerDO::getType, reqVO.getType())
+                .eqIfPresent(BpmProcessListenerDO::getEvent, reqVO.getEvent())
+                .eqIfPresent(BpmProcessListenerDO::getStatus, reqVO.getStatus())
+                .orderByDesc(BpmProcessListenerDO::getId));
+    }
+
+}

+ 32 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/definition/BpmUserGroupMapper.java

@@ -0,0 +1,32 @@
+package cn.iocoder.byzs.module.bpm.dal.mysql.definition;
+
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 用户组 Mapper
+ *
+ * @author 博雅智算源码
+ */
+@Mapper
+public interface BpmUserGroupMapper extends BaseMapperX<BpmUserGroupDO> {
+
+    default PageResult<BpmUserGroupDO> selectPage(BpmUserGroupPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BpmUserGroupDO>()
+                .likeIfPresent(BpmUserGroupDO::getName, reqVO.getName())
+                .eqIfPresent(BpmUserGroupDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(BpmUserGroupDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(BpmUserGroupDO::getId));
+    }
+
+    default List<BpmUserGroupDO> selectListByStatus(Integer status) {
+        return selectList(BpmUserGroupDO::getStatus, status);
+    }
+
+}

+ 29 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/oa/BpmOALeaveMapper.java

@@ -0,0 +1,29 @@
+package cn.iocoder.byzs.module.bpm.dal.mysql.oa;
+
+import cn.iocoder.byzs.module.bpm.controller.admin.oa.vo.BpmOALeavePageReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 请假申请 Mapper
+ *
+ * @author jason
+ * @author 博雅智算源码
+ */
+@Mapper
+public interface BpmOALeaveMapper extends BaseMapperX<BpmOALeaveDO> {
+
+    default PageResult<BpmOALeaveDO> selectPage(Long userId, BpmOALeavePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BpmOALeaveDO>()
+                .eqIfPresent(BpmOALeaveDO::getUserId, userId)
+                .eqIfPresent(BpmOALeaveDO::getStatus, reqVO.getStatus())
+                .eqIfPresent(BpmOALeaveDO::getType, reqVO.getType())
+                .likeIfPresent(BpmOALeaveDO::getReason, reqVO.getReason())
+                .betweenIfPresent(BpmOALeaveDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(BpmOALeaveDO::getId));
+    }
+
+}

+ 25 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/mysql/task/BpmProcessInstanceCopyMapper.java

@@ -0,0 +1,25 @@
+package cn.iocoder.byzs.module.bpm.dal.mysql.task;
+
+import cn.iocoder.byzs.framework.common.pojo.PageResult;
+import cn.iocoder.byzs.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.byzs.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.byzs.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
+import cn.iocoder.byzs.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface BpmProcessInstanceCopyMapper extends BaseMapperX<BpmProcessInstanceCopyDO> {
+
+    default PageResult<BpmProcessInstanceCopyDO> selectPage(Long loginUserId, BpmProcessInstanceCopyPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BpmProcessInstanceCopyDO>()
+                .eqIfPresent(BpmProcessInstanceCopyDO::getUserId, loginUserId)
+                .likeIfPresent(BpmProcessInstanceCopyDO::getProcessInstanceName, reqVO.getProcessInstanceName())
+                .betweenIfPresent(BpmProcessInstanceCopyDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(BpmProcessInstanceCopyDO::getId));
+    }
+
+    default void deleteByProcessInstanceId(String processInstanceId) {
+        delete(BpmProcessInstanceCopyDO::getProcessInstanceId, processInstanceId);
+    }
+
+}

+ 61 - 0
byzs-module-bpm/src/main/java/cn/iocoder/byzs/module/bpm/dal/redis/BpmProcessIdRedisDAO.java

@@ -0,0 +1,61 @@
+package cn.iocoder.byzs.module.bpm.dal.redis;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.byzs.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+import static cn.hutool.core.date.DatePattern.*;
+
+/**
+ * BPM 流程 Id 编码的 Redis DAO
+ *
+ * @author Lesan
+ */
+@Repository
+public class BpmProcessIdRedisDAO {
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 生成序号,使用定义的 processIdRule 规则生成
+     *
+     * @param processIdRule 规则
+     * @return 序号
+     */
+    public String generate(BpmModelMetaInfoVO.ProcessIdRule processIdRule) {
+        // 生成日期前缀
+        String infix = "";
+        switch (processIdRule.getInfix()) {
+            case "DAY":
+                infix = DateUtil.format(LocalDateTime.now(), PURE_DATE_PATTERN);
+                break;
+            case "HOUR":
+                infix = DateUtil.format(LocalDateTime.now(), PURE_DATE_PATTERN + "HH");
+                break;
+            case "MINUTE":
+                infix = DateUtil.format(LocalDateTime.now(), PURE_DATE_PATTERN + "HHmm");
+                break;
+            case "SECOND":
+                infix = DateUtil.format(LocalDateTime.now(), PURE_DATETIME_PATTERN);
+                break;
+        }
+
+        // 生成序号
+        String noPrefix = processIdRule.getPrefix() + infix + processIdRule.getPostfix();
+        String key = RedisKeyConstants.BPM_PROCESS_ID + noPrefix;
+        Long no = stringRedisTemplate.opsForValue().increment(key);
+        if (StrUtil.isNotEmpty(infix)) {
+            // 特殊:没有前缀,则不能过期,不能每次都是从 0 开始
+            stringRedisTemplate.expire(key, Duration.ofDays(1L));
+        }
+        return noPrefix + String.format("%0" + processIdRule.getLength() + "d", no);
+    }
+
+}

Some files were not shown because too many files changed in this diff