formValues;
+ private Object formValues;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/dto/process/CommonDeptCodeProcessParam.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/dto/process/CommonDeptCodeProcessParam.java
new file mode 100644
index 0000000..8044194
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/dto/process/CommonDeptCodeProcessParam.java
@@ -0,0 +1,22 @@
+package org.springblade.process.pojo.dto.process;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 公共部门编码流程参数
+ * @author bfhuange
+ * @date 2024/10/9
+ */
+@Data
+public class CommonDeptCodeProcessParam implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+ /**
+ * 部门编码
+ */
+ private String deptCode;
+
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/BusinessProcess.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/BusinessProcess.java
new file mode 100644
index 0000000..bac2329
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/entity/BusinessProcess.java
@@ -0,0 +1,164 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.process.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 业务流程关联表 实体类
+ *
+ * @author BladeX
+ * @since 2024-09-19
+ */
+@Data
+@TableName("blade_business_process")
+@Schema(description = "BusinessProcess对象")
+public class BusinessProcess implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 业务id
+ */
+ @Schema(description = "业务id")
+ private Long bizId;
+ /**
+ * 流程实例id
+ */
+ @Schema(description = "流程实例id")
+ private String processInstanceId;
+ /**
+ * 流程类型
+ */
+ @Schema(description = "流程类型")
+ private String processType;
+ /**
+ * 文档编号
+ */
+ @Schema(description = "文档编号")
+ private String docCode;
+ /**
+ * 标题
+ */
+ @Schema(description = "标题")
+ private String subject;
+ /**
+ * 发起人id
+ */
+ @Schema(description = "发起人id")
+ private Long promoterId;
+ /**
+ * 发起人名称
+ */
+ @Schema(description = "发起人名称")
+ private String promoterName;
+ /**
+ * 发起人登录名
+ */
+ @Schema(description = "发起人登录名")
+ private String promoterLoginName;
+ /**
+ * 提交时间
+ */
+ @Schema(description = "提交时间")
+ private Date submitTime;
+ /**
+ * 完成时间
+ */
+ @Schema(description = "完成时间")
+ private Date completeTime;
+ /**
+ * 当前节点id,多个用逗号拼接
+ */
+ @Schema(description = "当前节点id,多个用逗号拼接")
+ private String currentNodeIds;
+ /**
+ * 当前节点名称,多个用逗号拼接
+ */
+ @Schema(description = "当前节点名称,多个用逗号拼接")
+ private String currentNodeNames;
+ /**
+ * 当前处理人,多个用逗号拼接
+ */
+ @Schema(description = "当前处理人,多个用逗号拼接")
+ private String currentHandlers;
+ /**
+ * 接收时间
+ */
+ @Schema(description = "接收时间")
+ private Date receiveTime;
+ /**
+ * 是否已完成(0:未完成, 1:已完成)
+ */
+ @Schema(description = "是否已完成")
+ private Integer isCompleted;
+ /**
+ * 审批状态
+ */
+ @Schema(description = "审批状态")
+ private String approveStatus;
+ /**
+ * 租户ID
+ */
+ @Schema(description = "租户ID")
+ private String tenantId;
+ /**
+ * 创建时间
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATE)
+ @Schema(description = "创建时间", hidden = true)
+ @TableField(fill = FieldFill.INSERT)
+ private Date createTime;
+ /**
+ * 更新时间
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATE)
+ @Schema(description = "更新时间", hidden = true)
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Date updateTime;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/enums/ApproveStatusEnum.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/enums/ApproveStatusEnum.java
new file mode 100644
index 0000000..9b9aafe
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/enums/ApproveStatusEnum.java
@@ -0,0 +1,165 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.process.pojo.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 审批状态
+ *
+ * @author LiuXinjie
+ * @apiNote 合同审批状态
+ */
+@Getter
+@AllArgsConstructor
+public enum ApproveStatusEnum {
+
+ /**
+ * 默认编号
+ */
+ DRAFT("draft", "草稿"), //可提交
+ APPROVING("approval", "审批中"),
+ APPROVED("pass", "审批通过"),
+ REJECTED("reject", "审批驳回"), //通用的流程 驳回可编辑
+ REVOCATION("revocation", "已撤回"), //可重新提交
+ ABANDON("abandon", "废弃"),
+ ;
+
+ final String value;
+ final String text;
+
+ public boolean match(String value){
+ return this.value.equals(value);
+ }
+
+ public static String getValueByText(String text) {
+ if (text == null) {
+ return null;
+ }
+ for (ApproveStatusEnum item : values()) {
+ if (Objects.equals(item.getText(), text)) {
+ return item.getValue();
+ }
+ }
+ return null;
+ }
+
+ public static String getTextByValue(String value) {
+ if (value == null) {
+ return null;
+ }
+ for (ApproveStatusEnum item : values()) {
+ if (Objects.equals(item.getValue(), value)) {
+ return item.getText();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 是否可以撤回
+ *
+ * @param value
+ * @return
+ */
+ public static boolean canRevoke(String value) {
+ return APPROVING.getValue().equals(value);
+ }
+
+ /**
+ * 能不能删除审批流
+ * @param value
+ * @return
+ */
+ public static boolean canDelAuditFlow(String value){
+ return REJECTED.getValue().equals(value) || REVOCATION.getValue().equals(value);
+ }
+
+ /**
+ * 驳回或撤回
+ * @return
+ */
+ public static boolean rejectedOrRevocation(String approveStatus) {
+ return canDelAuditFlow(approveStatus);
+ }
+
+ /**
+ * 能不能删除数据
+ * @param value
+ * @return
+ */
+ public static boolean canDeleteData(String value){
+ return ABANDON.getValue().equals(value) || DRAFT.getValue().equals(value) || canDelAuditFlow(value);
+ }
+
+ public static String getNameStr(String value) {
+ for (ApproveStatusEnum state : values()) {
+ if (state.value.equals(value)) {
+ return state.text;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * 是否可以编辑表单
+ *
+ * @param value
+ * @return
+ */
+ public static boolean canEdit(String value) {
+ return DRAFT.getValue().equals(value) || REJECTED.getValue().equals(value) || REVOCATION.getValue().equals(value);
+ }
+
+ /**
+ * 获取可驳回状态
+ *
+ * @return
+ */
+ public static List buildPreviousApproveStatusList(String currentStatus) {
+ if (ApproveStatusEnum.APPROVING.getValue().equals(currentStatus)) {
+ // 变更成审批中,前置条件为 草稿或者审批中
+ return List.of(ApproveStatusEnum.APPROVING.getValue());
+ } else if (ApproveStatusEnum.APPROVED.getValue().equals(currentStatus)) {
+ // 变更成审批通过,前置条件为 审批中
+ return List.of(ApproveStatusEnum.APPROVING.getValue());
+ } else if (ApproveStatusEnum.REJECTED.getValue().equals(currentStatus)) {
+ // 变更成审批驳回,前置条件为 审批中
+ return List.of(ApproveStatusEnum.APPROVING.getValue());
+ } else if (ApproveStatusEnum.REVOCATION.getValue().equals(currentStatus)) {
+ // 变更成撤回,前置条件为 审批中
+ return List.of(ApproveStatusEnum.APPROVING.getValue());
+ } else {
+ // 其他情况,前置条件为 草稿
+ return List.of(ApproveStatusEnum.DRAFT.getValue());
+ }
+ }
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/enums/TodoStatus.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/enums/TodoStatus.java
new file mode 100644
index 0000000..962399e
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/enums/TodoStatus.java
@@ -0,0 +1,36 @@
+package org.springblade.process.pojo.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 待办状态枚举
+ * @author bfhuange
+ * @date 2024/9/20
+ */
+@AllArgsConstructor
+@Getter
+public enum TodoStatus {
+ /**
+ * 待办
+ */
+ TODO("todo", "待办"),
+ /**
+ * 已办
+ */
+ DONE("done", "已办"),
+ /**
+ * 身份重复跳过
+ */
+ SKIP("skip", "身份重复跳过"),
+ ;
+
+ /**
+ * 待办状态编码
+ */
+ private final String code;
+ /**
+ * 待办状态名称
+ */
+ private final String name;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ApprovalVO.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ApprovalVO.java
new file mode 100644
index 0000000..5c05c88
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ApprovalVO.java
@@ -0,0 +1,177 @@
+package org.springblade.process.pojo.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author bfhuange
+ * @since 2025/4/2
+ */
+@Data
+@Schema(description = "mk审批中心记录")
+public class ApprovalVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * id
+ */
+ @Schema(description = "id")
+ private String id;
+ /**
+ * 流程所属应用
+ */
+ @Schema(description = "流程所属应用")
+ private String appName;
+ /**
+ * 申请人名称
+ */
+ @Schema(description = "申请人名称")
+ private String applicantName;
+ /**
+ * 申请人登录名
+ */
+ @Schema(description = "申请人登录名")
+ private String applicantLoginName;
+ /**
+ * 发起人名称
+ */
+ @Schema(description = "发起人名称")
+ private String creator;
+ /**
+ * 处理人名称
+ */
+ @Schema(description = "处理人名称")
+ private String handlerName;
+ /**
+ * 处理人登录名
+ */
+ @Schema(description = "处理人登录名")
+ private String handlerLoginName;
+ /**
+ * 当前处理人名称
+ */
+ @Schema(description = "当前处理人名称")
+ private String currentHandler;
+ /**
+ * 节点名称
+ */
+ @Schema(description = "节点名称")
+ private String nodeName;
+ /**
+ * 流程id
+ */
+ @Schema(description = "流程id")
+ private String processId;
+ /**
+ * 流程发起时间
+ */
+ @Schema(description = "流程发起时间")
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
+ private Date startTime;
+ /**
+ * 创建时间
+ */
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
+ private Date createTime;
+ /**
+ * 如果是待办:待办接收时间 如果是待阅:传阅接收时间
+ */
+ @Schema(description = "如果是待办:待办接收时间 如果是待阅:传阅接收时间")
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
+ private Date receiveTime;
+ /**
+ * 任务结束时间
+ */
+ @Schema(description = "任务结束时间")
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
+ private Date finishTime;
+ /**
+ * 阅读时间(待阅任务)
+ */
+ @Schema(description = "阅读时间(待阅任务)")
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
+ private Date readTime;
+ /**
+ * 最后处理时间(待审任务)
+ */
+ @Schema(description = "最后处理时间(待审任务)")
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
+ private Date lastHandleTime;
+ /**
+ * 流程结束时间
+ */
+ @Schema(description = "流程结束时间")
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME, timezone = "GMT+8")
+ private Date processFinishTime;
+ /**
+ * 流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束
+ */
+ @Schema(description = "流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束")
+ private String status;
+ /**
+ * 流程状态名称
+ */
+ @Schema(description = "流程状态名称")
+ private String statusStr;
+ /**
+ * 流程主题
+ */
+ @Schema(description = "流程主题")
+ private String subject;
+ /**
+ * 模板编码
+ */
+ @Schema(description = "模板编码")
+ private String templateCode;
+ /**
+ * 任务id
+ */
+ @Schema(description = "任务id")
+ private String taskId;
+ /**
+ * 任务状态 20 - 激活、30 - 结束、40 - 挂起、50 - 自动跳过
+ */
+ @Schema(description = "任务状态 20 - 激活、30 - 结束、40 - 挂起、50 - 自动跳过")
+ private String taskStatus;
+ /**
+ * 任务标题
+ */
+ @Schema(description = "任务标题")
+ private String taskSubject;
+ /**
+ * 催办标记
+ */
+ @Schema(description = "催办标记")
+ private String urgeTab;
+ /**
+ * 任务类型 1待办,2待阅
+ */
+ @Schema(description = "任务类型 1待办,2待阅")
+ private Integer taskType;
+ /**
+ * 待办优先级
+ */
+ @Schema(description = "待办优先级")
+ private Integer level;
+ /**
+ * 模板名称中文
+ */
+ @Schema(description = "模板名称中文")
+ private String templateNameCn;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/BusinessProcessListVO.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/BusinessProcessListVO.java
new file mode 100644
index 0000000..0d1d1b6
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/BusinessProcessListVO.java
@@ -0,0 +1,110 @@
+package org.springblade.process.pojo.vo;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author bfhuange
+ * @date 2024/9/23
+ */
+@Data
+@Schema(description = "BusinessProcessListVO")
+public class BusinessProcessListVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "主键")
+ private Long id;
+
+ /**
+ * 业务id
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "业务id")
+ private Long bizId;
+ /**
+ * 流程实例id
+ */
+ @Schema(description = "流程实例id")
+ private String processInstanceId;
+ /**
+ * 流程类型
+ */
+ @Schema(description = "审批类型")
+ private String processType;
+ /**
+ * 流程类型名称
+ */
+ @Schema(description = "审批类型名称")
+ private String processTypeStr;
+ /**
+ * 文档编号
+ */
+ @Schema(description = "审批单号")
+ private String docCode;
+ /**
+ * 发起人id
+ */
+ @Schema(description = "发起人id")
+ private Long promoterId;
+ /**
+ * 发起人名称
+ */
+ @Schema(description = "发起人名称")
+ private String promoterName;
+ /**
+ * 提交时间
+ */
+ @Schema(description = "提交时间")
+ private Date submitTime;
+ /**
+ * 接收时间
+ */
+ @Schema(description = "接收时间")
+ private Date receiveTime;
+ /**
+ * 完成时间
+ */
+ @Schema(description = "完成时间")
+ private Date completeTime;
+ /**
+ * 当前节点id,多个用逗号拼接
+ */
+ @Schema(description = "当前节点id,多个用逗号拼接")
+ private String currentNodeIds;
+ /**
+ * 当前节点名称,多个用逗号拼接
+ */
+ @Schema(description = "当前节点名称,多个用逗号拼接")
+ private String currentNodeNames;
+ /**
+ * 当前处理人,多个用逗号拼接
+ */
+ @Schema(description = "当前处理人,多个用逗号拼接")
+ private String currentHandlers;
+ /**
+ * 是否已完成(0:未完成, 1:已完成)
+ */
+ @Schema(description = "是否已完成(0:未完成, 1:已完成)")
+ private Integer isCompleted;
+ /**
+ * 审批状态
+ */
+ @Schema(description = "审批状态")
+ private String approveStatus;
+ /**
+ * 审批状态名称
+ */
+ @Schema(description = "审批状态名称")
+ private String approveStatusStr;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/BusinessProcessVO.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/BusinessProcessVO.java
new file mode 100644
index 0000000..67b22eb
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/BusinessProcessVO.java
@@ -0,0 +1,43 @@
+package org.springblade.process.pojo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author bfhuange
+ * @date 2024/9/20
+ */
+@Data
+@Schema(description = "BusinessProcessVO")
+public class BusinessProcessVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+ /**
+ * 流程实例id
+ */
+ private String processInstanceId;
+ /**
+ * 当前节点id,多个用逗号拼接
+ */
+ @Schema(description = "当前节点id,多个用逗号拼接")
+ private String currentNodeIds;
+ /**
+ * 当前节点名称,多个用逗号拼接
+ */
+ @Schema(description = "当前节点名称,多个用逗号拼接")
+ private String currentNodeNames;
+ /**
+ * 当前处理人,多个用逗号拼接
+ */
+ @Schema(description = "当前处理人,多个用逗号拼接")
+ private String currentHandlers;
+ /**
+ * 接收时间
+ */
+ @Schema(description = "接收时间")
+ private Date receiveTime;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessApprovedRecordVO.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessApprovedRecordVO.java
new file mode 100644
index 0000000..e6a96d6
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessApprovedRecordVO.java
@@ -0,0 +1,110 @@
+package org.springblade.process.pojo.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author bfhuange
+ * @since 2025/2/24
+ */
+@Data
+@Schema(description = "流程审批记录")
+public class ProcessApprovedRecordVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+ /**
+ * 记录主键
+ */
+ @Schema(description = "id")
+ private String id;
+ /**
+ * 处理人
+ */
+ @Schema(description = "处理人")
+ private String handler;
+ /**
+ * 操作
+ */
+ @Schema(description = "操作")
+ private String action;
+ /**
+ * 操作编码
+ */
+ @Schema(description = "操作编码")
+ private String actionCode;
+ /**
+ * 操作描述(系统操作)
+ */
+ @Schema(description = "操作描述(系统操作)")
+ private String actionDesc;
+ /**
+ * 操作名称
+ */
+ @Schema(description = "操作名称")
+ private String actionName;
+ /**
+ * 处理意见
+ */
+ @Schema(description = "处理意见")
+ private String message;
+ /**
+ * 创建时间
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @Schema(description = "创建时间")
+ private Date createTime;
+ /**
+ * 流程实例id
+ */
+ @Schema(description = "流程实例id")
+ private String processInstanceId;
+ /**
+ * 节点实例id
+ */
+ @Schema(description = "节点实例id")
+ private String nodeInstanceId;
+ /**
+ * 节点类型
+ */
+ @Schema(description = "节点类型")
+ private String nodeType;
+ /**
+ * 节点id
+ */
+ @Schema(description = "节点id")
+ private String nodeId;
+ /**
+ * 节点名称
+ */
+ @Schema(description = "节点名称")
+ private String nodeName;
+ /**
+ * 节点编号
+ */
+ @Schema(description = "节点编号")
+ private String nodeNumber;
+ /**
+ * 抄送人列表
+ */
+ @Schema(description = "抄送人列表")
+ private List senders;
+ /**
+ * 附件参数
+ */
+ @Schema(description = "附件参数")
+ private List attachmentParameter;
+ /**
+ * 流程附言
+ */
+ @Schema(description = "流程附言")
+ private List processComments;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessAttachmentVO.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessAttachmentVO.java
new file mode 100644
index 0000000..4e0548e
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessAttachmentVO.java
@@ -0,0 +1,43 @@
+package org.springblade.process.pojo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * @author bfhuange
+ * @since 2025/2/24
+ */
+@Data
+@Schema(description = "流程附件")
+public class ProcessAttachmentVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+ /**
+ * id
+ */
+ @Schema(description = "id")
+ private String id;
+ /**
+ * 附件名称
+ */
+ @Schema(description = "附件名称")
+ private String name;
+ /**
+ * 附件类型 electronicSign 电子签名,attachment 附件
+ */
+ @Schema(description = "附件类型 electronicSign 电子签名,attachment 附件")
+ private String type;
+ /**
+ * 附件ID
+ */
+ @Schema(description = "附件ID")
+ private String fileId;
+ /**
+ * base64
+ */
+ @Schema(description = "base64")
+ private String base64;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessCommentVO.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessCommentVO.java
new file mode 100644
index 0000000..b95f96d
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessCommentVO.java
@@ -0,0 +1,63 @@
+package org.springblade.process.pojo.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springblade.core.tool.utils.DateUtil;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 流程附言
+ * @author bfhuange
+ * @since 2025/2/24
+ */
+@Data
+@Schema(description = "流程附言")
+public class ProcessCommentVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+ /**
+ * id
+ */
+ @Schema(description = "id")
+ private String id;
+ /**
+ * 附言内容
+ */
+ @Schema(description = "附言内容")
+ private String content;
+ /**
+ * 创建时间
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @Schema(description = "创建时间")
+ private Date createTime;
+ /**
+ * 更新时间
+ */
+ @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
+ @Schema(description = "更新时间")
+ private Date updateTime;
+ /**
+ * 用户id
+ */
+ @Schema(description = "用户id")
+ private String userId;
+ /**
+ * 用户名称
+ */
+ @Schema(description = "用户名称")
+ private String userName;
+ /**
+ * 附言附件
+ */
+ @Schema(description = "附言附件")
+ private List attachments;
+}
diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessTodoVO.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessTodoVO.java
new file mode 100644
index 0000000..8b4b615
--- /dev/null
+++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/pojo/vo/ProcessTodoVO.java
@@ -0,0 +1,79 @@
+package org.springblade.process.pojo.vo;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author bfhuange
+ * @since 2025/3/10
+ */
+@Data
+@Schema(description = "流程待办")
+public class ProcessTodoVO implements Serializable {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键
+ */
+ @JsonSerialize(using = ToStringSerializer.class)
+ @Schema(description = "主键")
+ private Long id;
+
+ /**
+ * 流程实例id
+ */
+ @Schema(description = "流程实例id")
+ private String processInstanceId;
+ /**
+ * 节点id
+ */
+ @Schema(description = "节点id")
+ private String nodeId;
+ /**
+ * 节点编号
+ */
+ @Schema(description = "节点编号")
+ private String nodeNumber;
+ /**
+ * 节点名称
+ */
+ @Schema(description = "节点名称")
+ private String nodeName;
+ /**
+ * mk登录名
+ */
+ @Schema(description = "mk登录名")
+ private String loginName;
+ /**
+ * 用户姓名
+ */
+ @Schema(description = "用户姓名")
+ private String userName;
+ /**
+ * 状态(todo:待办,done:已办,skip:身份重复跳过)
+ */
+ @Schema(description = "状态(todo:待办,done:已办,skip:身份重复跳过)")
+ private String status;
+ /**
+ * 接收时间
+ */
+ @Schema(description = "接收时间")
+ private Date receiveTime;
+ /**
+ * 操作时间
+ */
+ @Schema(description = "操作时间")
+ private Date operationTime;
+ /**
+ * 操作名称
+ */
+ @Schema(description = "操作名称")
+ private String operationName;
+}
diff --git a/blade-service-api/pom.xml b/blade-service-api/pom.xml
index 0cca155..538df59 100644
--- a/blade-service-api/pom.xml
+++ b/blade-service-api/pom.xml
@@ -25,6 +25,7 @@
blade-record-api
blade-file-api
blade-open-api
+ blade-process-api
diff --git a/blade-service/blade-system/pom.xml b/blade-service/blade-system/pom.xml
index 4ab8af9..d3bde19 100644
--- a/blade-service/blade-system/pom.xml
+++ b/blade-service/blade-system/pom.xml
@@ -45,6 +45,10 @@
org.springblade
blade-user-api
+
+ org.springblade
+ blade-process-api
+
org.springblade
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java
new file mode 100644
index 0000000..c976569
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java
@@ -0,0 +1,83 @@
+package org.springblade.process.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.api.R;
+import org.springblade.process.pojo.dto.ApprovalDTO;
+import org.springblade.process.pojo.vo.ApprovalVO;
+import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
+import org.springblade.process.service.IBusinessProcessService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 业务流程关联表 控制器
+ *
+ * @author BladeX
+ * @since 2024-09-19
+ */
+@Valid
+@RestController
+@AllArgsConstructor
+@RequestMapping("businessProcess")
+@Tag(name = "业务流程关联表", description = "业务流程关联表接口")
+public class BusinessProcessController extends BladeController {
+
+ private final IBusinessProcessService businessProcessService;
+
+ /**
+ * 业务流程关联表 分页
+ */
+ @PostMapping("/mkList")
+ @ApiOperationSupport(order = 1)
+ @Operation(summary = "分页", description = "传入businessProcess")
+ public R> mkList(@Validated @RequestBody(required = false) ApprovalDTO param, Query query) {
+ if (param == null) {
+ param = new ApprovalDTO();
+ }
+ param.setLoginName(AuthUtil.getUserAccount());
+ IPage pages = businessProcessService.queryMkApprovalList(Condition.getPage(query), param);
+ return R.data(pages);
+ }
+
+ @GetMapping("/isEditView")
+ @ApiOperationSupport(order = 2)
+ @Operation(summary = "是否编辑页", description = "传入业务id")
+ public R isEditView(@Valid @NotBlank(message = "业务id不能为空") String bizId) {
+ return R.data(businessProcessService.isEditView(bizId));
+ }
+
+ @GetMapping("/getMKApprovalUrl")
+ @ApiOperationSupport(order = 3)
+ @Operation(summary = "获取mk审批页链接", description = "传入业务id或流程实例id")
+ public R getMKApprovalUrl(String bizId, String processInstanceId) {
+ return R.data(businessProcessService.getMKApprovalUrl(bizId, processInstanceId));
+ }
+
+ @GetMapping("/getApprovedRecords")
+ @ApiOperationSupport(order = 4)
+ @Operation(summary = "查询审批记录", description = "传入业务id或流程实例id")
+ public R> getApprovedRecords(String bizId, String processInstanceId) {
+ return R.data(businessProcessService.queryApprovedRecords(bizId, processInstanceId));
+ }
+
+ @GetMapping("/downloadFile")
+ @ApiOperationSupport(order = 5)
+ @Operation(summary = "下载附件", description = "传入附件id")
+ public void downloadFile(HttpServletResponse response, @Valid @NotBlank(message = "附件id不能为空") String fileId) {
+ businessProcessService.downloadFile(response, fileId);
+ }
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/convert/ApprovalConvert.java b/blade-service/blade-system/src/main/java/org/springblade/process/convert/ApprovalConvert.java
new file mode 100644
index 0000000..dcc4b18
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/convert/ApprovalConvert.java
@@ -0,0 +1,51 @@
+package org.springblade.process.convert;
+
+import org.mapstruct.*;
+import org.springblade.common.constant.DictTypeEnum;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.process.pojo.dto.ApprovalDTO;
+import org.springblade.process.pojo.vo.ApprovalVO;
+import org.springblade.system.cache.DictCache;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO;
+import org.springblade.thirdparty.mk.pojo.vo.MKApprovalVO;
+import org.springblade.thirdparty.mk.pojo.vo.MKProcessVO;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author bfhuange
+ * @since 2025/4/3
+ */
+@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface ApprovalConvert {
+
+ @Mapping(source = "dynamicProps.templateNameCn", target = "templateNameCn")
+ @Mapping(source = "status", target = "statusStr", qualifiedByName = "statusStr")
+ ApprovalVO mk2vo(MKApprovalVO vo);
+
+ List mk2vos(List vos);
+
+ @Mapping(source = "creator", target = "applicantName")
+ @Mapping(source = "currentNode", target = "nodeName")
+ @Mapping(source = "templateName", target = "templateNameCn")
+ @Mapping(source = "createTime", target = "startTime")
+ ApprovalVO mk2vo(MKProcessVO vo);
+
+ List mkProcess2vos(List vos);
+
+ @Mapping(source = "docType", target = "mydoc")
+ @Mapping(source = "applicantTimeStart", target = "createBeginTime", qualifiedByName = "date2long")
+ @Mapping(source = "applicantTimeEnd", target = "createEndTime", qualifiedByName = "date2long")
+ MKProcessDTO dto2mk(ApprovalDTO dto);
+
+ @Named("statusStr")
+ default String statusStr(String status) {
+ return StringUtil.isBlank(status) ? "" : DictCache.getValue(DictTypeEnum.MK_STATUS.getType(), status);
+ }
+
+ @Named("date2long")
+ default Long date2long(Date date) {
+ return date == null ? null : date.getTime();
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/convert/BusinessProcessConvert.java b/blade-service/blade-system/src/main/java/org/springblade/process/convert/BusinessProcessConvert.java
new file mode 100644
index 0000000..d8bd781
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/convert/BusinessProcessConvert.java
@@ -0,0 +1,84 @@
+package org.springblade.process.convert;
+
+import org.mapstruct.*;
+import org.springblade.common.constant.DictTypeEnum;
+import org.springblade.core.tool.utils.CollectionUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.process.pojo.dto.AdditionOperationParameterDTO;
+import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
+import org.springblade.process.pojo.dto.ProcessExecuteDTO;
+import org.springblade.process.pojo.entity.BusinessProcess;
+import org.springblade.process.pojo.vo.BusinessProcessListVO;
+import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
+import org.springblade.process.pojo.vo.ProcessAttachmentVO;
+import org.springblade.process.pojo.vo.ProcessCommentVO;
+import org.springblade.system.cache.DictCache;
+import org.springblade.thirdparty.mk.constant.MKConstant;
+import org.springblade.thirdparty.mk.pojo.dto.MKAdditionOperationParameterDTO;
+import org.springblade.thirdparty.mk.pojo.dto.MKProcessExecuteDTO;
+import org.springblade.thirdparty.mk.pojo.vo.MKAttachmentVO;
+import org.springblade.thirdparty.mk.pojo.vo.MKAuditNoteVO;
+import org.springblade.thirdparty.mk.pojo.vo.MKProcessCommentVO;
+import org.springblade.thirdparty.mk.pojo.vo.MKUserOrgVO;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * @author bfhuange
+ * @date 2024/9/19
+ */
+@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
+public interface BusinessProcessConvert {
+
+ BusinessProcess dto2entity(BusinessProcessSubmitDTO> dto);
+
+ MKProcessExecuteDTO dto2mk(ProcessExecuteDTO dto);
+
+ MKAdditionOperationParameterDTO dto2mk(AdditionOperationParameterDTO dto);
+
+ ProcessApprovedRecordVO mk2vo(MKAuditNoteVO vo);
+
+ ProcessAttachmentVO mk2vo(MKAttachmentVO vo);
+
+ List attachments2vos(List vos);
+
+ @Mapping(source = "userOrgInfo", target = "userName", qualifiedByName = "userName")
+ ProcessCommentVO mk2vo(MKProcessCommentVO vo);
+
+ List comments2vos(List vos);
+
+ default List auditNotes2vos(List vos, Function> senderFunction) {
+ if (CollectionUtil.isEmpty(vos)) {
+ return Collections.emptyList();
+ }
+ return vos.stream()
+ .map(auditNote -> {
+ ProcessApprovedRecordVO record = this.mk2vo(auditNote);
+ if (record != null && MKConstant.NODE_TYPE_SEND.equals(record.getNodeType())) {
+ // 抄送节点,查询抄送人员
+ record.setSenders(senderFunction.apply(record));
+ }
+ return record;
+ }).toList();
+ }
+
+ default void handleDict(BusinessProcessListVO vo) {
+ if (vo == null) {
+ return;
+ }
+ String processTypeStr = StringUtil.isBlank(vo.getProcessType()) ? "" : DictCache.getValue(DictTypeEnum.PROCESS_TYPE.getType(), vo.getProcessType());
+ vo.setProcessTypeStr(processTypeStr);
+ String approveStatusStr = StringUtil.isBlank(vo.getApproveStatus()) ? "" : DictCache.getValue(DictTypeEnum.APPROVE_STATUS.getType(), vo.getApproveStatus());
+ vo.setApproveStatusStr(approveStatusStr);
+ }
+
+ @Named("userName")
+ default String userName(MKUserOrgVO userOrgInfo) {
+ return Optional.ofNullable(userOrgInfo)
+ .map(MKUserOrgVO::getName)
+ .orElse(null);
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/convert/MKApprovalConvert.java b/blade-service/blade-system/src/main/java/org/springblade/process/convert/MKApprovalConvert.java
new file mode 100644
index 0000000..8d680a3
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/convert/MKApprovalConvert.java
@@ -0,0 +1,149 @@
+package org.springblade.process.convert;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.springblade.process.pojo.dto.ApprovalDTO;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalConditionDTO;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO.MKConditionDTOBuilder;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+/**
+ * @author bfhuange
+ * @since 2025/4/3
+ */
+@Getter
+public enum MKApprovalConvert {
+ /**
+ * 单据类型
+ */
+ DOC_TYPE(MKApprovalConditionDTO::setMydoc, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getDocType)),
+ /**
+ * 关键字
+ */
+ KEYWORD((condition, keyword) -> {
+ if (condition.getKeyword() == null) {
+ condition.setKeyword(new ArrayList<>());
+ }
+ condition.getKeyword().add(keyword);
+ }, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getKeyword)),
+ /**
+ * 模板名称
+ */
+ TEMPLATE_NAME(MKApprovalConditionDTO::setTemplateName, compose(MKConditionDTOBuilder::contains, ApprovalDTO::getTemplateName)),
+ /**
+ * 发起时间
+ */
+ START_TIME(MKApprovalConditionDTO::setStartTime,
+ compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getApplicantTimeStart)),
+ compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getApplicantTimeEnd))
+ ),
+ /**
+ * 接收时间
+ */
+ RECEIVE_TIME(MKApprovalConditionDTO::setReceiveTime,
+ compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getReceiveTimeStart)),
+ compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getReceiveTimeEnd))
+ ),
+ /**
+ * 流程状态
+ */
+ STATUS(MKApprovalConditionDTO::setStatus, compose(MKConditionDTOBuilder::eq, ApprovalDTO::getStatus)),
+ /**
+ * 结束时间
+ */
+ FINISH_TIME(MKApprovalConditionDTO::setFinishTime,
+ compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getFinishTimeStart)),
+ compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getFinishTimeEnd))
+ ),
+ /**
+ * 最后处理时间
+ */
+ LAST_HANDLE_TIME(MKApprovalConditionDTO::setLastHandleTime,
+ compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getLastHandleStart)),
+ compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getLastHandleEnd))
+ ),
+ /**
+ * 阅读时间
+ */
+ READ_TIME(MKApprovalConditionDTO::setReadTime,
+ compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getReadTimeStart)),
+ compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getReadTimeEnd))
+ ),
+ /**
+ * 创建时间
+ */
+ CREATE_TIME(MKApprovalConditionDTO::setCreateTime,
+ compose(MKConditionDTOBuilder::gte, getGetter(ApprovalDTO::getCreateTimeStart)),
+ compose(MKConditionDTOBuilder::lte, getGetter(ApprovalDTO::getCreateTimeEnd))
+ ),
+ ;
+ /**
+ * 最终设置条件方法
+ */
+ private final BiConsumer setter;
+ /**
+ * 组合参数
+ */
+ private final List composes;
+
+ MKApprovalConvert(BiConsumer setter, Compose... compose) {
+ this.setter = setter;
+ this.composes = List.of(compose);
+ }
+
+ /**
+ * 获取时间戳
+ * @param date
+ * @return
+ */
+ private static String getTimestamp(Date date) {
+ return Optional.ofNullable(date)
+ .map(e -> String.valueOf(e.getTime()))
+ .orElse(null);
+ }
+
+ /**
+ * date 转 string
+ * @param dateGetter
+ * @return
+ */
+ private static Function getGetter(Function dateGetter) {
+ return approvalDTO -> getTimestamp(dateGetter.apply(approvalDTO));
+ }
+
+ /**
+ * 工厂方法
+ * @param builderSetter
+ * @param getter
+ * @return
+ */
+ private static Compose compose(BiConsumer builderSetter, Function getter) {
+ return new Compose(builderSetter, getter);
+ }
+
+ /**
+ * 组合参数,条件和取值
+ */
+ @AllArgsConstructor
+ @NoArgsConstructor
+ @Data
+ public static class Compose {
+ /**
+ * 条件builder的setter
+ */
+ private BiConsumer builderSetter;
+ /**
+ * 从参数取值
+ */
+ private Function getter;
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java b/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java
new file mode 100644
index 0000000..e8ca2df
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java
@@ -0,0 +1,77 @@
+package org.springblade.process.feign;
+
+import io.swagger.v3.oas.annotations.Hidden;
+import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import org.springblade.core.tool.api.FR;
+import org.springblade.process.pojo.dto.BusinessProcessCurrentHandlerRefreshDTO;
+import org.springblade.process.pojo.dto.BusinessProcessDeleteDTO;
+import org.springblade.process.pojo.dto.BusinessProcessSubmitDTO;
+import org.springblade.process.pojo.dto.BusinessProcessUpdateDTO;
+import org.springblade.process.pojo.vo.BusinessProcessVO;
+import org.springblade.process.pojo.vo.ProcessApprovedRecordVO;
+import org.springblade.process.pojo.vo.ProcessTodoVO;
+import org.springblade.process.service.IBusinessProcessService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 业务流程关联表 Feign实现类
+ *
+ * @author BladeX
+ * @since 2024-09-19
+ */
+@Valid
+@Hidden
+@RestController
+@AllArgsConstructor
+public class BusinessProcessClient implements IBusinessProcessClient {
+
+ private final IBusinessProcessService businessProcessService;
+
+ @PostMapping(SUBMIT_BUSINESS_PROCESS)
+ @Override
+ public FR submitBusinessProcess(@Validated @RequestBody BusinessProcessSubmitDTO> param) {
+ return FR.data(businessProcessService.submitBusinessProcess(param));
+ }
+
+ @Override
+ public FR updateBusinessProcessStatus(@Validated @RequestBody BusinessProcessUpdateDTO param) {
+ return FR.data(businessProcessService.updateBusinessProcessStatus(param));
+ }
+
+ @Override
+ public FR updateBusinessProcessApprover(@Validated @RequestBody BusinessProcessUpdateDTO param) {
+ return FR.data(businessProcessService.updateBusinessProcessApprover(param));
+ }
+
+ @Override
+ public FR refreshBusinessProcessCurrentHandlers(@Validated @RequestBody BusinessProcessCurrentHandlerRefreshDTO param) {
+ return FR.data(businessProcessService.refreshBusinessProcessCurrentHandlers(param));
+ }
+
+ @Override
+ public FR> queryTodoList(String processInstanceId) {
+ return FR.data(businessProcessService.queryTodoList(processInstanceId));
+ }
+
+ @Override
+ public FR queryBusinessProcessSnapshot(String processInstanceId) {
+ return FR.data(businessProcessService.queryBusinessProcessSnapshot(processInstanceId));
+ }
+
+ @PostMapping(DELETE_BUSINESS_PROCESS)
+ @Override
+ public FR deleteBusinessProcess(@Validated @RequestBody BusinessProcessDeleteDTO param) {
+ return FR.data(businessProcessService.deleteBusinessProcess(param));
+ }
+
+ @Override
+ public FR> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) {
+ return FR.data(businessProcessService.queryApprovedRecordsNoAttachments(bizId, processInstanceId));
+ }
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.java b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.java
new file mode 100644
index 0000000..3ef7fdf
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.java
@@ -0,0 +1,14 @@
+package org.springblade.process.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.process.pojo.entity.BusinessProcess;
+
+/**
+ * 业务流程关联表 Mapper 接口
+ *
+ * @author BladeX
+ * @since 2024-09-19
+ */
+public interface BusinessProcessMapper extends BaseMapper {
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.xml
new file mode 100644
index 0000000..47b45b7
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/mapper/BusinessProcessMapper.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java
new file mode 100644
index 0000000..9dd744d
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java
@@ -0,0 +1,115 @@
+package org.springblade.process.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springblade.process.pojo.dto.*;
+import org.springblade.process.pojo.entity.BusinessProcess;
+import org.springblade.process.pojo.vo.*;
+
+import java.util.List;
+
+/**
+ * 业务流程关联表 服务类
+ *
+ * @author BladeX
+ * @since 2024-09-19
+ */
+public interface IBusinessProcessService extends IService {
+
+ /**
+ * 提交业务流程
+ *
+ * @param param
+ * @return
+ */
+ BusinessProcessVO submitBusinessProcess(BusinessProcessSubmitDTO> param);
+
+ /**
+ * 修改业务流程状态
+ * @param param
+ * @return
+ */
+ boolean updateBusinessProcessStatus(BusinessProcessUpdateDTO param);
+
+ /**
+ * 修改业务流程审批人
+ * @param param
+ * @return
+ */
+ BusinessProcessVO updateBusinessProcessApprover(BusinessProcessUpdateDTO param);
+
+ /**
+ * 只刷新当前节点和当前处理人
+ * @param param
+ * @return
+ */
+ BusinessProcessVO refreshBusinessProcessCurrentHandlers(BusinessProcessCurrentHandlerRefreshDTO param);
+
+ /**
+ * 查询业务流程当前快照
+ * @param processInstanceId
+ * @return
+ */
+ BusinessProcessVO queryBusinessProcessSnapshot(String processInstanceId);
+
+ /**
+ * 是否编辑页
+ * @param bizId
+ * @return
+ */
+ boolean isEditView(String bizId);
+
+ /**
+ * 获取mk审批页面链接,业务id和流程实例id任意一个即可
+ * @param bizId 业务id
+ * @param processInstanceId 流程实例id
+ * @return
+ */
+ String getMKApprovalUrl(String bizId, String processInstanceId);
+
+ /**
+ * 删除业务流程
+ * @param param
+ * @return
+ */
+ boolean deleteBusinessProcess(BusinessProcessDeleteDTO param);
+
+ /**
+ * 查询流程审批记录
+ * @param bizId
+ * @param processInstanceId
+ * @return
+ */
+ List queryApprovedRecords(String bizId, String processInstanceId);
+
+ /**
+ * 查询流程审批记录不处理附件
+ * @param bizId
+ * @param processInstanceId
+ * @return
+ */
+ List queryApprovedRecordsNoAttachments(String bizId, String processInstanceId);
+
+ /**
+ * 下载文件
+ * @param response
+ * @param fileId
+ */
+ void downloadFile(HttpServletResponse response, String fileId);
+
+ /**
+ * 查询业务流程当前处理人
+ * @param processInstanceId
+ * @return
+ */
+ List queryTodoList(String processInstanceId);
+
+ /**
+ * 查询mk审批记录
+ * @param page
+ * @param param
+ * @return
+ */
+ IPage queryMkApprovalList(IPage page, ApprovalDTO param);
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java
new file mode 100644
index 0000000..105651e
--- /dev/null
+++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java
@@ -0,0 +1,725 @@
+package org.springblade.process.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springblade.process.pojo.dto.*;
+import org.springblade.process.pojo.enums.ApproveStatusEnum;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.log.utils.AssertUtils;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.process.convert.ApprovalConvert;
+import org.springblade.process.convert.BusinessProcessConvert;
+import org.springblade.process.convert.MKApprovalConvert;
+import org.springblade.process.mapper.BusinessProcessMapper;
+import org.springblade.process.pojo.entity.BusinessProcess;
+import org.springblade.process.pojo.enums.TodoStatus;
+import org.springblade.process.pojo.vo.*;
+import org.springblade.process.service.IBusinessProcessService;
+import org.springblade.thirdparty.mk.config.MKProperties;
+import org.springblade.thirdparty.mk.constant.MKConstant;
+import org.springblade.thirdparty.mk.constant.MKDoc;
+import org.springblade.thirdparty.mk.exception.MKException;
+import org.springblade.thirdparty.mk.pojo.dto.MKAuditNoteDTO;
+import org.springblade.thirdparty.mk.pojo.dto.MKProcessCreateDTO;
+import org.springblade.thirdparty.mk.pojo.dto.MKProcessExecuteDTO;
+import org.springblade.thirdparty.mk.pojo.dto.MKSenderDTO;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalConditionDTO;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKApprovalDTO;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKConditionDTO;
+import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO;
+import org.springblade.thirdparty.mk.pojo.dto.sort.*;
+import org.springblade.thirdparty.mk.pojo.vo.*;
+import org.springblade.thirdparty.mk.service.IMKService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+
+/**
+ * 业务流程关联表 服务实现类
+ *
+ * @author BladeX
+ * @since 2024-09-19
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class BusinessProcessServiceImpl extends ServiceImpl implements IBusinessProcessService {
+
+ private final BusinessProcessConvert convert;
+ private final IMKService mkService;
+ private final MKProperties mkProperties;
+ private final ApprovalConvert approvalConvert;
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public BusinessProcessVO submitBusinessProcess(BusinessProcessSubmitDTO> param) {
+ if (param == null) {
+ log.warn("提交业务流程参数为空");
+ return null;
+ }
+ Long bizId = param.getBizId();
+ if (bizId == null) {
+ log.warn("提交业务流程业务id为空");
+ return null;
+ }
+ log.info("提交业务流程参数:{}", JSON.toJSONString(param));
+ BusinessProcess businessProcess = this.getOne(Wrappers.lambdaQuery()
+ .eq(BusinessProcess::getBizId, bizId)
+ );
+ if (businessProcess == null) {
+ businessProcess = convert.dto2entity(param);
+ }
+ // 设置发起人
+ if (businessProcess.getPromoterId() == null) {
+ businessProcess.setPromoterId(AuthUtil.getUserId());
+ }
+ if (businessProcess.getPromoterName() == null) {
+ businessProcess.setPromoterName(AuthUtil.getNickName());
+ }
+ if (businessProcess.getPromoterLoginName() == null) {
+ businessProcess.setPromoterLoginName(AuthUtil.getUserName());
+ param.setPromoterLoginName(AuthUtil.getUserName());
+ }
+ // 设置提交时间
+ if (businessProcess.getSubmitTime() == null) {
+ businessProcess.setSubmitTime(new Date());
+ }
+ // 1.提交流程
+ long start = System.currentTimeMillis();
+ log.info("提交流程开始");
+ String processInstanceId = submitMKProcess(param);
+ long end = System.currentTimeMillis();
+ log.info("提交流程结束 耗时:{}", end - start);
+ businessProcess.setProcessInstanceId(processInstanceId);
+ // 提交是审批中状态
+ businessProcess.setApproveStatus(ApproveStatusEnum.APPROVING.getValue());
+ // 2.保存业务流程
+ this.saveOrUpdate(businessProcess);
+
+ // 3.查询当前节点
+ BusinessProcessVO businessProcessVO = new BusinessProcessVO();
+ businessProcessVO.setProcessInstanceId(processInstanceId);
+ return businessProcessVO;
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public boolean updateBusinessProcessStatus(BusinessProcessUpdateDTO param) {
+ AssertUtils.notNull(param, "参数不能为空");
+ BusinessProcess businessProcess = this.getOne(Wrappers.lambdaQuery()
+ .eq(BusinessProcess::getProcessInstanceId, param.getProcessInstanceId())
+ );
+ AssertUtils.notNull(businessProcess, "流程实例不存在");
+ // if (StringUtils.isNotBlank(approveStatus) && !rejectAfterPass(approveStatus, operationNodeNumber)) {
+ if (StringUtils.isNotBlank(param.getApproveStatus()) && updateApproveStatus(param.getApproveStatus(), param.getRejectNodeId())) {
+ // 审批状态不为空且需要修改审批状态
+ BusinessProcess updateParam = new BusinessProcess();
+ updateParam.setId(businessProcess.getId());
+ updateParam.setApproveStatus(param.getApproveStatus());
+ return this.updateById(updateParam);
+ }
+ return true;
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public BusinessProcessVO updateBusinessProcessApprover(BusinessProcessUpdateDTO param) {
+ BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(param.getProcessInstanceId());
+ String promoterLoginName = resolvePromoterLoginName(businessProcess, param.getPromoterLoginName());
+ if (StringUtils.isBlank(promoterLoginName)) {
+ log.warn("修改业务流程审批人失败,发起人登录名为空,流程实例id:{}", param.getProcessInstanceId());
+ return null;
+ }
+ BusinessProcessVO businessProcessVO = this.refreshBusinessProcessCurrentHandlers(param);
+ if (businessProcessVO == null) {
+ return null;
+ }
+ // 再补历史已办逻辑,兼容旧代码
+ if (param.getOperationNodeId() != null && !MKConstant.DAFTER_NODE_ID.equals(param.getOperationNodeId())) {
+ MKAllHandlerVO nodeHandlers = mkService.getNodeHandlers(param.getProcessInstanceId(), promoterLoginName, param.getOperationNodeId());
+ this.handleNodeHandlers(param.getProcessInstanceId(), nodeHandlers, param);
+ }
+ return businessProcessVO;
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public BusinessProcessVO refreshBusinessProcessCurrentHandlers(BusinessProcessCurrentHandlerRefreshDTO param) {
+ if (param == null) {
+ log.warn("刷新业务流程当前处理人参数为空");
+ return null;
+ }
+ BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(param.getProcessInstanceId());
+ String promoterLoginName = resolvePromoterLoginName(businessProcess, param.getPromoterLoginName());
+ if (StringUtils.isBlank(promoterLoginName)) {
+ log.warn("刷新业务流程当前处理人失败,发起人登录名为空,流程实例id:{}", param.getProcessInstanceId());
+ return null;
+ }
+ Long businessProcessId = Optional.ofNullable(businessProcess).map(BusinessProcess::getId).orElse(null);
+ // 1. 查询当前节点
+ BusinessProcessVO businessProcessVO = new BusinessProcessVO();
+ businessProcessVO.setProcessInstanceId(param.getProcessInstanceId());
+ List currentNodes = mkService.getCurrentNodes(param.getProcessInstanceId(), promoterLoginName);
+ // 处理当前节点信息
+ BusinessProcess updateBusinessProcess = this.handleCurrentNodes(businessProcessId, param.getProcessInstanceId(), param.isComplete(), currentNodes, businessProcessVO);
+ if (updateBusinessProcess != null) {
+ // 设置当前处理人、当前节点、接收时间
+ businessProcessVO.setCurrentHandlers(updateBusinessProcess.getCurrentHandlers());
+ businessProcessVO.setCurrentNodeIds(updateBusinessProcess.getCurrentNodeIds());
+ businessProcessVO.setCurrentNodeNames(updateBusinessProcess.getCurrentNodeNames());
+ businessProcessVO.setReceiveTime(updateBusinessProcess.getReceiveTime());
+ }
+ return businessProcessVO;
+ }
+
+ @Override
+ public BusinessProcessVO queryBusinessProcessSnapshot(String processInstanceId) {
+ AssertUtils.notBlank(processInstanceId, "流程实例id不能为空");
+ BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
+ AssertUtils.notNull(businessProcess, "业务流程不存在");
+ return this.buildBusinessProcessVO(businessProcess);
+ }
+
+ @Override
+ public boolean isEditView(String bizId) {
+ if (StringUtils.isBlank(bizId)) {
+ log.warn("查询是否编辑页,业务id为空");
+ return false;
+ }
+ BusinessProcess businessProcess = this.baseMapper.selectOne(Wrappers.lambdaQuery()
+ .eq(BusinessProcess::getBizId, bizId)
+ .last("limit 1")
+ );
+ if (businessProcess == null) {
+ log.warn("查询是否编辑页,业务流程不存在 业务id:{}", bizId);
+ return false;
+ }
+ String approveStatus = businessProcess.getApproveStatus();
+ String userAccount = AuthUtil.getUserAccount();
+ String promoterLoginName = businessProcess.getPromoterLoginName();
+ // (驳回或撤销或草稿)且当前登录人是流程提交人
+ boolean result = ApproveStatusEnum.canEdit(approveStatus) && userAccount.equals(promoterLoginName);
+ log.info("是否编辑页 审批状态:{} 当前登录人:{} 提交人:{} 结果:{}", approveStatus, userAccount, promoterLoginName, result);
+ return result;
+ }
+
+ @Override
+ public String getMKApprovalUrl(String bizId, String processInstanceId) {
+ boolean bizIdBlank = StringUtil.isBlank(bizId);
+ boolean processInstanceIdBlank = StringUtil.isBlank(processInstanceId);
+ AssertUtils.isFalse(bizIdBlank && processInstanceIdBlank, "参数不能为空");
+ if (processInstanceIdBlank) {
+ BusinessProcess businessProcess = this.getOne(Wrappers.lambdaQuery()
+ .eq(BusinessProcess::getBizId, bizId)
+ .last("limit 1")
+ );
+ AssertUtils.notNull(businessProcess, "业务流程不存在");
+ processInstanceId = businessProcess.getProcessInstanceId();
+ }
+ try {
+ String mkApprovalUrl = mkService.getMKApprovalUrl(processInstanceId, AuthUtil.getUserAccount());
+ AssertUtils.notBlank(mkApprovalUrl, "获取mk审批页面链接异常");
+ return mkApprovalUrl;
+ } catch (MKException e) {
+ throw new ServiceException("获取mk审批页面链接异常 " + e.getMessage());
+ }
+ }
+
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public boolean deleteBusinessProcess(BusinessProcessDeleteDTO param) {
+ log.info("删除业务流程 参数:{}", JSON.toJSONString(param));
+ Long bizId = param.getBizId();
+ String promoterLoginName = param.getPromoterLoginName();
+ if (bizId == null) {
+ return false;
+ }
+ BusinessProcess businessProcess = this.getOne(Wrappers.lambdaQuery()
+ .eq(BusinessProcess::getBizId, bizId)
+ );
+ if (businessProcess == null) {
+ log.warn("业务流程不存在 业务id:{}", bizId);
+ return false;
+ }
+ if (promoterLoginName == null) {
+ promoterLoginName = businessProcess.getPromoterLoginName();
+ }
+ // 1. 删除业务流程
+ this.removeById(businessProcess.getId());
+ // 2. 删除待办
+ if (StringUtils.isEmpty(businessProcess.getProcessInstanceId())) {
+ log.warn("流程id为空 id:{} 业务id:{}", businessProcess.getId(), bizId);
+ return true;
+ }
+ // 3. 删除流程
+ return mkService.processDelete(businessProcess.getProcessInstanceId(), promoterLoginName);
+ }
+
+ @Override
+ public List queryApprovedRecords(String bizId, String processInstanceId) {
+ List records = this.queryApprovedRecordsNoAttachments(bizId, processInstanceId);
+ if (CollectionUtil.isEmpty(records)) {
+ return records;
+ }
+ Map fileMap = new HashMap<>();
+ for (ProcessApprovedRecordVO record : records) {
+ List attachmentParameter = record.getAttachmentParameter();
+ // 处理电子签名base64并排序附件
+ attachmentParameter = handleAttachmentBase64(attachmentParameter, fileMap);
+ record.setAttachmentParameter(attachmentParameter);
+ if (CollectionUtil.isNotEmpty(record.getProcessComments())) {
+ for (ProcessCommentVO processComment : record.getProcessComments()) {
+ // 处理电子签名base64并排序附件
+ List attachments = handleAttachmentBase64(processComment.getAttachments(), fileMap);
+ processComment.setAttachments(attachments);
+ }
+ }
+ }
+ return records;
+ }
+
+ @Override
+ public List queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) {
+ boolean bizIdBlank = StringUtil.isBlank(bizId);
+ boolean processInstanceIdBlank = StringUtil.isBlank(processInstanceId);
+ AssertUtils.isFalse(bizIdBlank && processInstanceIdBlank, "参数不能为空");
+
+ BusinessProcess businessProcess = this.getOne(Wrappers.lambdaQuery()
+ .eq(!bizIdBlank, BusinessProcess::getBizId, bizId)
+ .eq(!processInstanceIdBlank, BusinessProcess::getProcessInstanceId, processInstanceId)
+ .last("limit 1")
+ );
+ AssertUtils.notNull(businessProcess, "业务流程不存在");
+ if (processInstanceIdBlank) {
+ processInstanceId = businessProcess.getProcessInstanceId();
+ }
+ // 查询审批记录
+ List mkAuditNotes = mkService.queryAuditNotes(new MKAuditNoteDTO(businessProcess.getPromoterLoginName(), processInstanceId));
+ // 转换参数
+ return convert.auditNotes2vos(mkAuditNotes, record -> {
+ List mkSenders = mkService.querySenderList(new MKSenderDTO(record.getProcessInstanceId(), record.getNodeInstanceId()));
+ return mkSenders.stream()
+ .map(MKSenderVO::getName)
+ .toList();
+ });
+ }
+
+ /**
+ * 处理附件base64并排序附件
+ * @param attachmentParameter
+ * @param fileMap
+ */
+ private List handleAttachmentBase64(List attachmentParameter, Map fileMap) {
+ if (CollectionUtil.isEmpty(attachmentParameter)) {
+ return attachmentParameter;
+ }
+ // 电子签名的附件
+ List signAttachments = attachmentParameter.stream()
+ .filter(attachment -> MKConstant.FILE_TYPE_SIGN.equals(attachment.getType()))
+ .peek(attachment -> {
+ // 电子签名,查询图片base64
+ if (fileMap.containsKey(attachment.getFileId())) {
+ attachment.setBase64(fileMap.get(attachment.getFileId()));
+ } else {
+ String fileBase64 = mkService.getFileBase64(attachment.getFileId());
+ fileMap.put(attachment.getFileId(), fileBase64);
+ attachment.setBase64(fileBase64);
+ }
+ }).toList();
+ if (CollectionUtil.isEmpty(signAttachments)) {
+ // 没有电子签名的附件,无需处理
+ return attachmentParameter;
+ }
+ List result = new ArrayList<>();
+ // 纯附件,非电子签名附件
+ List attachments = attachmentParameter.stream()
+ .filter(attachment -> !MKConstant.FILE_TYPE_SIGN.equals(attachment.getType()))
+ .toList();
+ if (CollectionUtil.isNotEmpty(attachments)) {
+ result.addAll(attachments);
+ }
+ // 把电子签名附件放到最后
+ result.addAll(signAttachments);
+ return result;
+ }
+
+ @Override
+ public void downloadFile(HttpServletResponse response, String fileId) {
+ mkService.downloadFile(response,fileId);
+ }
+
+ @Override
+ public List queryTodoList(String processInstanceId) {
+ AssertUtils.notNull(processInstanceId, "流程实例id不能为空");
+ BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
+ if (businessProcess == null) {
+ return null;
+ }
+ // 1. 查询当前节点处理人
+ List currentNodes = mkService.getCurrentNodes(processInstanceId, businessProcess.getPromoterLoginName());
+ if (CollectionUtil.isEmpty(currentNodes)) {
+ return Collections.emptyList();
+ }
+ return getProcessTodoList(currentNodes);
+ }
+
+ /**
+ * 通过流程实例id查询业务流程
+ * @param processInstanceId 流程实例id
+ * @return 业务流程
+ */
+ private BusinessProcess getBusinessProcessByProcessInstanceId(String processInstanceId) {
+ return this.getOne(Wrappers.lambdaQuery()
+ .eq(BusinessProcess::getProcessInstanceId, processInstanceId)
+ );
+ }
+
+ /**
+ * 解析最终使用的发起人登录名
+ * @param businessProcess 业务流程
+ * @param fallbackPromoterLoginName 调用方传入的发起人登录名
+ * @return 发起人登录名
+ */
+ private String resolvePromoterLoginName(BusinessProcess businessProcess, String fallbackPromoterLoginName) {
+ return Optional.ofNullable(businessProcess)
+ .map(BusinessProcess::getPromoterLoginName)
+ .filter(StringUtils::isNotBlank)
+ .orElse(fallbackPromoterLoginName);
+ }
+
+ /**
+ * 构造业务流程快照
+ * @param businessProcess 业务流程
+ * @return 快照
+ */
+ private BusinessProcessVO buildBusinessProcessVO(BusinessProcess businessProcess) {
+ BusinessProcessVO businessProcessVO = new BusinessProcessVO();
+ businessProcessVO.setProcessInstanceId(businessProcess.getProcessInstanceId());
+ businessProcessVO.setCurrentNodeIds(businessProcess.getCurrentNodeIds());
+ businessProcessVO.setCurrentNodeNames(businessProcess.getCurrentNodeNames());
+ businessProcessVO.setCurrentHandlers(businessProcess.getCurrentHandlers());
+ businessProcessVO.setReceiveTime(businessProcess.getReceiveTime());
+ return businessProcessVO;
+ }
+
+ /**
+ * 获取流程待办列表
+ * @param currentNodes
+ * @return
+ */
+ private List getProcessTodoList(List currentNodes) {
+ if (CollectionUtil.isEmpty(currentNodes)) {
+ return Collections.emptyList();
+ }
+ return currentNodes.stream()
+ .filter(node -> CollectionUtil.isNotEmpty(node.getNodeHandlers()))
+ .flatMap(node -> node.getNodeHandlers().stream()
+ // 过滤掉登录名为空的脏数据
+ .filter(handler -> handler.getFdHandlerOrgInfo() != null && StringUtil.isNotBlank(handler.getFdHandlerOrgInfo().getLoginName()))
+ .map(handler -> {
+ ProcessTodoVO addParam = new ProcessTodoVO();
+ addParam.setProcessInstanceId(node.getProcessInstanceId());
+ addParam.setNodeId(node.getNodeId());
+ addParam.setNodeNumber(node.getNodeNumber());
+ addParam.setNodeName(node.getNodeName());
+ addParam.setLoginName(handler.getFdHandlerOrgInfo().getLoginName());
+ addParam.setUserName(handler.getHandlerName());
+ addParam.setStatus(TodoStatus.TODO.getCode());
+ addParam.setReceiveTime(handler.getReceiveTime());
+ return addParam;
+ })
+ ).toList();
+ }
+
+ @Override
+ public IPage queryMkApprovalList(IPage page, ApprovalDTO param) {
+ if (MKDoc.RELATED.getCode().equals(param.getDocType())) {
+ // 我参与的,调用流程列表接口
+ MKProcessDTO processParam = approvalConvert.dto2mk(param);
+ processParam.setPage((int) page.getCurrent(), (int) page.getSize());
+ MKPageVO mkPage = mkService.queryProcessList(processParam);
+ page.setTotal(mkPage.getTotalSize());
+ page.setRecords(approvalConvert.mkProcess2vos(mkPage.getContent()));
+ return page;
+ }
+ // 非我参与的,调用审批中心接口
+ MKApprovalDTO approvalParam = getMkApprovalParam(param);
+ approvalParam.setPage((int) page.getCurrent(), (int) page.getSize());
+ MKPageVO mkPage = mkService.queryApprovalList(approvalParam);
+ page.setTotal(mkPage.getTotalSize());
+ page.setRecords(approvalConvert.mk2vos(mkPage.getContent()));
+ return page;
+ }
+
+ /**
+ * 获取mk查询参数
+ *
+ * @param param
+ * @return
+ */
+ private MKApprovalDTO getMkApprovalParam(ApprovalDTO param) {
+ String docType = param.getDocType();
+ // 我的待审
+ // mk页面接口参数 {"offset":0,"pageNo":1,"pageSize":10,"conditions":{"fdStartTime":{"$gte":1711900800000,"$lte":1746374399999},"fdReceiveTime":{"$gte":1712160000000,"$lte":1746115199999},"fdTemplateName":{"$contains":"测试"},"keyword":{"$eq":"提交"},"mydoc":{"$eq":"myApproving"}},"sorts":{"fdLevel":"asc","fdReceiveTime":"desc"}}
+ MKApprovalDTO approvalParam = new MKApprovalDTO();
+ approvalParam.setLoginName(param.getLoginName());
+ ISort sort = getSort(docType, approvalParam);
+ approvalParam.setSorts(sort);
+ // 获取查询条件的参数
+ MKApprovalConditionDTO condition = getCondition(param);
+ approvalParam.setConditions(condition);
+ return approvalParam;
+ }
+
+ /**
+ * 获取排序
+ * @param docType
+ * @param approvalParam
+ * @return
+ */
+ private ISort getSort(String docType, MKApprovalDTO approvalParam) {
+ if (MKDoc.APPROVING.getCode().equals(docType) || MKDoc.READING.getCode().equals(docType)) {
+ // 待办、待阅排序是相同的
+ return new MKApprovingSortDTO();
+ }
+ if (MKDoc.APPROVED.getCode().equals(docType)) {
+ // 已办
+ return new MKApprovedSortDTO();
+ }
+ if (MKDoc.READ.getCode().equals(docType)) {
+ // 已阅
+ return new MKReadSortDTO();
+ }
+ if (MKDoc.CREATE.getCode().equals(docType) || MKDoc.RELATED.getCode().equals(docType)) {
+ // 我发起的/我关联的
+ return new MKCreateSortDTO();
+ }
+ throw new ServiceException("不支持的单据类型");
+ }
+
+ /**
+ * 获取查询条件
+ * @param param
+ * @return
+ */
+ private MKApprovalConditionDTO getCondition(ApprovalDTO param) {
+ MKApprovalConditionDTO condition = null;
+ for (MKApprovalConvert convert : MKApprovalConvert.values()) {
+ MKConditionDTO.MKConditionDTOBuilder builder = null;
+ BiConsumer setter = convert.getSetter();
+ List composes = convert.getComposes();
+ // 是否多个 Compose
+ boolean multi = composes.size() > 1;
+ if (multi) {
+ // 不是多个setter
+ for (MKApprovalConvert.Compose compose : composes) {
+ // 遍历获取参数值
+ String value = compose.getGetter().apply(param);
+ if (StringUtils.isNotBlank(value)) {
+ // 参数值不为空,设置到builder
+ if (builder == null) {
+ builder = MKConditionDTO.builder();
+ }
+ compose.getBuilderSetter().accept(builder, value);
+ }
+ }
+ if (builder != null) {
+ // builder不为空,设置到最终的条件
+ if (condition == null) {
+ condition = new MKApprovalConditionDTO();
+ }
+ setter.accept(condition, builder.build());
+ convert.getSetter().accept(condition, builder.build());
+ }
+ } else {
+ // 只有1个compose
+ MKApprovalConvert.Compose compose = composes.get(0);
+ String value = compose.getGetter().apply(param);
+ if (StringUtils.isNotBlank(value)) {
+ // 替换中文逗号为英文逗号
+ value = value.replace(",", ",");
+ // 按英文逗号拆分值
+ String[] values = value.split(",");
+ for (String singleValue: values) {
+ if (StringUtils.isNotBlank(value)) {
+ // 参数值不为空,设置到builder
+ if (builder == null) {
+ builder = MKConditionDTO.builder();
+ }
+ compose.getBuilderSetter().accept(builder, singleValue);
+ if (builder != null) {
+ // builder不为空,设置到最终的条件
+ if (condition == null) {
+ condition = new MKApprovalConditionDTO();
+ }
+ // 索引不超过setters长度,设置条件
+ setter.accept(condition, builder.build());
+ }
+ }
+ }
+ }
+ }
+ }
+ return condition;
+ }
+
+ /**
+ * 处理已审批的人
+ *
+ * @param processInstanceId
+ * @param nodeHandlers
+ * @param param
+ */
+ private void handleNodeHandlers(String processInstanceId, MKAllHandlerVO nodeHandlers, BusinessProcessUpdateDTO param) {
+ if (nodeHandlers == null) {
+ log.warn("修改业务流程 查询操作节点历史处理信息为空 流程实例id:{} 操作节点id:{}", param.getProcessInstanceId(), param.getOperationNodeId());
+ return;
+ }
+ List approvedHandlers = nodeHandlers.getApprovedHandlers();
+ if (CollectionUtil.isEmpty(approvedHandlers)) {
+ // 已审批为空
+ log.warn("修改业务流程 查询操作节点已处理信息为空 流程实例id:{} 操作节点id:{}", param.getProcessInstanceId(), param.getOperationNodeId());
+ return;
+ }
+
+ }
+
+ /**
+ * 处理当前节点信息
+ *
+ * @param businessProcessId
+ * @param processInstanceId
+ * @param complete
+ * @param currentNodes
+ * @param businessProcessVO
+ */
+ private BusinessProcess handleCurrentNodes(Long businessProcessId, String processInstanceId, boolean complete, List currentNodes, BusinessProcessVO businessProcessVO) {
+ if (CollectionUtil.isEmpty(currentNodes)) {
+ log.info("修改业务流程 当前节点处理人为空");
+ if (businessProcessId != null) {
+ // 清空当前节点,当前处理人,接收时间
+ log.info("修改业务流程 流程结束清空当前节点,当前处理人,接收时间 业务流程id:{} 流程实例id:{}", businessProcessId, processInstanceId);
+ this.lambdaUpdate()
+ .eq(BusinessProcess::getId, businessProcessId)
+ .set(BusinessProcess::getCurrentNodeIds, null)
+ .set(BusinessProcess::getCurrentNodeNames, null)
+ .set(BusinessProcess::getCurrentHandlers, null)
+ .set(complete, BusinessProcess::getIsCompleted, true)
+ .set(complete, BusinessProcess::getCompleteTime, new Date())
+ .set(BusinessProcess::getUpdateTime, new Date())
+ .update();
+ } else {
+ log.warn("修改业务流程 流程结束 业务流程id为空");
+ }
+ return null;
+ }
+ // 处理待办
+ List processTodoList = getProcessTodoList(currentNodes);
+
+ // 更新业务流程
+ return updateBusinessProcess(businessProcessId, processTodoList);
+ }
+
+ /**
+ * 更新业务流程
+ *
+ * @param businessProcessId
+ * @param addToDos
+ */
+ private BusinessProcess updateBusinessProcess(Long businessProcessId, List addToDos) {
+ if (CollectionUtil.isEmpty(addToDos)) {
+ log.warn("新增待办为空");
+ return null;
+ }
+ String nodeIds = addToDos.stream()
+ .map(ProcessTodoVO::getNodeId)
+ .distinct()
+ .collect(Collectors.joining(","));
+ String nodeNames = addToDos.stream()
+ .map(ProcessTodoVO::getNodeName)
+ .distinct()
+ .collect(Collectors.joining(","));
+ String usernames = addToDos.stream()
+ .map(ProcessTodoVO::getUserName)
+ .distinct()
+ .collect(Collectors.joining(","));
+ Date receiveTime = addToDos.get(0).getReceiveTime();
+ // 更新当前节点id,当前节点名称,当前处理人,接收时间
+ BusinessProcess updateParam = new BusinessProcess();
+ updateParam.setId(businessProcessId);
+ updateParam.setCurrentNodeIds(nodeIds);
+ updateParam.setCurrentNodeNames(nodeNames);
+ updateParam.setCurrentHandlers(usernames);
+ updateParam.setReceiveTime(receiveTime);
+ if (businessProcessId != null) {
+ this.updateById(updateParam);
+ } else {
+ log.warn("新增待办,业务流程id为空");
+ }
+ return updateParam;
+ }
+
+ /**
+ * 是否修改审批状态,驳回状态只修改驳回节点是起草节点的
+ * @param approveStatus
+ * @param rejectNodeId
+ * @return
+ */
+ private boolean updateApproveStatus(String approveStatus, String rejectNodeId) {
+ if (!ApproveStatusEnum.REJECTED.getValue().equals(approveStatus)) {
+ // 不是驳回状态,直接修改
+ return true;
+ }
+ if (StringUtils.isBlank(rejectNodeId)) {
+ // 驳回节点id为空说明是老流程,没有配置参数,可以修改
+ return true;
+ }
+ // 是驳回状态,只修改驳回节点id是起草节点id的
+ return MKConstant.DAFTER_NODE_ID.equals(rejectNodeId);
+ }
+
+ /**
+ * 提交mk流程
+ * @param param
+ * @return
+ */
+ private String submitMKProcess(BusinessProcessSubmitDTO> param) {
+ if (param.getExecuteParam() == null || StringUtil.isBlank(param.getExecuteParam().getProcessId())) {
+ // 执行参数为空,是提交
+ MKProcessCreateDTO processParam = new MKProcessCreateDTO();
+ processParam.setFormInstanceId(String.valueOf(param.getBizId()));
+ processParam.setLoginName(param.getPromoterLoginName());
+ processParam.setSubmitIdentity(param.getPromoterLoginName());
+ processParam.setSubject(param.getSubject());
+ processParam.setTemplateCode(mkProperties.getTemplateCodePrefix() + param.getProcessType());
+ processParam.setFormValues(param.getProcessParam());
+ // 临时变量设置为业务表单对象,以便不用修改mk流程表单字段,直接使用流程模板临时变量
+ processParam.setTempVarData(ObjectUtil.cloneByStream(param.getProcessParam()));
+ return mkService.processSubmit(processParam);
+ }
+ // 执行参数不为空,是驳回/撤销后提交/废弃
+ ProcessExecuteDTO executeParam = param.getExecuteParam();
+ MKProcessExecuteDTO processExecuteDTO = convert.dto2mk(executeParam);
+ processExecuteDTO.setLoginName(param.getPromoterLoginName());
+ processExecuteDTO.setFormValues(param.getProcessParam());
+ // 临时变量设置为业务表单对象,以便不用修改mk流程表单字段,直接使用流程模板临时变量
+ processExecuteDTO.setTempVarData(ObjectUtil.cloneByStream(param.getProcessParam()));
+ // 重新设置标题,防止标题变了
+ processExecuteDTO.setSubject(param.getSubject());
+ mkService.processExecute(processExecuteDTO);
+ return executeParam.getProcessId();
+ }
+
+}
diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/SystemApplication.java b/blade-service/blade-system/src/main/java/org/springblade/system/SystemApplication.java
index 278ef63..b82811e 100644
--- a/blade-service/blade-system/src/main/java/org/springblade/system/SystemApplication.java
+++ b/blade-service/blade-system/src/main/java/org/springblade/system/SystemApplication.java
@@ -28,11 +28,13 @@ package org.springblade.system;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
+import org.springframework.context.annotation.ComponentScan;
/**
* 系统模块启动器
* @author Chill
*/
+@ComponentScan(basePackages = {"org.springblade.system", "org.springblade.process"})
@BladeCloudApplication
public class SystemApplication {
diff --git a/pom.xml b/pom.xml
index 41c2daa..29b91ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -110,6 +110,11 @@
blade-system-api
${revision}
+
+ org.springblade
+ blade-process-api
+ ${revision}
+
org.springblade