修复风险仪表盘总人数统计
This commit is contained in:
@@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
|
||||
@@ -235,6 +236,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
}
|
||||
|
||||
bankStatementMapper.deleteByProjectIdAndBatchId(record.getProjectId(), record.getLogId());
|
||||
refreshProjectTargetCount(record.getProjectId());
|
||||
|
||||
CcdiFileUploadRecord update = new CcdiFileUploadRecord();
|
||||
update.setId(record.getId());
|
||||
@@ -853,6 +855,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
result.setTotalCount(totalCount == null ? 0 : totalCount);
|
||||
if (totalCount == null || totalCount <= 0) {
|
||||
log.warn("【文件上传】无流水数据需要保存: totalCount={}", totalCount);
|
||||
refreshProjectTargetCount(projectId);
|
||||
result.setSuccess(true);
|
||||
return result;
|
||||
}
|
||||
@@ -922,6 +925,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
|
||||
log.info("【文件上传】流水入库完成: fetchedCount={}, attemptedCount={}",
|
||||
totalCount, totalAttempted);
|
||||
refreshProjectTargetCount(projectId);
|
||||
result.setSuccess(true);
|
||||
result.setAttemptedCount(totalAttempted);
|
||||
return result;
|
||||
@@ -938,6 +942,24 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
bankStatementMapper.deleteByProjectIdAndBatchId(projectId, logId);
|
||||
}
|
||||
|
||||
private void refreshProjectTargetCount(Long projectId) {
|
||||
CcdiProject project = projectMapper.selectById(projectId);
|
||||
if (project == null) {
|
||||
log.warn("【项目】刷新目标人数时项目不存在: projectId={}", projectId);
|
||||
return;
|
||||
}
|
||||
|
||||
QueryWrapper<CcdiBankStatement> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.select("DISTINCT TRIM(cret_no)");
|
||||
queryWrapper.eq("project_id", projectId);
|
||||
queryWrapper.isNotNull("cret_no");
|
||||
queryWrapper.apply("TRIM(cret_no) <> ''");
|
||||
|
||||
int targetCount = bankStatementMapper.selectObjs(queryWrapper).size();
|
||||
project.setTargetCount(targetCount);
|
||||
projectMapper.updateById(project);
|
||||
}
|
||||
|
||||
private void validateDeleteRecord(CcdiFileUploadRecord record) {
|
||||
if (record == null) {
|
||||
throw new RuntimeException("上传记录不存在");
|
||||
|
||||
@@ -284,6 +284,10 @@ class CcdiFileUploadServiceImplTest {
|
||||
List<String> events = new ArrayList<>();
|
||||
AtomicInteger sequence = new AtomicInteger();
|
||||
captureRecordStatus(events, sequence);
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(PROJECT_ID);
|
||||
when(projectMapper.selectById(PROJECT_ID)).thenReturn(project);
|
||||
when(bankStatementMapper.selectObjs(any())).thenReturn(List.of("110101199001018888"));
|
||||
|
||||
when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any())).thenReturn(buildUploadResponse());
|
||||
when(lsfxClient.checkParseStatus(LSFX_PROJECT_ID, String.valueOf(LOG_ID)))
|
||||
@@ -304,6 +308,9 @@ class CcdiFileUploadServiceImplTest {
|
||||
int successIndex = findEventIndex(events, "record:parsed_success");
|
||||
assertTrue(fetchIndex >= 0);
|
||||
assertTrue(successIndex > fetchIndex);
|
||||
verify(projectMapper).updateById(org.mockito.ArgumentMatchers.<CcdiProject>argThat(item ->
|
||||
PROJECT_ID.equals(item.getProjectId()) && Integer.valueOf(1).equals(item.getTargetCount())
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -362,9 +369,13 @@ class CcdiFileUploadServiceImplTest {
|
||||
record.setLsfxProjectId(LSFX_PROJECT_ID);
|
||||
record.setLogId(LOG_ID);
|
||||
record.setFileStatus("parsed_success");
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(PROJECT_ID);
|
||||
|
||||
when(recordMapper.selectById(RECORD_ID)).thenReturn(record);
|
||||
when(lsfxClient.deleteFiles(any())).thenReturn(buildDeleteFilesResponse());
|
||||
when(projectMapper.selectById(PROJECT_ID)).thenReturn(project);
|
||||
when(bankStatementMapper.selectObjs(any())).thenReturn(List.of("110101199001018888", "110101199001019999"));
|
||||
|
||||
String result = service.deleteFileUploadRecord(RECORD_ID, 9527L);
|
||||
|
||||
@@ -379,6 +390,9 @@ class CcdiFileUploadServiceImplTest {
|
||||
verify(recordMapper).updateById(org.mockito.ArgumentMatchers.<CcdiFileUploadRecord>argThat(item ->
|
||||
RECORD_ID.equals(item.getId()) && "deleted".equals(item.getFileStatus())
|
||||
));
|
||||
verify(projectMapper).updateById(org.mockito.ArgumentMatchers.<CcdiProject>argThat(item ->
|
||||
PROJECT_ID.equals(item.getProjectId()) && Integer.valueOf(2).equals(item.getTargetCount())
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -544,6 +558,23 @@ class CcdiFileUploadServiceImplTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshProjectTargetCount_shouldUpdateProjectWithDistinctIdCardCount() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(PROJECT_ID);
|
||||
when(projectMapper.selectById(PROJECT_ID)).thenReturn(project);
|
||||
when(bankStatementMapper.selectObjs(any())).thenReturn(List.of(
|
||||
"110101199001018888",
|
||||
"110101199001019999"
|
||||
));
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "refreshProjectTargetCount", PROJECT_ID);
|
||||
|
||||
verify(projectMapper).updateById(org.mockito.ArgumentMatchers.<CcdiProject>argThat(item ->
|
||||
PROJECT_ID.equals(item.getProjectId()) && Integer.valueOf(2).equals(item.getTargetCount())
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
void processFileAsync_shouldMarkParsedFailedWhenInsertBatchThrowsUnexpectedSqlError() throws IOException {
|
||||
List<String> events = new ArrayList<>();
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# 风险仪表盘总人数修复实施记录
|
||||
|
||||
**日期**: 2026-03-19
|
||||
**范围**: 后端
|
||||
|
||||
## 问题现象
|
||||
|
||||
- 风险仪表盘中的“总人数”始终显示 `0`
|
||||
- 真实数据库中多个项目已存在流水数据,但 `ccdi_project.target_count` 未被维护
|
||||
|
||||
## 根因
|
||||
|
||||
- 结果总览接口按既定口径直接读取 `ccdi_project.target_count`
|
||||
- 项目创建时将 `target_count` 初始化为 `0`
|
||||
- 后续在银行流水导入成功、删除上传记录成功后,都没有重新统计并回写 `target_count`
|
||||
|
||||
## 本次修改
|
||||
|
||||
1. 在 `CcdiFileUploadServiceImpl` 中新增项目目标人数刷新逻辑
|
||||
2. 在银行流水入库成功后,按 `ccdi_bank_statement` 中去重身份证号数量回写 `ccdi_project.target_count`
|
||||
3. 在删除上传记录并清理项目流水后,同步刷新 `ccdi_project.target_count`
|
||||
4. 新增增量脚本 `sql/migration/2026-03-19-backfill-project-target-count.sql`,用于回填历史项目的目标人数
|
||||
5. 补充单元测试,覆盖:
|
||||
- 流水入库成功后刷新目标人数
|
||||
- 删除上传记录后刷新目标人数
|
||||
- 目标人数刷新按去重身份证号统计
|
||||
|
||||
## 验证
|
||||
|
||||
- 执行:`mvn -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest test`
|
||||
- 结果:通过
|
||||
17
sql/migration/2026-03-19-backfill-project-target-count.sql
Normal file
17
sql/migration/2026-03-19-backfill-project-target-count.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- 回填项目目标人数
|
||||
-- 口径:按项目已入库流水中去重后的员工身份证号数量回写 ccdi_project.target_count
|
||||
|
||||
UPDATE ccdi_project project
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
project_id,
|
||||
COUNT(DISTINCT TRIM(cret_no)) AS target_count
|
||||
FROM ccdi_bank_statement
|
||||
WHERE cret_no IS NOT NULL
|
||||
AND TRIM(cret_no) <> ''
|
||||
GROUP BY project_id
|
||||
) stats ON stats.project_id = project.project_id
|
||||
SET project.target_count = COALESCE(stats.target_count, 0),
|
||||
project.update_by = 'system',
|
||||
project.update_time = NOW()
|
||||
WHERE project.del_flag = '0';
|
||||
Reference in New Issue
Block a user