feat(approval): PC 单据查询分页与数据权限 (#25)

This commit is contained in:
2026-10-30 20:50:53 +08:00
parent bd9049c807
commit da5b5cfde7
7 changed files with 609 additions and 1 deletions
@@ -34,7 +34,7 @@ public class ApprovalDirectoryController extends BaseController {
return success(directoryService.listOrganizations());
}
@PreAuthorize("hasAuthority('approval:instance:assign')")
@PreAuthorize("hasAnyAuthority('approval:instance:list', 'approval:instance:assign')")
@ApiOperation("启用用户搜索")
@GetMapping("/users")
public ApiResult<List<ApprovalUserVO>> users(
@@ -0,0 +1,35 @@
package com.gxwebsoft.approval.controller;
import com.gxwebsoft.approval.param.ApprovalQueryParam;
import com.gxwebsoft.approval.service.ApprovalQueryService;
import com.gxwebsoft.approval.vo.ApprovalItemVO;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.PageResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* PC 单据查询接口。
*/
@Api(tags = "审批单据查询")
@RestController
@RequestMapping("/api/approval")
public class ApprovalQueryController extends BaseController {
@Resource
private ApprovalQueryService queryService;
@PreAuthorize("hasAuthority('approval:instance:list')")
@ApiOperation("PC 单据查询分页")
@GetMapping("/instance/page")
public ApiResult<PageResult<ApprovalItemVO>> page(ApprovalQueryParam param) {
return success(queryService.page(param, getLoginUser()));
}
}
@@ -0,0 +1,22 @@
package com.gxwebsoft.approval.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.approval.param.ApprovalQueryParam;
import com.gxwebsoft.approval.support.ApprovalItemRow;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* PC 单据查询Mapper:两类单据联表单,数据权限条件与详情一致。
*/
public interface ApprovalQueryMapper {
/**
* 单据查询分页,默认提交时间倒序、同秒 id 倒序。
*/
List<ApprovalItemRow> selectInstancePage(@Param("page") IPage<ApprovalItemRow> page,
@Param("param") ApprovalQueryParam param,
@Param("userId") Integer userId,
@Param("allVisible") Integer allVisible);
}
@@ -0,0 +1,71 @@
<?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.gxwebsoft.approval.mapper.ApprovalQueryMapper">
<!-- PC 单据查询:数据权限条件与详情保持一致(草稿仅发起人可见) -->
<select id="selectInstancePage" resultType="com.gxwebsoft.approval.support.ApprovalItemRow">
SELECT i.id,
i.biz_no,
i.doc_type,
i.status,
i.reporter_id,
i.reporter_name,
i.report_org_name,
i.accept_org_name,
i.archive_time,
i.current_node_key,
i.definition_id,
i.submit_time,
COALESCE(f.name, co.title) AS title
FROM approval_instance i
LEFT JOIN approval_completion_form f ON f.instance_id = i.id
LEFT JOIN approval_coordination_form co ON co.instance_id = i.id
WHERE (
i.reporter_id = #{userId}
OR (i.status != 'DRAFT' AND (
#{allVisible} = 1
OR EXISTS (SELECT 1 FROM approval_task t WHERE t.instance_id = i.id AND t.assignee_id = #{userId})
OR EXISTS (SELECT 1 FROM approval_record r WHERE r.instance_id = i.id AND r.operator_id = #{userId})
OR i.dispatch_target_id = #{userId}
))
)
<if test="param.docType != null and param.docType != ''">
AND i.doc_type = #{param.docType}
</if>
<if test="param.bizNo != null and param.bizNo != ''">
AND i.biz_no LIKE CONCAT('%', #{param.bizNo}, '%')
</if>
<if test="param.reportOrgId != null">
AND i.report_org_id = #{param.reportOrgId}
</if>
<if test="param.acceptOrgId != null">
AND i.accept_org_id = #{param.acceptOrgId}
</if>
<if test="param.reporterId != null">
AND i.reporter_id = #{param.reporterId}
</if>
<if test="param.status != null and param.status != ''">
AND i.status = #{param.status}
</if>
<if test="param.keyword != null and param.keyword != ''">
AND (i.biz_no LIKE CONCAT('%', #{param.keyword}, '%')
OR f.name LIKE CONCAT('%', #{param.keyword}, '%')
OR co.title LIKE CONCAT('%', #{param.keyword}, '%')
OR f.implement_org LIKE CONCAT('%', #{param.keyword}, '%'))
</if>
<if test="param.submitTimeStart != null and param.submitTimeStart != ''">
AND i.submit_time &gt;= #{param.submitTimeStart}
</if>
<if test="param.submitTimeEnd != null and param.submitTimeEnd != ''">
AND i.submit_time &lt;= #{param.submitTimeEnd}
</if>
<if test="param.archiveTimeStart != null and param.archiveTimeStart != ''">
AND i.archive_time &gt;= #{param.archiveTimeStart}
</if>
<if test="param.archiveTimeEnd != null and param.archiveTimeEnd != ''">
AND i.archive_time &lt;= #{param.archiveTimeEnd}
</if>
ORDER BY (i.submit_time IS NULL) ASC, i.submit_time DESC, i.id DESC
</select>
</mapper>
@@ -0,0 +1,50 @@
package com.gxwebsoft.approval.param;
import com.gxwebsoft.common.core.web.BaseParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* PC 单据查询参数:两类单据统一检索,含归档单据。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel(description = "审批单据查询参数")
public class ApprovalQueryParam extends BaseParam {
private static final long serialVersionUID = 1L;
@ApiModelProperty("单据类型 completion/coordination")
private String docType;
@ApiModelProperty("单号")
private String bizNo;
@ApiModelProperty("关键词:单号、名称或标题、实施单位")
private String keyword;
@ApiModelProperty("报送部门id")
private Integer reportOrgId;
@ApiModelProperty("受理部门id")
private Integer acceptOrgId;
@ApiModelProperty("发起人id")
private Integer reporterId;
@ApiModelProperty("单据状态")
private String status;
@ApiModelProperty("提交时间起,yyyy-MM-dd HH:mm:ss")
private String submitTimeStart;
@ApiModelProperty("提交时间止,yyyy-MM-dd HH:mm:ss")
private String submitTimeEnd;
@ApiModelProperty("归档时间起,yyyy-MM-dd HH:mm:ss")
private String archiveTimeStart;
@ApiModelProperty("归档时间止,yyyy-MM-dd HH:mm:ss")
private String archiveTimeEnd;
}
@@ -0,0 +1,87 @@
package com.gxwebsoft.approval.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gxwebsoft.approval.constant.ApprovalConstants;
import com.gxwebsoft.approval.mapper.ApprovalQueryMapper;
import com.gxwebsoft.approval.param.ApprovalQueryParam;
import com.gxwebsoft.approval.support.ApprovalItemRow;
import com.gxwebsoft.approval.support.ApprovalNode;
import com.gxwebsoft.approval.vo.ApprovalItemVO;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* PC 单据查询服务:两类单据统一分页检索,列表与详情共用同一套数据权限条件。
*
* <p>跨库约束:allVisible 角色判定走 core 专用连接,本方法不开启事务。</p>
*/
@Service
public class ApprovalQueryService {
private static final long DEFAULT_LIMIT = 10L;
@Resource
private ApprovalQueryMapper queryMapper;
@Resource
private ApprovalFlowService flowService;
@Resource
private ApprovalDirectoryService directoryService;
/**
* 单据查询分页,默认提交时间倒序、同秒按 id 倒序。
*/
public PageResult<ApprovalItemVO> page(ApprovalQueryParam param, User user) {
long current = param.getPage() == null ? 1 : Math.max(1, param.getPage());
long size = param.getLimit() == null ? DEFAULT_LIMIT : Math.max(1, param.getLimit());
int allVisible = directoryService.hasAnyRole(user.getUserId(),
ApprovalConstants.ROLE_SUPER_ADMIN, ApprovalConstants.ROLE_ARCHIVIST) ? 1 : 0;
Page<ApprovalItemRow> page = new Page<>(current, size);
List<ApprovalItemRow> rows = queryMapper.selectInstancePage(page, param, user.getUserId(), allVisible);
Map<Long, List<ApprovalNode>> nodeCache = new HashMap<>();
List<ApprovalItemVO> list = new ArrayList<>(rows.size());
for (ApprovalItemRow row : rows) {
list.add(toItem(row, nodeCache));
}
return new PageResult<>(list, page.getTotal());
}
private ApprovalItemVO toItem(ApprovalItemRow row, Map<Long, List<ApprovalNode>> nodeCache) {
ApprovalItemVO item = new ApprovalItemVO();
item.setId(row.getId());
item.setBizNo(row.getBizNo());
item.setDocType(row.getDocType());
item.setDocTypeName(ApprovalConstants.docTypeName(row.getDocType()));
item.setStatus(row.getStatus());
item.setStatusName(ApprovalConstants.statusName(row.getStatus()));
item.setTitle(row.getTitle());
item.setSubmitTime(row.getSubmitTime());
item.setReporterId(row.getReporterId());
item.setReporterName(row.getReporterName());
item.setReportOrgName(row.getReportOrgName());
item.setAcceptOrgName(row.getAcceptOrgName());
item.setArchiveTime(row.getArchiveTime());
item.setCurrentNodeName(nodeName(nodeCache, row.getDefinitionId(), row.getCurrentNodeKey()));
return item;
}
private String nodeName(Map<Long, List<ApprovalNode>> cache, Long definitionId, String nodeKey) {
if (definitionId == null || nodeKey == null) {
return null;
}
List<ApprovalNode> nodes = cache.computeIfAbsent(definitionId, flowService::parseNodes);
return nodes.stream()
.filter(node -> nodeKey.equals(node.getKey()))
.map(ApprovalNode::getName)
.findFirst()
.orElse(null);
}
}