feat(approval): 新增业务审批流程及配置管理
- 新增审批配置主子表(biz_approval_config / biz_approval_config_user),支持或签 - 5 个业务模块接入审批: 采购订单/客户报价/供应商报价/发货单/订单异议 - 统一审批动作接口(提交/通过/驳回),status=10 表示审批中 - 新增"待我审批"聚合页面,按业务类型筛选 - 修复 logback 写本地路径报错,去除文件 appender - 修复 Redis SSL 配置在 Spring Boot 4 下需对象格式 - 补齐部分业务表缺失的 update_by/update_time 审计列 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.system.domain.bid;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
import java.util.List;
|
||||
|
||||
public class BizApprovalConfig extends BaseEntity {
|
||||
private Long id;
|
||||
private String bizType;
|
||||
private String bizName;
|
||||
private String signType;
|
||||
private String enabled;
|
||||
private List<Long> userIds;
|
||||
private List<String> userNames;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public String getBizType() { return bizType; }
|
||||
public void setBizType(String bizType) { this.bizType = bizType; }
|
||||
public String getBizName() { return bizName; }
|
||||
public void setBizName(String bizName) { this.bizName = bizName; }
|
||||
public String getSignType() { return signType; }
|
||||
public void setSignType(String signType) { this.signType = signType; }
|
||||
public String getEnabled() { return enabled; }
|
||||
public void setEnabled(String enabled) { this.enabled = enabled; }
|
||||
public List<Long> getUserIds() { return userIds; }
|
||||
public void setUserIds(List<Long> userIds) { this.userIds = userIds; }
|
||||
public List<String> getUserNames() { return userNames; }
|
||||
public void setUserNames(List<String> userNames) { this.userNames = userNames; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.system.mapper.bid;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
public interface BizApprovalActionMapper {
|
||||
/** 仅当当前状态在 fromStatuses 中时更新业务单据状态 */
|
||||
int updateStatus(@Param("table") String table,
|
||||
@Param("pk") String pk,
|
||||
@Param("statusCol") String statusCol,
|
||||
@Param("id") Long id,
|
||||
@Param("newStatus") String newStatus,
|
||||
@Param("fromStatuses") java.util.List<String> fromStatuses,
|
||||
@Param("updateBy") String updateBy);
|
||||
|
||||
String selectStatus(@Param("table") String table,
|
||||
@Param("pk") String pk,
|
||||
@Param("statusCol") String statusCol,
|
||||
@Param("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.system.mapper.bid;
|
||||
|
||||
import com.ruoyi.system.domain.bid.BizApprovalConfig;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
public interface BizApprovalConfigMapper {
|
||||
List<BizApprovalConfig> selectList(BizApprovalConfig query);
|
||||
BizApprovalConfig selectById(Long id);
|
||||
BizApprovalConfig selectByBizType(String bizType);
|
||||
int insert(BizApprovalConfig record);
|
||||
int update(BizApprovalConfig record);
|
||||
int deleteById(Long id);
|
||||
|
||||
List<Long> selectUserIds(@Param("configId") Long configId);
|
||||
List<String> selectUserNames(@Param("configId") Long configId);
|
||||
int deleteUsers(@Param("configId") Long configId);
|
||||
int insertUser(@Param("configId") Long configId, @Param("userId") Long userId, @Param("sortNo") Integer sortNo);
|
||||
|
||||
int existsUser(@Param("bizType") String bizType, @Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.ruoyi.system.mapper.bid;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface BizApprovalPendingMapper {
|
||||
/** 查询某用户作为审批人的、所有业务表中 status='10' 的单据 */
|
||||
List<Map<String, Object>> selectPendingForUser(@Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.ruoyi.system.service.bid;
|
||||
|
||||
public interface IBizApprovalActionService {
|
||||
/** 提交审批: 业务单据 status -> 10 */
|
||||
int submit(String bizType, Long id, String username);
|
||||
|
||||
/** 通过审批(或签): 任一审批人通过即生效, 业务单据 status -> 通过状态 */
|
||||
int approve(String bizType, Long id, Long userId, String username);
|
||||
|
||||
/** 驳回审批: 业务单据 status -> rejected, 备注记录原因 */
|
||||
int reject(String bizType, Long id, Long userId, String username, String reason);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.system.service.bid;
|
||||
|
||||
import com.ruoyi.system.domain.bid.BizApprovalConfig;
|
||||
import java.util.List;
|
||||
|
||||
public interface IBizApprovalConfigService {
|
||||
List<BizApprovalConfig> selectList(BizApprovalConfig query);
|
||||
BizApprovalConfig selectById(Long id);
|
||||
BizApprovalConfig selectByBizType(String bizType);
|
||||
int insert(BizApprovalConfig record);
|
||||
int update(BizApprovalConfig record);
|
||||
int deleteByIds(Long[] ids);
|
||||
|
||||
/** 校验当前用户是否为该业务的审批人(或签) */
|
||||
boolean canApprove(String bizType, Long userId);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.ruoyi.system.service.bid.impl;
|
||||
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.system.mapper.bid.BizApprovalActionMapper;
|
||||
import com.ruoyi.system.service.bid.IBizApprovalActionService;
|
||||
import com.ruoyi.system.service.bid.IBizApprovalConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class BizApprovalActionServiceImpl implements IBizApprovalActionService {
|
||||
|
||||
@Autowired private BizApprovalActionMapper mapper;
|
||||
@Autowired private IBizApprovalConfigService configService;
|
||||
|
||||
/** 业务类型 -> 表/主键/状态列/审批通过的目标状态/可提交的初始状态 */
|
||||
static class Meta {
|
||||
final String table, pk, statusCol, approvedStatus;
|
||||
final List<String> draftStatuses;
|
||||
Meta(String t, String pk, String sc, String approved, List<String> drafts) {
|
||||
this.table = t; this.pk = pk; this.statusCol = sc;
|
||||
this.approvedStatus = approved; this.draftStatuses = drafts;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, Meta> META = new HashMap<>();
|
||||
static {
|
||||
META.put("PURCHASE_ORDER", new Meta("biz_purchase_order", "po_id", "status", "confirmed", Arrays.asList("draft")));
|
||||
META.put("CLIENT_QUOTE", new Meta("biz_client_quote", "quote_id", "status", "confirmed", Arrays.asList("draft")));
|
||||
META.put("QUOTATION", new Meta("biz_quotation", "quotation_id", "status", "accepted", Arrays.asList("draft", "submitted")));
|
||||
META.put("DELIVERY_ORDER", new Meta("biz_delivery_order", "do_id", "delivery_status", "confirmed", Arrays.asList("pending")));
|
||||
META.put("ORDER_OBJECTION", new Meta("biz_order_objection", "objection_id", "status", "resolved", Arrays.asList("pending")));
|
||||
}
|
||||
|
||||
private Meta meta(String bizType) {
|
||||
Meta m = META.get(bizType);
|
||||
if (m == null) throw new ServiceException("不支持的业务类型: " + bizType);
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int submit(String bizType, Long id, String username) {
|
||||
Meta m = meta(bizType);
|
||||
int rows = mapper.updateStatus(m.table, m.pk, m.statusCol, id, "10", m.draftStatuses, username);
|
||||
if (rows == 0) throw new ServiceException("当前状态不允许提交审批");
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int approve(String bizType, Long id, Long userId, String username) {
|
||||
Meta m = meta(bizType);
|
||||
if (!configService.canApprove(bizType, userId)) {
|
||||
throw new ServiceException("无权审批: 当前用户不是该业务的审批人");
|
||||
}
|
||||
int rows = mapper.updateStatus(m.table, m.pk, m.statusCol, id,
|
||||
m.approvedStatus, Collections.singletonList("10"), username);
|
||||
if (rows == 0) throw new ServiceException("单据非审批中, 无法通过");
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int reject(String bizType, Long id, Long userId, String username, String reason) {
|
||||
Meta m = meta(bizType);
|
||||
if (!configService.canApprove(bizType, userId)) {
|
||||
throw new ServiceException("无权审批: 当前用户不是该业务的审批人");
|
||||
}
|
||||
int rows = mapper.updateStatus(m.table, m.pk, m.statusCol, id,
|
||||
"rejected", Collections.singletonList("10"), username);
|
||||
if (rows == 0) throw new ServiceException("单据非审批中, 无法驳回");
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.ruoyi.system.service.bid.impl;
|
||||
|
||||
import com.ruoyi.system.domain.bid.BizApprovalConfig;
|
||||
import com.ruoyi.system.mapper.bid.BizApprovalConfigMapper;
|
||||
import com.ruoyi.system.service.bid.IBizApprovalConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class BizApprovalConfigServiceImpl implements IBizApprovalConfigService {
|
||||
@Autowired private BizApprovalConfigMapper mapper;
|
||||
|
||||
@Override
|
||||
public List<BizApprovalConfig> selectList(BizApprovalConfig query) {
|
||||
List<BizApprovalConfig> list = mapper.selectList(query);
|
||||
for (BizApprovalConfig c : list) {
|
||||
c.setUserIds(mapper.selectUserIds(c.getId()));
|
||||
c.setUserNames(mapper.selectUserNames(c.getId()));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BizApprovalConfig selectById(Long id) {
|
||||
BizApprovalConfig c = mapper.selectById(id);
|
||||
if (c != null) {
|
||||
c.setUserIds(mapper.selectUserIds(id));
|
||||
c.setUserNames(mapper.selectUserNames(id));
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BizApprovalConfig selectByBizType(String bizType) {
|
||||
BizApprovalConfig c = mapper.selectByBizType(bizType);
|
||||
if (c != null) {
|
||||
c.setUserIds(mapper.selectUserIds(c.getId()));
|
||||
c.setUserNames(mapper.selectUserNames(c.getId()));
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int insert(BizApprovalConfig r) {
|
||||
int rows = mapper.insert(r);
|
||||
saveUsers(r);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int update(BizApprovalConfig r) {
|
||||
int rows = mapper.update(r);
|
||||
mapper.deleteUsers(r.getId());
|
||||
saveUsers(r);
|
||||
return rows;
|
||||
}
|
||||
|
||||
private void saveUsers(BizApprovalConfig r) {
|
||||
if (r.getUserIds() == null) return;
|
||||
int sort = 0;
|
||||
for (Long uid : r.getUserIds()) {
|
||||
if (uid != null) mapper.insertUser(r.getId(), uid, sort++);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int deleteByIds(Long[] ids) {
|
||||
int n = 0;
|
||||
for (Long id : ids) {
|
||||
mapper.deleteUsers(id);
|
||||
n += mapper.deleteById(id);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canApprove(String bizType, Long userId) {
|
||||
if (bizType == null || userId == null) return false;
|
||||
return mapper.existsUser(bizType, userId) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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="com.ruoyi.system.mapper.bid.BizApprovalActionMapper">
|
||||
|
||||
<update id="updateStatus">
|
||||
UPDATE ${table}
|
||||
SET ${statusCol} = #{newStatus},
|
||||
update_by = #{updateBy},
|
||||
update_time = NOW()
|
||||
WHERE ${pk} = #{id}
|
||||
<if test="fromStatuses != null and !fromStatuses.isEmpty()">
|
||||
AND ${statusCol} IN
|
||||
<foreach collection="fromStatuses" item="s" open="(" separator="," close=")">#{s}</foreach>
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<select id="selectStatus" resultType="java.lang.String">
|
||||
SELECT ${statusCol} FROM ${table} WHERE ${pk}=#{id}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,77 @@
|
||||
<?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="com.ruoyi.system.mapper.bid.BizApprovalConfigMapper">
|
||||
|
||||
<resultMap id="BaseRM" type="com.ruoyi.system.domain.bid.BizApprovalConfig">
|
||||
<id property="id" column="id"/>
|
||||
<result property="bizType" column="biz_type"/>
|
||||
<result property="bizName" column="biz_name"/>
|
||||
<result property="signType" column="sign_type"/>
|
||||
<result property="enabled" column="enabled"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectList" resultMap="BaseRM">
|
||||
SELECT * FROM biz_approval_config
|
||||
<where>
|
||||
<if test="bizType != null and bizType != ''"> AND biz_type LIKE CONCAT('%',#{bizType},'%')</if>
|
||||
<if test="bizName != null and bizName != ''"> AND biz_name LIKE CONCAT('%',#{bizName},'%')</if>
|
||||
<if test="enabled != null and enabled != ''"> AND enabled=#{enabled}</if>
|
||||
</where>
|
||||
ORDER BY id ASC
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultMap="BaseRM">
|
||||
SELECT * FROM biz_approval_config WHERE id=#{id}
|
||||
</select>
|
||||
|
||||
<select id="selectByBizType" resultMap="BaseRM">
|
||||
SELECT * FROM biz_approval_config WHERE biz_type=#{bizType}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO biz_approval_config(biz_type,biz_name,sign_type,enabled,remark,create_by,create_time)
|
||||
VALUES(#{bizType},#{bizName},#{signType},#{enabled},#{remark},#{createBy},NOW())
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE biz_approval_config
|
||||
<set>
|
||||
<if test="bizName != null">biz_name=#{bizName},</if>
|
||||
<if test="signType != null">sign_type=#{signType},</if>
|
||||
<if test="enabled != null">enabled=#{enabled},</if>
|
||||
<if test="remark != null">remark=#{remark},</if>
|
||||
update_by=#{updateBy}, update_time=NOW()
|
||||
</set>
|
||||
WHERE id=#{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">DELETE FROM biz_approval_config WHERE id=#{id}</delete>
|
||||
|
||||
<select id="selectUserIds" resultType="java.lang.Long">
|
||||
SELECT user_id FROM biz_approval_config_user WHERE config_id=#{configId} ORDER BY sort_no, id
|
||||
</select>
|
||||
|
||||
<select id="selectUserNames" resultType="java.lang.String">
|
||||
SELECT u.nick_name FROM biz_approval_config_user cu
|
||||
JOIN sys_user u ON u.user_id=cu.user_id
|
||||
WHERE cu.config_id=#{configId} ORDER BY cu.sort_no, cu.id
|
||||
</select>
|
||||
|
||||
<delete id="deleteUsers">DELETE FROM biz_approval_config_user WHERE config_id=#{configId}</delete>
|
||||
|
||||
<insert id="insertUser">
|
||||
INSERT INTO biz_approval_config_user(config_id,user_id,sort_no)
|
||||
VALUES(#{configId},#{userId},#{sortNo})
|
||||
</insert>
|
||||
|
||||
<select id="existsUser" resultType="int">
|
||||
SELECT COUNT(1) FROM biz_approval_config c
|
||||
JOIN biz_approval_config_user cu ON cu.config_id=c.id
|
||||
WHERE c.biz_type=#{bizType} AND c.enabled='1' AND cu.user_id=#{userId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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="com.ruoyi.system.mapper.bid.BizApprovalPendingMapper">
|
||||
|
||||
<select id="selectPendingForUser" resultType="java.util.LinkedHashMap">
|
||||
SELECT * FROM (
|
||||
SELECT 'PURCHASE_ORDER' AS bizType, '采购订单' AS bizName,
|
||||
po_id AS id, po_no AS bizNo,
|
||||
CAST(total_amount AS CHAR) AS amount,
|
||||
create_by AS createBy, create_time AS createTime
|
||||
FROM biz_purchase_order
|
||||
WHERE status = '10'
|
||||
AND EXISTS (SELECT 1 FROM biz_approval_config c
|
||||
JOIN biz_approval_config_user cu ON cu.config_id = c.id
|
||||
WHERE c.biz_type = 'PURCHASE_ORDER' AND c.enabled = '1' AND cu.user_id = #{userId})
|
||||
|
||||
UNION ALL
|
||||
SELECT 'CLIENT_QUOTE', '客户报价',
|
||||
quote_id, quote_no, CAST(total_amount AS CHAR),
|
||||
create_by, create_time
|
||||
FROM biz_client_quote
|
||||
WHERE status = '10'
|
||||
AND EXISTS (SELECT 1 FROM biz_approval_config c
|
||||
JOIN biz_approval_config_user cu ON cu.config_id = c.id
|
||||
WHERE c.biz_type = 'CLIENT_QUOTE' AND c.enabled = '1' AND cu.user_id = #{userId})
|
||||
|
||||
UNION ALL
|
||||
SELECT 'QUOTATION', '供应商报价',
|
||||
quotation_id, quote_no, CAST(total_amount AS CHAR),
|
||||
create_by, create_time
|
||||
FROM biz_quotation
|
||||
WHERE status = '10'
|
||||
AND EXISTS (SELECT 1 FROM biz_approval_config c
|
||||
JOIN biz_approval_config_user cu ON cu.config_id = c.id
|
||||
WHERE c.biz_type = 'QUOTATION' AND c.enabled = '1' AND cu.user_id = #{userId})
|
||||
|
||||
UNION ALL
|
||||
SELECT 'DELIVERY_ORDER', '发货单',
|
||||
do_id, do_no, CAST(total_amount AS CHAR),
|
||||
create_by, create_time
|
||||
FROM biz_delivery_order
|
||||
WHERE delivery_status = '10'
|
||||
AND EXISTS (SELECT 1 FROM biz_approval_config c
|
||||
JOIN biz_approval_config_user cu ON cu.config_id = c.id
|
||||
WHERE c.biz_type = 'DELIVERY_ORDER' AND c.enabled = '1' AND cu.user_id = #{userId})
|
||||
|
||||
UNION ALL
|
||||
SELECT 'ORDER_OBJECTION', '订单异议',
|
||||
objection_id, CAST(objection_id AS CHAR), NULL,
|
||||
create_by, create_time
|
||||
FROM biz_order_objection
|
||||
WHERE status = '10'
|
||||
AND EXISTS (SELECT 1 FROM biz_approval_config c
|
||||
JOIN biz_approval_config_user cu ON cu.config_id = c.id
|
||||
WHERE c.biz_type = 'ORDER_OBJECTION' AND c.enabled = '1' AND cu.user_id = #{userId})
|
||||
) t
|
||||
ORDER BY t.createTime DESC
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user