云
2026-09-29 f87ce86307056f63f79312178037cc8714eb2b13
yudao-module-research/src/main/java/cn/iocoder/yudao/module/research/service/application/ResearchApplicationServiceImpl.java
@@ -7,6 +7,9 @@
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.archive.api.document.ArchiveDocumentApi;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentCreateReqDTO;
import cn.iocoder.yudao.module.archive.api.document.dto.ArchiveDocumentRespDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
@@ -24,6 +27,7 @@
import cn.iocoder.yudao.module.research.enums.ResearchGuidelineStatusEnum;
import cn.iocoder.yudao.module.research.service.guideline.ResearchGuidelineService;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.notice.vo.NoticeSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.notice.NoticeDO;
import cn.iocoder.yudao.module.system.service.notice.NoticeService;
@@ -40,6 +44,7 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -54,6 +59,15 @@
    private static final String APPLICATION_APPROVE_CATEGORY_CODE = "research_topic_approve";
    public static final String APPLICATION_RECORD_TYPE = "research_application";
    /** 归档来源类型:2-课题(对应 archive 模块 source_type) */
    private static final Integer ARCHIVE_SOURCE_TYPE_RESEARCH = 2;
    /** 归档密级:2-内部 */
    private static final Integer ARCHIVE_SECRET_LEVEL_INTERNAL = 2;
    /** 课题档案分类编码(对应 archive_category.code) */
    private static final String ARCHIVE_CATEGORY_CODE_RESEARCH = "research";
    /** 立项通知使用「公告」类型(system_notice_type: 1-通知 2-公告) */
    private static final Integer NOTICE_TYPE_ANNOUNCEMENT = 2;
@@ -62,6 +76,9 @@
    @Resource
    private ResearchApplicationMapper applicationMapper;
    @Resource
    private ArchiveDocumentApi archiveDocumentApi;
    @Resource
    private ResearchNoRedisDAO noRedisDAO;
@@ -275,6 +292,42 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String archiveApplication(Long id) {
        // 1.1 校验存在
        ResearchApplicationDO application = validateApplicationExists(id);
        // 1.2 只有「已立项」的课题才能归档
        if (!ResearchApplicationStatusEnum.APPROVED.getStatus().equals(application.getStatus())) {
            throw exception(APPLICATION_ARCHIVE_FAIL_STATUS,
                    application.getNo(), ResearchApplicationStatusEnum.nameOf(application.getStatus()));
        }
        // 1.3 重复归档校验
        if (application.getArchiveDocumentId() != null) {
            throw exception(APPLICATION_ALREADY_ARCHIVED, application.getNo(),
                    StrUtil.blankToDefault(archiveNo(id), "未知"));
        }
        // 2. 带入申报书附件,按来源单据归档(来源单号取立项编号,未生成时回退申报编号)
        String sourceNo = StrUtil.blankToDefault(application.getResearchNo(), application.getNo());
        List<Long> blobIds = storageAttachmentApi.listAttachments(APPLICATION_RECORD_TYPE, id).stream()
                .map(StorageBlobRespDTO::getId).collect(Collectors.toList());
        Long archiveDocumentId = archiveDocumentApi.archiveBySource(new ArchiveDocumentCreateReqDTO()
                .setSourceType(ARCHIVE_SOURCE_TYPE_RESEARCH)
                .setSourceId(id)
                .setSourceNo(sourceNo)
                .setSourceTitle(application.getTitle())
                .setCategoryCode(ARCHIVE_CATEGORY_CODE_RESEARCH)
                .setSecretLevel(ARCHIVE_SECRET_LEVEL_INTERNAL)
                .setSummary("课题申报书归档")
                .setBlobIds(blobIds)).getCheckedData();
        // 3. 回写归档档案 ID(作为已归档硬标记)
        applicationMapper.updateById(new ResearchApplicationDO()
                .setId(id).setArchiveDocumentId(archiveDocumentId));
        return StrUtil.blankToDefault(archiveNo(id), "未知");
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateReviewSummary(Long id, BigDecimal reviewAvgScore, Integer reviewPass) {
        // 用 UpdateWrapper 显式 set,保证「评审明细被删空」时能把汇总值清成 null
        applicationMapper.update(null, new LambdaUpdateWrapper<ResearchApplicationDO>()
@@ -293,6 +346,13 @@
        return application;
    }
    /** 查询来源单据已生成的档案编号(未归档时返回 null) */
    private String archiveNo(Long applicationId) {
        ArchiveDocumentRespDTO archive = archiveDocumentApi
                .getBySource(ARCHIVE_SOURCE_TYPE_RESEARCH, applicationId).getCheckedData();
        return archive != null ? archive.getNo() : null;
    }
    /** 保存(草稿)阶段校验指南:必须存在且已发布 */
    private ResearchGuidelineDO validateGuidelineForSave(Long guidelineId) {
        ResearchGuidelineDO guideline = guidelineService.getGuideline(guidelineId);