feat(process): 添加业务流程管理相关API接口

This commit is contained in:
黄斌峰
2026-08-09 22:15:15 +08:00
parent 31f8e07ffe
commit 0d247afef3
34 changed files with 2941 additions and 0 deletions
@@ -0,0 +1,16 @@
<?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>org.springblade</groupId>
<artifactId>blade-service-api</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-process-api</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
</project>
@@ -0,0 +1,105 @@
package org.springblade.process.feign;
import org.springblade.core.launch.constant.AppConstant;
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.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* 业务流程关联表 Feign接口类
*
* @author BladeX
* @since 2024-09-19
*/
@FeignClient(
value = AppConstant.APPLICATION_SYSTEM_NAME
)
public interface IBusinessProcessClient {
String API_PREFIX = "/feign/client/businessProcess";
String SUBMIT_BUSINESS_PROCESS = API_PREFIX + "/submitBusinessProcess";
String UPDATE_BUSINESS_PROCESS_APPROVER = API_PREFIX + "/updateBusinessProcessApprover";
String REFRESH_BUSINESS_PROCESS_CURRENT_HANDLERS = API_PREFIX + "/refreshBusinessProcessCurrentHandlers";
String UPDATE_BUSINESS_PROCESS_STATUS = API_PREFIX + "/updateBusinessProcessStatus";
String DELETE_BUSINESS_PROCESS = API_PREFIX + "/deleteBusinessProcess";
String QUERY_TODO_LIST = API_PREFIX + "/queryTodoList";
String QUERY_BUSINESS_PROCESS_SNAPSHOT = API_PREFIX + "/queryBusinessProcessSnapshot";
String QUERY_APPROVED_RECORD_LIST = API_PREFIX + "/queryApprovedRecordsNoAttachments";
/**
* 提交业务流程
* @param param
*/
@PostMapping(SUBMIT_BUSINESS_PROCESS)
FR<BusinessProcessVO> submitBusinessProcess(@Validated @RequestBody BusinessProcessSubmitDTO<?> param);
/**
* 修改业务流程状态
* @param param
* @return
*/
@PostMapping(UPDATE_BUSINESS_PROCESS_STATUS)
FR<Boolean> updateBusinessProcessStatus(@Validated @RequestBody BusinessProcessUpdateDTO param);
/**
* 修改业务流程审批人
* @param param
* @return
*/
@PostMapping(UPDATE_BUSINESS_PROCESS_APPROVER)
FR<BusinessProcessVO> updateBusinessProcessApprover(@Validated @RequestBody BusinessProcessUpdateDTO param);
/**
* 只刷新当前节点和当前处理人
* @param param
* @return
*/
@PostMapping(REFRESH_BUSINESS_PROCESS_CURRENT_HANDLERS)
FR<BusinessProcessVO> refreshBusinessProcessCurrentHandlers(@Validated @RequestBody BusinessProcessCurrentHandlerRefreshDTO param);
/**
* 查询流程当前待办列表
* @param processInstanceId
* @return
*/
@GetMapping(QUERY_TODO_LIST)
FR<List<ProcessTodoVO>> queryTodoList(@RequestParam("processInstanceId") String processInstanceId);
/**
* 查询业务流程当前快照
* @param processInstanceId
* @return
*/
@GetMapping(QUERY_BUSINESS_PROCESS_SNAPSHOT)
FR<BusinessProcessVO> queryBusinessProcessSnapshot(@RequestParam("processInstanceId") String processInstanceId);
/**
* 删除业务流程
* @param param
* @return
*/
@PostMapping(DELETE_BUSINESS_PROCESS)
FR<Boolean> deleteBusinessProcess(@Validated @RequestBody BusinessProcessDeleteDTO param);
/**
* 查询流程审批记录不处理附件
* @param bizId
* @param processInstanceId
* @return
*/
@GetMapping(QUERY_APPROVED_RECORD_LIST)
FR<List<ProcessApprovedRecordVO>> queryApprovedRecordsNoAttachments(@RequestParam(name = "bizId", required = false) String bizId, @RequestParam(name = "processInstanceId", required = false) String processInstanceId);
}
@@ -0,0 +1,34 @@
package org.springblade.process.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 附加操作信息
*
* @author linbb
*/
@Schema(description = "附加操作信息")
@Data
public class AdditionOperationParameterDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 操作类型
*/
private String operationType;
/**
* 操作身份
*/
private String operationIdentity;
/**
* 操作参数
*/
private String parameter;
}
@@ -0,0 +1,119 @@
package org.springblade.process.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* mk审批中心查询参数
* @author bfhuange
* @since 2025/4/2
*/
@Data
@Schema(description = "mk审批中心查询参数")
public class ApprovalDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 单据类型 myApproving 我的待审,myApproved 我的已审,myReading 我的待阅,myReaded 我的已阅,myRelated 我参与的,myCreated 我发起的
*/
@NotBlank(message = "单据类型不能为空")
@Schema(description = "单据类型 myApproving 我的待审,myApproved 我的已审,myReading 我的待阅,myReaded 我的已阅,myRelated 我参与的,myCreated 我发起的")
private String docType;
/**
* 关键字
*/
@Schema(description = "关键字")
private String keyword;
/**
* 模板名称
*/
@Schema(description = "模板名称")
private String templateName;
/**
* 申请时间开始
*/
@Schema(description = "申请时间开始")
private Date applicantTimeStart;
/**
* 申请时间结束
*/
@Schema(description = "申请时间结束")
private Date applicantTimeEnd;
//==================================待办参数=======================
/**
* 接收时间开始
*/
@Schema(description = "接收时间开始")
private Date receiveTimeStart;
/**
* 接收时间结束
*/
@Schema(description = "接收时间结束")
private Date receiveTimeEnd;
//==================================已处理参数=======================
/**
* 流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束
*/
@Schema(description = "流程状态 00 – 废弃、10 – 草稿、11 – 驳回 20 – 待审、21 – 异常、40 – 挂起 30 – 结束")
private String status;
/**
* 结束时间开始
*/
@Schema(description = "结束时间开始")
private Date finishTimeStart;
/**
* 结束时间结束
*/
@Schema(description = "结束时间结束")
private Date finishTimeEnd;
/**
* 最后处理时间开始
*/
@Schema(description = "最后处理时间开始")
private Date lastHandleStart;
/**
* 最后处理时间结束
*/
@Schema(description = "最后处理时间结束")
private Date lastHandleEnd;
//==================================已阅参数=======================
/**
* 阅读时间开始
*/
@Schema(description = "阅读时间开始")
private Date readTimeStart;
/**
* 阅读时间结束
*/
@Schema(description = "阅读时间结束")
private Date readTimeEnd;
//==================================我参与的参数=======================
/**
* 创建时间开始
*/
@Schema(description = "创建时间开始")
private Date createTimeStart;
/**
* 创建时间结束
*/
@Schema(description = "创建时间结束")
private Date createTimeEnd;
/**
* 登录名
*/
@Schema(description = "登录名", hidden = true)
private String loginName;
}
@@ -0,0 +1,41 @@
package org.springblade.process.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 业务流程当前处理人刷新参数
*
* @author bfhuange
* @date 2026/4/9
*/
@Schema(description = "业务流程当前处理人刷新参数")
@Data
public class BusinessProcessCurrentHandlerRefreshDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程实例id
*/
@NotBlank(message = "流程实例id不能为空")
@Schema(description = "流程实例id")
private String processInstanceId;
/**
* 发起人登录名,可为空。
* 为空时优先从业务流程表回填;仅当业务流程不存在时,才回退使用调用方传入值。
*/
@Schema(description = "发起人登录名,可为空;为空时优先从业务流程表回填")
private String promoterLoginName;
/**
* 是否流程已完成
*/
@Schema(description = "是否流程已完成")
private boolean complete;
}
@@ -0,0 +1,39 @@
package org.springblade.process.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 业务流程删除参数
*
* @author BladeX
* @since 2024-11-26
*/
@NoArgsConstructor
@Schema(description = "业务流程删除参数")
@Data
public class BusinessProcessDeleteDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务id
*/
@NotNull(message = "业务id不能为空")
@Schema(description = "业务id")
private Long bizId;
/**
* 发起人登录名,即erp手机号或账号
*/
// @NotBlank(message = "发起人登录名不能为空")
@Schema(description = "发起人登录名,即erp手机号或账号")
private String promoterLoginName;
public BusinessProcessDeleteDTO(Long bizId) {
this.bizId = bizId;
}
}
@@ -0,0 +1,42 @@
package org.springblade.process.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 业务流程查询参数
*
* @author BladeX
* @since 2024-09-23
*/
@Schema(description = "业务流程查询参数")
@Data
public class BusinessProcessQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程类型
*/
@Schema(description = "审批类型")
private String processType;
/**
* 文档编号
*/
@Schema(description = "审批编号")
private String docCode;
/**
* 类型 todo:待审批,create:我创建的,done:我参与的
*/
@NotBlank(message = "类型不能为空")
@Schema(defaultValue = "类型 todo:待审批,create:我创建的,done:我参与的")
private String type;
/**
* 当前登录人账号
*/
@Schema(hidden = true)
private String loginName;
}
@@ -0,0 +1,81 @@
package org.springblade.process.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 业务流程提交参数
*
* @author BladeX
* @since 2024-09-19
*/
@Schema(description = "业务流程提交参数")
@Data
public class BusinessProcessSubmitDTO<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 业务id
*/
@NotNull(message = "业务id不能为空")
@Schema(description = "业务id")
private Long bizId;
/**
* 流程类型
*/
@NotBlank(message = "流程类型不能为空")
@Schema(description = "流程类型")
private String processType;
/**
* 文档编号
*/
@Schema(description = "文档编号")
private String docCode;
/**
* 标题
*/
@Schema(description = "标题")
private String subject;
/**
* 发起人id,空自动取登录人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 T processParam;
/**
* 流程执行参数,透传mk参数
*/
@Schema(description = "流程执行参数")
private ProcessExecuteDTO executeParam;
/**
* 添加群组编码
*/
@Schema(description = "添加群组编码")
private boolean addGroupCode;
}
@@ -0,0 +1,39 @@
package org.springblade.process.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
/**
* 业务流程修改参数
*
* @author BladeX
* @since 2024-09-19
*/
@Schema(description = "业务流程修改参数")
@Data
public class BusinessProcessUpdateDTO extends BusinessProcessCurrentHandlerRefreshDTO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 操作节点id,流程审批结束为空
*/
@Schema(description = "操作节点id")
private String operationNodeId;
/**
* 操作节点编号,流程审批结束为空
*/
@Schema(description = "操作节点编号")
private String operationNodeNumber;
/**
* 驳回节点id N2 是起草节点
*/
@Schema(description = "驳回节点id")
private String rejectNodeId;
/**
* 审批状态
*/
@Schema(description = "审批状态")
private String approveStatus;
}
@@ -0,0 +1,74 @@
package org.springblade.process.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.system.pojo.dto.AdditionOperationParameterDTO;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* mk 流程执行参数
* @author bfhuange
* @date 2024/9/26
*/
@Schema(description = "mk 流程执行参数")
@Data
public class ProcessExecuteDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 表单实例id,业务id
*/
private String formInstanceId;
/**
* 登录账号 登录用户账号/手机号
*/
private String loginName;
/**
* 流程标题
*/
private String subject;
/**
* 任务ID
*/
private String taskId;
/**
* 任务类型
*/
private String activityType;
/**
* 操作详细参数(json)
*/
private String parameter;
/**
* 流程实例ID
*/
private String processId;
/**
* 操作标识(相同操作类型和相同操作身份可能存在多个操作配置)
*/
private String operationId;
/**
* 操作类型
*/
private String operationType;
/**
* 操作身份
*/
private String operationIdentity;
/**
* 附加操作参数信息
*/
private List<AdditionOperationParameterDTO> additionParameters;
/**
* 表单实例Model Name
*/
private String formInstanceModel;
/**
* 业务表单字段值集合
*/
// private Map<String, Object> formValues;
private Object formValues;
}
@@ -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;
}
@@ -0,0 +1,164 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* 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;
}
@@ -0,0 +1,165 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* 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<String> 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());
}
}
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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<String> senders;
/**
* 附件参数
*/
@Schema(description = "附件参数")
private List<ProcessAttachmentVO> attachmentParameter;
/**
* 流程附言
*/
@Schema(description = "流程附言")
private List<ProcessCommentVO> processComments;
}
@@ -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;
}
@@ -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<ProcessAttachmentVO> attachments;
}
@@ -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;
}
+1
View File
@@ -25,6 +25,7 @@
<module>blade-record-api</module> <module>blade-record-api</module>
<module>blade-file-api</module> <module>blade-file-api</module>
<module>blade-open-api</module> <module>blade-open-api</module>
<module>blade-process-api</module>
</modules> </modules>
<dependencies> <dependencies>
+4
View File
@@ -45,6 +45,10 @@
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
<artifactId>blade-user-api</artifactId> <artifactId>blade-user-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-process-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>
@@ -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<IPage<ApprovalVO>> mkList(@Validated @RequestBody(required = false) ApprovalDTO param, Query query) {
if (param == null) {
param = new ApprovalDTO();
}
param.setLoginName(AuthUtil.getUserAccount());
IPage<ApprovalVO> pages = businessProcessService.queryMkApprovalList(Condition.getPage(query), param);
return R.data(pages);
}
@GetMapping("/isEditView")
@ApiOperationSupport(order = 2)
@Operation(summary = "是否编辑页", description = "传入业务id")
public R<Boolean> 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<String> getMKApprovalUrl(String bizId, String processInstanceId) {
return R.data(businessProcessService.getMKApprovalUrl(bizId, processInstanceId));
}
@GetMapping("/getApprovedRecords")
@ApiOperationSupport(order = 4)
@Operation(summary = "查询审批记录", description = "传入业务id或流程实例id")
public R<List<ProcessApprovedRecordVO>> 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);
}
}
@@ -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<ApprovalVO> mk2vos(List<MKApprovalVO> 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<ApprovalVO> mkProcess2vos(List<MKProcessVO> 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();
}
}
@@ -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<ProcessAttachmentVO> attachments2vos(List<MKAttachmentVO> vos);
@Mapping(source = "userOrgInfo", target = "userName", qualifiedByName = "userName")
ProcessCommentVO mk2vo(MKProcessCommentVO vo);
List<ProcessCommentVO> comments2vos(List<MKProcessCommentVO> vos);
default List<ProcessApprovedRecordVO> auditNotes2vos(List<MKAuditNoteVO> vos, Function<ProcessApprovedRecordVO, List<String>> 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);
}
}
@@ -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<MKApprovalConditionDTO, MKConditionDTO> setter;
/**
* 组合参数
*/
private final List<Compose> composes;
MKApprovalConvert(BiConsumer<MKApprovalConditionDTO, MKConditionDTO> 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<ApprovalDTO, String> getGetter(Function<ApprovalDTO, Date> dateGetter) {
return approvalDTO -> getTimestamp(dateGetter.apply(approvalDTO));
}
/**
* 工厂方法
* @param builderSetter
* @param getter
* @return
*/
private static Compose compose(BiConsumer<MKConditionDTOBuilder, String> builderSetter, Function<ApprovalDTO, String> getter) {
return new Compose(builderSetter, getter);
}
/**
* 组合参数,条件和取值
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public static class Compose {
/**
* 条件builder的setter
*/
private BiConsumer<MKConditionDTOBuilder, String> builderSetter;
/**
* 从参数取值
*/
private Function<ApprovalDTO, String> getter;
}
}
@@ -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<BusinessProcessVO> submitBusinessProcess(@Validated @RequestBody BusinessProcessSubmitDTO<?> param) {
return FR.data(businessProcessService.submitBusinessProcess(param));
}
@Override
public FR<Boolean> updateBusinessProcessStatus(@Validated @RequestBody BusinessProcessUpdateDTO param) {
return FR.data(businessProcessService.updateBusinessProcessStatus(param));
}
@Override
public FR<BusinessProcessVO> updateBusinessProcessApprover(@Validated @RequestBody BusinessProcessUpdateDTO param) {
return FR.data(businessProcessService.updateBusinessProcessApprover(param));
}
@Override
public FR<BusinessProcessVO> refreshBusinessProcessCurrentHandlers(@Validated @RequestBody BusinessProcessCurrentHandlerRefreshDTO param) {
return FR.data(businessProcessService.refreshBusinessProcessCurrentHandlers(param));
}
@Override
public FR<List<ProcessTodoVO>> queryTodoList(String processInstanceId) {
return FR.data(businessProcessService.queryTodoList(processInstanceId));
}
@Override
public FR<BusinessProcessVO> queryBusinessProcessSnapshot(String processInstanceId) {
return FR.data(businessProcessService.queryBusinessProcessSnapshot(processInstanceId));
}
@PostMapping(DELETE_BUSINESS_PROCESS)
@Override
public FR<Boolean> deleteBusinessProcess(@Validated @RequestBody BusinessProcessDeleteDTO param) {
return FR.data(businessProcessService.deleteBusinessProcess(param));
}
@Override
public FR<List<ProcessApprovedRecordVO>> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) {
return FR.data(businessProcessService.queryApprovedRecordsNoAttachments(bizId, processInstanceId));
}
}
@@ -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<BusinessProcess> {
}
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.process.mapper.BusinessProcessMapper">
<!-- 通用查询映射结果 -->
<resultMap id="businessProcessResultMap" type="org.springblade.process.pojo.entity.BusinessProcess">
<result column="id" property="id"/>
<result column="biz_id" property="bizId"/>
<result column="process_instance_id" property="processInstanceId"/>
<result column="process_type" property="processType"/>
<result column="doc_code" property="docCode"/>
<result column="subject" property="subject"/>
<result column="promoter_id" property="promoterId"/>
<result column="promoter_name" property="promoterName"/>
<result column="promoter_login_name" property="promoterLoginName"/>
<result column="submit_time" property="submitTime"/>
<result column="complete_time" property="completeTime"/>
<result column="current_node_ids" property="currentNodeIds"/>
<result column="current_node_names" property="currentNodeNames"/>
<result column="current_handlers" property="currentHandlers"/>
<result column="receive_time" property="receiveTime"/>
<result column="is_completed" property="isCompleted"/>
<result column="approve_status" property="approveStatus"/>
<result column="tenant_id" property="tenantId"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
</mapper>
@@ -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<BusinessProcess> {
/**
* 提交业务流程
*
* @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<ProcessApprovedRecordVO> queryApprovedRecords(String bizId, String processInstanceId);
/**
* 查询流程审批记录不处理附件
* @param bizId
* @param processInstanceId
* @return
*/
List<ProcessApprovedRecordVO> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId);
/**
* 下载文件
* @param response
* @param fileId
*/
void downloadFile(HttpServletResponse response, String fileId);
/**
* 查询业务流程当前处理人
* @param processInstanceId
* @return
*/
List<ProcessTodoVO> queryTodoList(String processInstanceId);
/**
* 查询mk审批记录
* @param page
* @param param
* @return
*/
IPage<ApprovalVO> queryMkApprovalList(IPage<ApprovalVO> page, ApprovalDTO param);
}
@@ -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<BusinessProcessMapper, BusinessProcess> 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.<BusinessProcess>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.<BusinessProcess>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<MKNodeVO> 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.<BusinessProcess>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.<BusinessProcess>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.<BusinessProcess>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<ProcessApprovedRecordVO> queryApprovedRecords(String bizId, String processInstanceId) {
List<ProcessApprovedRecordVO> records = this.queryApprovedRecordsNoAttachments(bizId, processInstanceId);
if (CollectionUtil.isEmpty(records)) {
return records;
}
Map<String, String> fileMap = new HashMap<>();
for (ProcessApprovedRecordVO record : records) {
List<ProcessAttachmentVO> attachmentParameter = record.getAttachmentParameter();
// 处理电子签名base64并排序附件
attachmentParameter = handleAttachmentBase64(attachmentParameter, fileMap);
record.setAttachmentParameter(attachmentParameter);
if (CollectionUtil.isNotEmpty(record.getProcessComments())) {
for (ProcessCommentVO processComment : record.getProcessComments()) {
// 处理电子签名base64并排序附件
List<ProcessAttachmentVO> attachments = handleAttachmentBase64(processComment.getAttachments(), fileMap);
processComment.setAttachments(attachments);
}
}
}
return records;
}
@Override
public List<ProcessApprovedRecordVO> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) {
boolean bizIdBlank = StringUtil.isBlank(bizId);
boolean processInstanceIdBlank = StringUtil.isBlank(processInstanceId);
AssertUtils.isFalse(bizIdBlank && processInstanceIdBlank, "参数不能为空");
BusinessProcess businessProcess = this.getOne(Wrappers.<BusinessProcess>lambdaQuery()
.eq(!bizIdBlank, BusinessProcess::getBizId, bizId)
.eq(!processInstanceIdBlank, BusinessProcess::getProcessInstanceId, processInstanceId)
.last("limit 1")
);
AssertUtils.notNull(businessProcess, "业务流程不存在");
if (processInstanceIdBlank) {
processInstanceId = businessProcess.getProcessInstanceId();
}
// 查询审批记录
List<MKAuditNoteVO> mkAuditNotes = mkService.queryAuditNotes(new MKAuditNoteDTO(businessProcess.getPromoterLoginName(), processInstanceId));
// 转换参数
return convert.auditNotes2vos(mkAuditNotes, record -> {
List<MKSenderVO> mkSenders = mkService.querySenderList(new MKSenderDTO(record.getProcessInstanceId(), record.getNodeInstanceId()));
return mkSenders.stream()
.map(MKSenderVO::getName)
.toList();
});
}
/**
* 处理附件base64并排序附件
* @param attachmentParameter
* @param fileMap
*/
private List<ProcessAttachmentVO> handleAttachmentBase64(List<ProcessAttachmentVO> attachmentParameter, Map<String, String> fileMap) {
if (CollectionUtil.isEmpty(attachmentParameter)) {
return attachmentParameter;
}
// 电子签名的附件
List<ProcessAttachmentVO> 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<ProcessAttachmentVO> result = new ArrayList<>();
// 纯附件,非电子签名附件
List<ProcessAttachmentVO> 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<ProcessTodoVO> queryTodoList(String processInstanceId) {
AssertUtils.notNull(processInstanceId, "流程实例id不能为空");
BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId);
if (businessProcess == null) {
return null;
}
// 1. 查询当前节点处理人
List<MKNodeVO> 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.<BusinessProcess>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<ProcessTodoVO> getProcessTodoList(List<MKNodeVO> 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<ApprovalVO> queryMkApprovalList(IPage<ApprovalVO> page, ApprovalDTO param) {
if (MKDoc.RELATED.getCode().equals(param.getDocType())) {
// 我参与的,调用流程列表接口
MKProcessDTO processParam = approvalConvert.dto2mk(param);
processParam.setPage((int) page.getCurrent(), (int) page.getSize());
MKPageVO<MKProcessVO> 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<MKApprovalVO> 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<MKApprovalConditionDTO, MKConditionDTO> setter = convert.getSetter();
List<MKApprovalConvert.Compose> 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<MKApprovedHandlerVO> 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<MKNodeVO> 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<ProcessTodoVO> processTodoList = getProcessTodoList(currentNodes);
// 更新业务流程
return updateBusinessProcess(businessProcessId, processTodoList);
}
/**
* 更新业务流程
*
* @param businessProcessId
* @param addToDos
*/
private BusinessProcess updateBusinessProcess(Long businessProcessId, List<ProcessTodoVO> 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();
}
}
@@ -28,11 +28,13 @@ package org.springblade.system;
import org.springblade.core.cloud.client.BladeCloudApplication; import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication; import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant; import org.springblade.core.launch.constant.AppConstant;
import org.springframework.context.annotation.ComponentScan;
/** /**
* 系统模块启动器 * 系统模块启动器
* @author Chill * @author Chill
*/ */
@ComponentScan(basePackages = {"org.springblade.system", "org.springblade.process"})
@BladeCloudApplication @BladeCloudApplication
public class SystemApplication { public class SystemApplication {
+5
View File
@@ -110,6 +110,11 @@
<artifactId>blade-system-api</artifactId> <artifactId>blade-system-api</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-process-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springblade</groupId> <groupId>org.springblade</groupId>