多组织机构审批流功能开发

This commit is contained in:
syruan 2026-01-03 16:05:57 +08:00
parent 2d5acdce92
commit fedf90f077
17 changed files with 195 additions and 81 deletions

View File

@ -108,6 +108,11 @@ public class SysUser extends BaseEntity {
@Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT)
private Date loginDate;
/**
* 组织机构名称
*/
private String deptName;
/**
* 部门对象
*/
@ -230,6 +235,14 @@ public class SysUser extends BaseEntity {
return phonenumber;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public void setPhonenumber(String phonenumber) {
this.phonenumber = phonenumber;
}

View File

@ -49,6 +49,17 @@ public class ApprovalProcessController extends BaseController {
return success(processService.selectProcessById(id));
}
/**
* 根据业务类型和组织机构ID查询审批流程
*/
@ApiOperation("根据业务类型和组织机构ID查询审批流程")
//@RequiresPermissions("material:approval:process:query")
@GetMapping("/query")
public AjaxResult queryByBusinessTypeAndOrgId(@RequestParam("businessType") String businessType, @RequestParam(value = "orgId", required = false) Long orgId) {
ApprovalProcess process = processService.selectProcessByBusinessTypeAndOrgId(businessType, orgId);
return success(process);
}
/**
* 新增审批流程
*/

View File

@ -30,9 +30,6 @@ public class ApprovalNode implements Serializable {
@ApiModelProperty("流程ID")
private Long processId;
@ApiModelProperty("节点编码")
private String nodeCode;
@ApiModelProperty("节点名称")
private String nodeName;

View File

@ -28,15 +28,18 @@ public class ApprovalProcess implements Serializable {
@ApiModelProperty("主键ID")
private Long id;
@ApiModelProperty("流程编码")
private String processCode;
@ApiModelProperty("流程名称")
private String processName;
@ApiModelProperty("业务类型")
private String businessType;
@ApiModelProperty("组织机构ID")
private Long orgId;
@ApiModelProperty("组织机构名称")
private String orgName;
@ApiModelProperty("流程描述")
private String description;

View File

@ -33,19 +33,21 @@ public interface ApprovalProcessMapper {
/**
* 根据业务类型查询审批流程
*
*
* @param businessType 业务类型
* @return 流程信息
*/
ApprovalProcess selectProcessByBusinessType(@Param("businessType") String businessType);
/**
* 根据流程编码查询审批流程
*
* @param processCode 流程编码
* 根据业务类型和组织机构ID查询审批流程
*
* @param businessType 业务类型
* @param orgId 组织机构ID
* @return 流程信息
*/
ApprovalProcess selectProcessByCode(@Param("processCode") String processCode);
ApprovalProcess selectProcessByBusinessTypeAndOrgId(@Param("businessType") String businessType,
@Param("orgId") Long orgId);
/**
* 新增审批流程

View File

@ -13,7 +13,7 @@ public interface IApprovalEngineService {
/**
* 创建审批实例
*
*
* @param businessType 业务类型
* @param businessId 业务ID
* @param businessData 业务数据JSON
@ -21,6 +21,17 @@ public interface IApprovalEngineService {
*/
ApprovalInstance createApprovalInstance(String businessType, String businessId, String businessData);
/**
* 创建审批实例支持多组织机构
*
* @param businessType 业务类型
* @param businessId 业务ID
* @param businessData 业务数据JSON
* @param orgId 组织机构ID
* @return 审批实例
*/
ApprovalInstance createApprovalInstance(String businessType, String businessId, String businessData, Long orgId);
/**
* 执行审批操作
*

View File

@ -30,12 +30,21 @@ public interface IApprovalProcessService {
/**
* 根据业务类型查询审批流程
*
*
* @param businessType 业务类型
* @return 流程信息
*/
ApprovalProcess selectProcessByBusinessType(String businessType);
/**
* 根据业务类型和组织机构ID查询审批流程
*
* @param businessType 业务类型
* @param orgId 组织机构ID
* @return 流程信息
*/
ApprovalProcess selectProcessByBusinessTypeAndOrgId(String businessType, Long orgId);
/**
* 新增审批流程
*

View File

@ -32,8 +32,7 @@ import java.util.stream.Collectors;
/**
* 审批引擎服务实现
*
* @author bonus
* @date 2024-11-18
* @author syruan
*/
@Service
public class ApprovalEngineServiceImpl implements IApprovalEngineService {
@ -67,8 +66,28 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
@Override
@Transactional(rollbackFor = Exception.class)
public ApprovalInstance createApprovalInstance(String businessType, String businessId, String businessData) {
// 调用新的方法orgId null使用默认流程
Long orgId = SecurityUtils.getLoginUser().getSysUser().getDeptId();
return createApprovalInstance(businessType, businessId, businessData, orgId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ApprovalInstance createApprovalInstance(String businessType, String businessId, String businessData, Long orgId) {
// 1. 查询审批流程配置
ApprovalProcess process = processMapper.selectProcessByBusinessType(businessType);
ApprovalProcess process;
if (orgId != null) {
// 按组织机构查询
process = processMapper.selectProcessByBusinessTypeAndOrgId(businessType, orgId);
if (process == null) {
// 按业务类型查询兼容旧逻辑
process = processMapper.selectProcessByBusinessType(businessType);
}
} else {
// 按业务类型查询兼容旧逻辑
process = processMapper.selectProcessByBusinessType(businessType);
}
if (process == null) {
throw new ServiceException("未找到业务类型[" + businessType + "]的审批流程配置");
}
@ -105,16 +124,7 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
log.info("第一个节点[{}]设置为自动通过,开始自动审批", firstNode.getNodeName());
// 创建自动审批记录
ApprovalRecord autoRecord = new ApprovalRecord();
autoRecord.setInstanceId(instance.getId());
autoRecord.setNodeId(firstNode.getId());
autoRecord.setNodeName(firstNode.getNodeName());
autoRecord.setNodeOrder(firstNode.getNodeOrder());
autoRecord.setApproverId(0L); // 系统自动审批
autoRecord.setApproverName("系统自动审批");
autoRecord.setApproveResult(ApprovalResultEnum.PASS.getCode());
autoRecord.setApproveOpinion("自动通过");
autoRecord.setApproveTime(new Date());
ApprovalRecord autoRecord = getAutoRecord(instance, firstNode);
recordMapper.insertRecord(autoRecord);
log.info("第一个节点[{}]自动审批完成,继续流转", firstNode.getNodeName());
@ -257,23 +267,14 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
instanceMapper.updateInstanceStatus(instance.getId(), ApprovalStatusEnum.IN_PROGRESS.getCode());
log.info("审批实例[{}]流转到下一节点[{}]", instance.getInstanceCode(), nextNode.getNodeName());
// 检查下一个节点是否自动通过
log.info("节点[{}]的autoPass值为[{}]", nextNode.getNodeName(), nextNode.getAutoPass());
if ("1".equals(nextNode.getAutoPass())) {
log.info("节点[{}]设置为自动通过,开始自动审批", nextNode.getNodeName());
// 创建自动审批记录
ApprovalRecord autoRecord = new ApprovalRecord();
autoRecord.setInstanceId(instance.getId());
autoRecord.setNodeId(nextNode.getId());
autoRecord.setNodeName(nextNode.getNodeName());
autoRecord.setNodeOrder(nextNode.getNodeOrder());
autoRecord.setApproverId(0L); // 系统自动审批
autoRecord.setApproverName("系统自动审批");
autoRecord.setApproveResult(ApprovalResultEnum.PASS.getCode());
autoRecord.setApproveOpinion("自动通过");
autoRecord.setApproveTime(new Date());
ApprovalRecord autoRecord = getAutoRecord(instance, nextNode);
recordMapper.insertRecord(autoRecord);
log.info("节点[{}]自动审批完成,继续流转", nextNode.getNodeName());
@ -286,6 +287,23 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
}
}
/**
* 创建自动审批记录
*/
private static ApprovalRecord getAutoRecord(ApprovalInstance instance, ApprovalNode nextNode) {
ApprovalRecord autoRecord = new ApprovalRecord();
autoRecord.setInstanceId(instance.getId());
autoRecord.setNodeId(nextNode.getId());
autoRecord.setNodeName(nextNode.getNodeName());
autoRecord.setNodeOrder(nextNode.getNodeOrder());
autoRecord.setApproverId(0L); // 系统自动审批
autoRecord.setApproverName("系统自动审批");
autoRecord.setApproveResult(ApprovalResultEnum.PASS.getCode());
autoRecord.setApproveOpinion("自动通过");
autoRecord.setApproveTime(new Date());
return autoRecord;
}
/**
* 校验审批人权限
*/
@ -341,8 +359,7 @@ public class ApprovalEngineServiceImpl implements IApprovalEngineService {
}
}
} catch (Exception e) {
log.error("执行审批回调失败,实例编号:{},业务类型:{}",
instance.getInstanceCode(), instance.getBusinessType(), e);
log.error("执行审批回调失败,实例编号:{},业务类型:{}", instance.getInstanceCode(), instance.getBusinessType(), e);
// 重新抛出异常触发事务回滚
throw new RuntimeException("审批回调执行失败:" + e.getMessage(), e);
}

View File

@ -1,5 +1,6 @@
package com.bonus.material.approval.service.impl;
import com.bonus.common.security.utils.SecurityUtils;
import com.bonus.material.approval.domain.ApprovalNode;
import com.bonus.material.approval.domain.ApprovalProcess;
import com.bonus.material.approval.mapper.ApprovalNodeMapper;
@ -14,8 +15,7 @@ import java.util.List;
/**
* 审批流程配置服务实现
*
* @author bonus
* @date 2024-11-18
* @author syruan
*/
@Service
public class ApprovalProcessServiceImpl implements IApprovalProcessService {
@ -53,13 +53,35 @@ public class ApprovalProcessServiceImpl implements IApprovalProcessService {
return process;
}
@Override
public ApprovalProcess selectProcessByBusinessTypeAndOrgId(String businessType, Long orgId) {
if (orgId == null) {
orgId = SecurityUtils.getLoginUser().getSysUser().getDeptId();
}
ApprovalProcess process = processMapper.selectProcessByBusinessTypeAndOrgId(businessType, orgId);
if (process != null) {
// 查询节点列表
List<ApprovalNode> nodeList = nodeMapper.selectNodeListByProcessId(process.getId());
process.setNodeList(nodeList);
}
return process;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int insertProcess(ApprovalProcess process) {
// 检查是否已存在相同业务类型的流程
ApprovalProcess existingProcess = processMapper.selectProcessByBusinessType(process.getBusinessType());
if (process == null) {
throw new RuntimeException("审批流程信息不能为空");
}
if (process.getOrgId() == null) {
process.setOrgId(SecurityUtils.getLoginUser().getSysUser().getDeptId());
process.setOrgName(SecurityUtils.getLoginUser().getSysUser().getDept().getDeptName());
}
// 检查是否已存在相同业务类型和组织机构的流程
ApprovalProcess existingProcess = processMapper.selectProcessByBusinessTypeAndOrgId(process.getBusinessType(), process.getOrgId());
if (existingProcess != null) {
throw new RuntimeException("业务类型 [" + process.getBusinessType() + "] 的审批流程已存在,流程名称:" + existingProcess.getProcessName());
throw new RuntimeException("业务类型 [" + process.getBusinessType() + "] 在组织机构 ["
+ process.getOrgName() + "] 的审批流程已存在,流程名称:" + existingProcess.getProcessName());
}
// 插入流程
@ -94,7 +116,6 @@ public class ApprovalProcessServiceImpl implements IApprovalProcessService {
}
nodeMapper.batchInsertNode(process.getNodeList());
}
return rows;
}

View File

@ -1,12 +1,12 @@
package com.bonus.material.equipment.mapper;
import com.bonus.system.api.domain.SysUser;
import com.bonus.material.basic.domain.SysDeptVO;
import com.bonus.material.equipment.domain.ConfigEntity;
import com.bonus.material.equipment.domain.DeptEquipmentConfig;
import com.bonus.material.equipment.domain.DeptTreeSelect;
import com.bonus.material.equipment.domain.SysDept;
import com.bonus.material.equipment.domain.*;
import com.bonus.system.api.domain.SysUser;
import java.util.List;

View File

@ -77,4 +77,16 @@ public class UserController extends BaseController {
return getDataTableError(new ArrayList<>());
}
@GetMapping("/userList")
@SysLog(title = "用户列表", businessType = OperaType.QUERY,logType = 0,module = "系统管理->用户管理",details = "查询用户列表")
public TableDataInfo userList(com.bonus.common.biz.domain.SysUser user) {
try{
List<com.bonus.common.biz.domain.SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}catch (Exception e){
logger.error(e.toString(),e);
}
return getDataTableError(new ArrayList<>());
}
}

View File

@ -1,5 +1,6 @@
package com.bonus.material.user.mapper;
import com.bonus.common.biz.domain.SysUser;
import com.bonus.material.user.entity.UserDto;
import com.bonus.common.biz.domain.SysDept;
import com.bonus.system.api.domain.SysRole;
@ -18,4 +19,6 @@ public interface UserMapper {
List<SysDept> selectDeptList(SysDept dept);
List<SysRole> selectRoleList(SysRole role);
List<SysUser> selectUserList(SysUser user);
}

View File

@ -1,5 +1,6 @@
package com.bonus.material.user.service;
import com.bonus.common.biz.domain.SysUser;
import com.bonus.common.biz.domain.TreeSelect;
import com.bonus.material.user.entity.UserDto;
import com.bonus.common.biz.domain.SysDept;
@ -20,4 +21,6 @@ public interface UserService {
List<TreeSelect> selectDeptTreeList(SysDept dept);
List<SysRole> selectRoleList(SysRole role);
List<SysUser> selectUserList(SysUser user);
}

View File

@ -1,6 +1,7 @@
package com.bonus.material.user.service.impl;
import com.bonus.common.biz.domain.SysDept;
import com.bonus.common.biz.domain.SysUser;
import com.bonus.common.biz.domain.TreeSelect;
import com.bonus.common.core.utils.SpringUtils;
import com.bonus.common.core.utils.StringUtils;
@ -21,6 +22,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
@ -70,6 +72,11 @@ public class UserServiceImpl implements UserService {
}
}
@Override
public List<SysUser> selectUserList(SysUser user) {
return userMapper.selectUserList(user);
}
/**
* 查询部门管理数据
*

View File

@ -7,7 +7,6 @@
<resultMap id="ApprovalNodeResult" type="com.bonus.material.approval.domain.ApprovalNode">
<id property="id" column="id"/>
<result property="processId" column="process_id"/>
<result property="nodeCode" column="node_code"/>
<result property="nodeName" column="node_name"/>
<result property="nodeOrder" column="node_order"/>
<result property="approverType" column="approver_type"/>
@ -24,7 +23,6 @@
<sql id="selectNodeVo">
select id,
process_id,
node_code,
node_name,
node_order,
approver_type,
@ -41,7 +39,6 @@
<select id="selectNodeListByProcessId" parameterType="Long" resultMap="ApprovalNodeResult">
SELECT ban.id,
ban.process_id,
ban.node_code,
ban.node_name,
ban.node_order,
ban.approver_type,
@ -87,7 +84,6 @@
insert into bm_approval_node
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="processId != null">process_id,</if>
<if test="nodeCode != null and nodeCode != ''">node_code,</if>
<if test="nodeName != null and nodeName != ''">node_name,</if>
<if test="nodeOrder != null">node_order,</if>
<if test="approverType != null">approver_type,</if>
@ -99,7 +95,6 @@
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="processId != null">#{processId},</if>
<if test="nodeCode != null and nodeCode != ''">#{nodeCode},</if>
<if test="nodeName != null and nodeName != ''">#{nodeName},</if>
<if test="nodeOrder != null">#{nodeOrder},</if>
<if test="approverType != null">#{approverType},</if>
@ -139,11 +134,11 @@
</delete>
<insert id="batchInsertNode" parameterType="java.util.List">
insert into bm_approval_node (process_id, node_code, node_name, node_order, approver_type,
insert into bm_approval_node (process_id, node_name, node_order, approver_type,
approver_ids, approve_mode, auto_pass, create_by, create_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.processId}, #{item.nodeCode}, #{item.nodeName}, #{item.nodeOrder}, #{item.approverType},
(#{item.processId}, #{item.nodeName}, #{item.nodeOrder}, #{item.approverType},
#{item.approverIds}, #{item.approveMode}, #{item.autoPass}, #{item.createBy}, now())
</foreach>
</insert>

View File

@ -6,9 +6,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap id="ApprovalProcessResult" type="com.bonus.material.approval.domain.ApprovalProcess">
<id property="id" column="id"/>
<result property="processCode" column="process_code"/>
<result property="processName" column="process_name"/>
<result property="businessType" column="business_type"/>
<result property="orgId" column="org_id"/>
<result property="orgName" column="org_name"/>
<result property="description" column="description"/>
<result property="status" column="status"/>
<result property="createBy" column="create_by"/>
@ -19,19 +20,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectProcessVo">
select id, process_code, process_name, business_type, description, status,
select id, process_name, business_type, org_id, org_name, description, status,
create_by, create_time, update_by, update_time, remark
from bm_approval_process
</sql>
<select id="selectProcessList" parameterType="com.bonus.material.approval.domain.ApprovalProcess"
resultMap="ApprovalProcessResult">
<select id="selectProcessList" parameterType="com.bonus.material.approval.domain.ApprovalProcess" resultMap="ApprovalProcessResult">
<include refid="selectProcessVo"/>
<where>
<if test="processCode != null and processCode != ''">
AND process_code like concat('%', #{processCode}, '%')
</if>
<if test="processName != null and processName != ''">
AND process_name like concat('%', #{processName}, '%')
</if>
@ -39,7 +36,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND business_type = #{businessType}
</if>
<if test="status != null and status != ''">
AND status = #{status}
AND `status` = #{status}
</if>
</where>
order by create_time desc
@ -55,17 +52,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where business_type = #{businessType} limit 1
</select>
<select id="selectProcessByCode" parameterType="String" resultMap="ApprovalProcessResult">
<select id="selectProcessByBusinessTypeAndOrgId" resultMap="ApprovalProcessResult">
<include refid="selectProcessVo"/>
where process_code = #{processCode}
where business_type = #{businessType} and org_id = #{orgId} limit 1
</select>
<insert id="insertProcess" parameterType="com.bonus.material.approval.domain.ApprovalProcess" useGeneratedKeys="true" keyProperty="id">
insert ignore into bm_approval_process
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="processCode != null and processCode != ''">process_code,</if>
<if test="processName != null and processName != ''">process_name,</if>
<if test="businessType != null and businessType != ''">business_type,</if>
<if test="orgId != null">org_id,</if>
<if test="orgName != null and orgName != ''">org_name,</if>
<if test="description != null">description,</if>
<if test="status != null">status,</if>
<if test="createBy != null">create_by,</if>
@ -73,9 +71,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
create_time
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="processCode != null and processCode != ''">#{processCode},</if>
<if test="processName != null and processName != ''">#{processName},</if>
<if test="businessType != null and businessType != ''">#{businessType},</if>
<if test="orgId != null">#{orgId},</if>
<if test="orgName != null and orgName != ''">#{orgName},</if>
<if test="description != null">#{description},</if>
<if test="status != null">#{status},</if>
<if test="createBy != null">#{createBy},</if>

View File

@ -15,28 +15,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="getUserById" resultType="com.bonus.material.user.entity.UserDto">
SELECT
su.*,
SUM(
IF
( is_read = 0, 1, 0 )) AS msgNum
SUM(IF(is_read = 0, 1, 0)) AS msgNum
FROM
sys_user su
LEFT JOIN bm_message bm ON su.user_id = bm.to_user
LEFT JOIN bm_message bm ON su.user_id = bm.to_user
WHERE
su.user_id =#{userId}
</select>
<select id="selectDeptList" resultType="com.bonus.common.biz.domain.SysDept">
select d.dept_id,
d.parent_id,
d.ancestors,
d.dept_name,
d.order_num,
d.leader,
d.phone,
d.status,
d.del_flag,
d.create_by,
d.create_time
from sys_dept d
select
d.dept_id,
d.parent_id,
d.ancestors,
d.dept_name,
d.order_num,
d.leader,
d.phone,
d.status,
d.del_flag,
d.create_by,
d.create_time
from
sys_dept d
where d.del_flag = '0'
<if test="companyId != null and companyId != 0">
AND (find_in_set(#{companyId}, ancestors) or dept_id = #{companyId})
@ -55,6 +56,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</if>
order by d.parent_id, d.order_num
</select>
<select id="selectRoleList" resultType="com.bonus.system.api.domain.SysRole">
select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly,
r.company_id, r.status, r.del_flag, r.create_time, r.remark, r.is_built_in
@ -86,4 +88,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</if>
order by r.role_sort
</select>
<select id="selectUserList" resultType="com.bonus.common.biz.domain.SysUser">
select
su.user_id AS userId, su.login_date AS loginDate ,su.user_name AS userName, su.nick_name AS nickName,
su.dept_id as deptId, su.phonenumber, su.status, su.create_time AS createTime, sd.dept_name AS deptName
from sys_user su
left join sys_dept sd on su.dept_id = sd.dept_id
where su.del_flag = '0'
</select>
</mapper>