From d6457491e83d899e168f767689da2192d3bbd10a Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Sun, 29 Mar 2026 09:56:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E9=80=9A=E5=8E=86=E5=8F=B2=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=AF=BC=E5=85=A5=E5=90=8E=E7=AB=AF=E9=97=AD=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/CcdiFileUploadServiceImpl.java | 4 +++ .../CcdiProjectHistoryImportServiceImpl.java | 20 +++++++++++++ .../impl/CcdiFileUploadServiceImplTest.java | 14 +++++++++ ...diProjectHistoryImportServiceImplTest.java | 30 +++++++++++++++++++ ...3-29-project-import-history-plan-record.md | 7 +++++ 5 files changed, 75 insertions(+) diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java index 9c670e00..c6edf39c 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java @@ -16,6 +16,7 @@ import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; import com.ruoyi.ccdi.project.service.ICcdiBankTagService; import com.ruoyi.ccdi.project.service.ICcdiFileUploadService; import com.ruoyi.ccdi.project.service.ICcdiProjectService; +import com.ruoyi.common.exception.ServiceException; import com.ruoyi.lsfx.client.LsfxAnalysisClient; import com.ruoyi.lsfx.constants.LsfxConstants; import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest; @@ -964,6 +965,9 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService { if (record == null) { throw new RuntimeException("上传记录不存在"); } + if ("HISTORY_IMPORT".equals(record.getSourceType())) { + throw new ServiceException("历史导入文件不支持删除"); + } if (!"parsed_success".equals(record.getFileStatus())) { if ("deleted".equals(record.getFileStatus())) { throw new RuntimeException("文件已删除,请勿重复操作"); diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java index 19a9bb3e..ac6eb596 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java @@ -2,11 +2,13 @@ package com.ruoyi.ccdi.project.service.impl; import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; +import com.ruoyi.ccdi.project.domain.enums.TriggerType; import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement; import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord; import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper; import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; +import com.ruoyi.ccdi.project.service.ICcdiBankTagService; import com.ruoyi.ccdi.project.service.ICcdiProjectHistoryImportService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -47,6 +49,9 @@ public class CcdiProjectHistoryImportServiceImpl implements ICcdiProjectHistoryI @Qualifier("fileUploadExecutor") private Executor fileUploadExecutor; + @Resource + private ICcdiBankTagService bankTagService; + @Override public void submitImport(Long targetProjectId, Integer targetLsfxProjectId, CcdiProjectImportHistoryDTO dto, String operator) { @@ -97,6 +102,10 @@ public class CcdiProjectHistoryImportServiceImpl implements ICcdiProjectHistoryI if (!recordsToInsert.isEmpty()) { recordMapper.insertBatch(recordsToInsert); } + if (!statementsToInsert.isEmpty()) { + refreshProjectTargetCount(targetProjectId); + bankTagService.submitAutoRebuild(targetProjectId, TriggerType.AUTO_BATCH_UPLOAD); + } } private CcdiBankStatement copyStatement(CcdiBankStatement sourceStatement, Long targetProjectId, @@ -136,6 +145,17 @@ public class CcdiProjectHistoryImportServiceImpl implements ICcdiProjectHistoryI return sourceProject == null ? null : sourceProject.getProjectName(); } + private void refreshProjectTargetCount(Long targetProjectId) { + CcdiProject targetProject = projectMapper.selectById(targetProjectId); + if (targetProject == null) { + log.warn("【项目历史导入】刷新目标人数时项目不存在: projectId={}", targetProjectId); + return; + } + int targetCount = bankStatementMapper.countMatchedStaffCountByProjectId(targetProjectId); + targetProject.setTargetCount(targetCount); + projectMapper.updateById(targetProject); + } + private void normalizeDedupFields(CcdiBankStatement statement) { statement.setLeAccountNo(trimToNull(statement.getLeAccountNo())); } diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java index b979a552..cf1d0741 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java @@ -411,6 +411,20 @@ class CcdiFileUploadServiceImplTest { assertTrue(exception.getMessage().contains("仅支持删除解析成功文件")); } + @Test + void deleteFileUploadRecord_shouldRejectHistoryImportRecord() { + CcdiFileUploadRecord record = buildRecord(); + record.setFileStatus("parsed_success"); + record.setSourceType("HISTORY_IMPORT"); + when(recordMapper.selectById(RECORD_ID)).thenReturn(record); + + ServiceException exception = assertThrows(ServiceException.class, + () -> service.deleteFileUploadRecord(RECORD_ID, 9527L)); + + assertTrue(exception.getMessage().contains("历史导入文件不支持删除")); + verify(lsfxClient, never()).deleteFiles(any()); + } + @Test void deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails() { CcdiFileUploadRecord record = buildRecord(); diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java index 2b6d9ced..8cf088d2 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java @@ -2,11 +2,13 @@ package com.ruoyi.ccdi.project.service.impl; import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; +import com.ruoyi.ccdi.project.domain.enums.TriggerType; import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement; import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord; import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper; import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; +import com.ruoyi.ccdi.project.service.ICcdiBankTagService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -23,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; @@ -46,6 +49,9 @@ class CcdiProjectHistoryImportServiceImplTest { @Mock private Executor fileUploadExecutor; + @Mock + private ICcdiBankTagService bankTagService; + @Test void shouldFilterStatementsByTrxDateAndDeduplicateAcrossSourceProjects() { CcdiProjectImportHistoryDTO dto = buildImportDto(); @@ -126,6 +132,30 @@ class CcdiProjectHistoryImportServiceImplTest { assertNotEquals(sourceRecord.getLogId(), insertedRecords.get().get(0).getLogId()); } + @Test + void shouldRefreshTargetCountAndSubmitAutoRebuildAfterImport() { + CcdiProjectImportHistoryDTO dto = buildImportDto(); + when(recordMapper.selectSuccessfulRecordsByProjectIds(dto.getSourceProjectIds())) + .thenReturn(List.of(buildSourceRecord(11L, 101, "批次A"))); + when(bankStatementMapper.selectStatementsForHistoryImport(11L, 101, "2026-01-01", "2026-01-31")) + .thenReturn(List.of(buildStatement("2026-01-12", "6444", "300.00", "备注C"))); + doAnswer(invocation -> { + Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }).when(fileUploadExecutor).execute(any(Runnable.class)); + when(projectMapper.selectById(11L)).thenReturn(buildProject(11L, "历史项目A")); + when(projectMapper.selectById(90L)).thenReturn(buildProject(90L, "新项目")); + when(bankStatementMapper.countMatchedStaffCountByProjectId(90L)).thenReturn(3); + + service.submitImport(90L, 3001, dto, "tester"); + + verify(projectMapper).updateById(org.mockito.ArgumentMatchers.argThat(project -> + Long.valueOf(90L).equals(project.getProjectId()) && Integer.valueOf(3).equals(project.getTargetCount()) + )); + verify(bankTagService).submitAutoRebuild(90L, TriggerType.AUTO_BATCH_UPLOAD); + } + private CcdiProjectImportHistoryDTO buildImportDto() { CcdiProjectImportHistoryDTO dto = new CcdiProjectImportHistoryDTO(); dto.setProjectName("新项目"); diff --git a/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md b/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md index 962e6da9..b64b384d 100644 --- a/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md +++ b/docs/reports/implementation/2026-03-29-project-import-history-plan-record.md @@ -50,3 +50,10 @@ - 将 `CcdiProjectHistoryImportServiceImpl` 扩展为真实异步复制链路:读取成功批次、按新批次号重建目标流水、按来源字段生成历史导入文件记录,并在导入前做内存去重 - 新增 `CcdiProjectHistoryImportServiceImplTest` 与 XML 断言,验证日期范围传递、跨来源批次去重、新批次号生成及来源标识写入 - 验证命令:`mvn -pl ccdi-project -am -Dtest=CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest,CcdiBankStatementMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test` + +### 2026-03-29 Task 5 后端自动打标与删除拦截收口 + +- 在 `CcdiProjectHistoryImportServiceImpl` 中补齐目标人数刷新与 `bankTagService.submitAutoRebuild(targetProjectId, TriggerType.AUTO_BATCH_UPLOAD)` 调用 +- 在 `CcdiFileUploadServiceImpl#validateDeleteRecord` 中优先拦截 `sourceType = HISTORY_IMPORT` 的记录,返回“历史导入文件不支持删除” +- 扩展历史导入服务测试与文件删除测试,锁定自动重算与禁删行为 +- 完成后端回归验证:`mvn -pl ccdi-project -am -Dtest=CcdiProjectControllerTest,CcdiProjectControllerContractTest,CcdiProjectServiceImplTest,CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest,CcdiBankStatementMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test`