feat(mes): 设计项目分页返回待审核文件数 pendingAuditCount,文件审核完成后才可提交审批

- MesPdProjectRespVO 新增 pendingAuditCount(未审核文件数)
- 列表/详情/导出统一批量填充,避免前端 N+1 查询
- MesPdDocumentMapper/AuditMapper 新增批量查询方法
- 统计口径与 submitProjectApproval 校验(仅 auditStatus=0)一致

Co-Authored-By: Claude <noreply@anthropic.com>
已修改8个文件
85 ■■■■■ 文件已修改
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/document/MesPdDocumentMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/documentaudit/MesPdDocumentAuditMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java
@@ -160,12 +160,16 @@
        // 1.1 批量获取关联数据(主设计师 + 审核人)
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(list,
                project -> Stream.of(project.getChiefDesigner(), project.getReviewer())));
        // 1.2 批量获取各项目待审核文件数
        Map<Long, Integer> pendingAuditCountMap = projectService.getPendingAuditCountMap(
                convertSet(list, MesPdProjectDO::getId));
        // 2. 拼接 VO
        return BeanUtils.toBean(list, MesPdProjectRespVO.class, vo -> {
            MapUtils.findAndThen(userMap, vo.getChiefDesigner(),
                    user -> vo.setChiefDesignerNickname(user.getNickname()));
            MapUtils.findAndThen(userMap, vo.getReviewer(),
                    user -> vo.setReviewerNickname(user.getNickname()));
            vo.setPendingAuditCount(pendingAuditCountMap.getOrDefault(vo.getId(), 0));
        });
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java
@@ -69,6 +69,9 @@
    @Schema(description = "BPM 流程实例编号")
    private String processInstanceId;
    @Schema(description = "待审核文件数", example = "3")
    private Integer pendingAuditCount;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("创建时间")
    private LocalDateTime createTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/document/MesPdDocumentMapper.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.dal.mysql.pd.document;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -7,6 +8,8 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Mapper
@@ -24,6 +27,13 @@
        return selectList(MesPdDocumentDO::getProjectId, projectId);
    }
    default List<MesPdDocumentDO> selectListByProjectIds(Collection<Long> projectIds) {
        if (CollUtil.isEmpty(projectIds)) {
            return Collections.emptyList();
        }
        return selectList(MesPdDocumentDO::getProjectId, projectIds);
    }
    default int deleteByProjectId(Long projectId) {
        return delete(MesPdDocumentDO::getProjectId, projectId);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/documentaudit/MesPdDocumentAuditMapper.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.dal.mysql.pd.documentaudit;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -8,6 +9,7 @@
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@Mapper
@@ -54,4 +56,19 @@
                .eq(MesPdDocumentAuditDO::getAuditStatus, 0)) > 0;
    }
    /**
     * 批量查询指定资料ID列表中所有待审核的记录
     *
     * @param documentIds 资料ID列表
     * @return 待审核记录列表
     */
    default List<MesPdDocumentAuditDO> selectPendingAuditListByDocumentIds(Collection<Long> documentIds) {
        if (CollUtil.isEmpty(documentIds)) {
            return Collections.emptyList();
        }
        return selectList(new LambdaQueryWrapperX<MesPdDocumentAuditDO>()
                .in(MesPdDocumentAuditDO::getDocumentId, documentIds)
                .eq(MesPdDocumentAuditDO::getAuditStatus, 0));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentService.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
@@ -62,6 +63,14 @@
    List<MesPdDocumentDO> getDocumentListByProjectId(Long projectId);
    /**
     * 批量获得指定多个设计项目下的资料列表
     *
     * @param projectIds 设计项目ID集合
     * @return 资料列表
     */
    List<MesPdDocumentDO> getDocumentListByProjectIds(Collection<Long> projectIds);
    /**
     * 删除指定设计项目下的所有资料
     *
     * @param projectId 设计项目ID
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java
@@ -18,6 +18,7 @@
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -109,6 +110,11 @@
    }
    @Override
    public List<MesPdDocumentDO> getDocumentListByProjectIds(Collection<Long> projectIds) {
        return documentMapper.selectListByProjectIds(projectIds);
    }
    @Override
    public void deleteByProjectId(Long projectId) {
        List<MesPdDocumentDO> documents = documentMapper.selectListByProjectId(projectId);
        for (MesPdDocumentDO document : documents) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java
@@ -125,4 +125,14 @@
    List<MesPdProjectDO> getProjectByProductCode(String productCode);
    /**
     * 批量统计各设计项目的待审核文件数
     * <p>
     * 统计口径:该项目下资料对应的审核记录中,存在 auditStatus=0(待审核)的记录数。
     *
     * @param projectIds 设计项目ID集合
     * @return 项目ID -> 待审核文件数
     */
    Map<Long, Integer> getPendingAuditCountMap(Collection<Long> projectIds);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java
@@ -343,6 +343,32 @@
        return projectMapper.selectList(new QueryWrapper<MesPdProjectDO>().lambda().eq(MesPdProjectDO::getProductCode,productCode).eq(MesPdProjectDO::getStatus, MesPdProjectStatusEnum.ARCHIVE.getStatus()));
    }
    @Override
    public Map<Long, Integer> getPendingAuditCountMap(Collection<Long> projectIds) {
        if (CollUtil.isEmpty(projectIds)) {
            return Collections.emptyMap();
        }
        // 1. 批量查出这些项目下的所有资料
        List<MesPdDocumentDO> documents = documentService.getDocumentListByProjectIds(projectIds);
        if (CollUtil.isEmpty(documents)) {
            return Collections.emptyMap();
        }
        // 2. 资料ID -> 项目ID
        Map<Long, Long> documentProjectIdMap = convertMap(documents, MesPdDocumentDO::getId, MesPdDocumentDO::getProjectId);
        // 3. 批量查出所有待审核记录(auditStatus=0)
        List<MesPdDocumentAuditDO> pendingAudits =
                documentAuditMapper.selectPendingAuditListByDocumentIds(documentProjectIdMap.keySet());
        // 4. 按项目统计待审核文件数
        Map<Long, Integer> result = new HashMap<>();
        for (MesPdDocumentAuditDO audit : pendingAudits) {
            Long projectId = documentProjectIdMap.get(audit.getDocumentId());
            if (projectId != null) {
                result.merge(projectId, 1, Integer::sum);
            }
        }
        return result;
    }
    private void validateProjectInitiate(MesPdProjectDO project) {
        if (!MesPdProjectStatusEnum.INITIATE.getStatus().equals(project.getStatus())) {
            throw exception(PD_PROJECT_NOT_INITIATE);