实现项目打标状态联动并执行前后端适配
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.ruoyi.ccdi.project.constants;
|
||||
|
||||
/**
|
||||
* 项目状态常量
|
||||
*/
|
||||
public final class CcdiProjectStatusConstants {
|
||||
|
||||
public static final String PROCESSING = "0";
|
||||
public static final String COMPLETED = "1";
|
||||
public static final String ARCHIVED = "2";
|
||||
public static final String TAGGING = "3";
|
||||
|
||||
private CcdiProjectStatusConstants() {
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class CcdiProject implements Serializable {
|
||||
/** 配置方式:default-全局默认,custom-自定义 */
|
||||
private String configType;
|
||||
|
||||
/** 项目状态:0-进行中,1-已完成,2-已归档 */
|
||||
/** 项目状态:0-进行中,1-已完成,2-已归档,3-打标中 */
|
||||
private String status;
|
||||
|
||||
/** 是否归档:0-未归档,1-已归档 */
|
||||
|
||||
@@ -20,4 +20,7 @@ public class CcdiProjectStatusCountsVO {
|
||||
|
||||
/** 已归档项目数(状态2) */
|
||||
private Long status2;
|
||||
|
||||
/** 打标中项目数(状态3) */
|
||||
private Long status3;
|
||||
}
|
||||
|
||||
@@ -59,4 +59,28 @@ public interface ICcdiProjectService {
|
||||
* @return 状态统计
|
||||
*/
|
||||
CcdiProjectStatusCountsVO getStatusCounts();
|
||||
|
||||
/**
|
||||
* 更新项目状态
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param status 状态编码
|
||||
* @param operator 操作人
|
||||
*/
|
||||
void updateProjectStatus(Long projectId, String status, String operator);
|
||||
|
||||
/**
|
||||
* 校验项目是否允许进入打标流程
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
*/
|
||||
void ensureProjectCanStartTagging(Long projectId);
|
||||
|
||||
/**
|
||||
* 校验项目是否允许写入
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param message 拒绝文案
|
||||
*/
|
||||
void ensureProjectWritable(Long projectId, String message);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiBankTagRebuildDTO;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagResult;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagRule;
|
||||
@@ -13,6 +14,7 @@ import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagRuleMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -54,6 +56,9 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
@Resource
|
||||
private BankTagRuleConfigResolver configResolver;
|
||||
|
||||
@Resource
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
@Resource
|
||||
@Qualifier("tagRuleExecutor")
|
||||
private Executor tagRuleExecutor;
|
||||
@@ -88,6 +93,7 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
*/
|
||||
public Long rebuildProject(Long projectId, String modelCode, String operator, TriggerType triggerType) {
|
||||
long taskStartTime = System.currentTimeMillis();
|
||||
projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.TAGGING, operator);
|
||||
CcdiBankTagTask task = buildRunningTask(projectId, modelCode, operator, triggerType);
|
||||
taskMapper.insertTask(task);
|
||||
log.info("【流水标签】任务创建成功: taskId={}, projectId={}, modelCode={}, triggerType={}, operator={}",
|
||||
@@ -128,6 +134,7 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
task.setUpdateBy(operator);
|
||||
task.setUpdateTime(new Date());
|
||||
taskMapper.updateTask(task);
|
||||
projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.COMPLETED, operator);
|
||||
log.info("【流水标签】任务执行成功: taskId={}, projectId={}, modelCode={}, triggerType={}, ruleCount={}, hitCount={}, costMs={}",
|
||||
task.getId(), projectId, modelCode, triggerType, rules.size(), allResults.size(),
|
||||
System.currentTimeMillis() - taskStartTime);
|
||||
@@ -140,6 +147,7 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
task.setUpdateBy(operator);
|
||||
task.setUpdateTime(new Date());
|
||||
taskMapper.updateTask(task);
|
||||
projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.PROCESSING, operator);
|
||||
log.error("【流水标签】任务执行失败: taskId={}, projectId={}, modelCode={}, triggerType={}, error={}",
|
||||
task.getId(), projectId, modelCode, triggerType, ex.getMessage(), ex);
|
||||
throw ex;
|
||||
|
||||
@@ -15,6 +15,7 @@ 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.ICcdiFileUploadService;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||
import com.ruoyi.lsfx.constants.LsfxConstants;
|
||||
import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest;
|
||||
@@ -96,6 +97,9 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
@Resource
|
||||
private ICcdiBankTagService bankTagService;
|
||||
|
||||
@Resource
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
/**
|
||||
* 获取临时文件存储目录
|
||||
*/
|
||||
@@ -165,6 +169,8 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
throw new IllegalArgumentException("开始日期不能晚于结束日期");
|
||||
}
|
||||
|
||||
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
||||
|
||||
CcdiProject project = projectMapper.selectById(projectId);
|
||||
if (project == null) {
|
||||
throw new IllegalArgumentException("项目不存在: projectId=" + projectId);
|
||||
@@ -311,6 +317,8 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
log.info("【文件上传】开始批量上传: projectId={}, 文件数量={}, username={}",
|
||||
projectId, files.length, username);
|
||||
|
||||
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
||||
|
||||
// 1. 生成批次ID
|
||||
String batchId = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.ruoyi.ccdi.project.domain.vo.ModelGroupVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiModelParamMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiModelParamService;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -46,6 +47,9 @@ public class CcdiModelParamServiceImpl implements ICcdiModelParamService {
|
||||
@Resource
|
||||
private CcdiProjectMapper projectMapper;
|
||||
|
||||
@Resource
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
@Override
|
||||
public List<ModelListVO> selectModelList(Long projectId) {
|
||||
log.info("selectModelList 被调用,projectId={}", projectId);
|
||||
@@ -102,6 +106,7 @@ public class CcdiModelParamServiceImpl implements ICcdiModelParamService {
|
||||
Long projectId = saveDTO.getProjectId();
|
||||
|
||||
if (projectId > 0) {
|
||||
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
||||
switchToCustomConfigIfNeeded(getRequiredProject(projectId));
|
||||
}
|
||||
|
||||
@@ -178,6 +183,7 @@ public class CcdiModelParamServiceImpl implements ICcdiModelParamService {
|
||||
Long projectId = saveAllDTO.getProjectId();
|
||||
|
||||
if (projectId > 0) {
|
||||
projectService.ensureProjectWritable(projectId, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
||||
switchToCustomConfigIfNeeded(getRequiredProject(projectId));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
||||
@@ -18,6 +19,8 @@ import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 项目Service实现类
|
||||
*
|
||||
@@ -43,7 +46,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
BeanUtils.copyProperties(dto, project);
|
||||
|
||||
// 3. 设置默认值和流水分析平台ID
|
||||
project.setStatus("0"); // 进行中
|
||||
project.setStatus(CcdiProjectStatusConstants.PROCESSING);
|
||||
project.setIsArchived(0); // 未归档
|
||||
project.setTargetCount(0);
|
||||
project.setHighRiskCount(0);
|
||||
@@ -115,27 +118,70 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
// 统计进行中项目(状态0)
|
||||
Long status0Count = projectMapper.selectCount(
|
||||
new LambdaQueryWrapper<CcdiProject>()
|
||||
.eq(CcdiProject::getStatus, "0")
|
||||
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.PROCESSING)
|
||||
);
|
||||
vo.setStatus0(status0Count);
|
||||
|
||||
// 统计已完成项目(状态1)
|
||||
Long status1Count = projectMapper.selectCount(
|
||||
new LambdaQueryWrapper<CcdiProject>()
|
||||
.eq(CcdiProject::getStatus, "1")
|
||||
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.COMPLETED)
|
||||
);
|
||||
vo.setStatus1(status1Count);
|
||||
|
||||
// 统计已归档项目(状态2)
|
||||
Long status2Count = projectMapper.selectCount(
|
||||
new LambdaQueryWrapper<CcdiProject>()
|
||||
.eq(CcdiProject::getStatus, "2")
|
||||
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.ARCHIVED)
|
||||
);
|
||||
vo.setStatus2(status2Count);
|
||||
|
||||
Long status3Count = projectMapper.selectCount(
|
||||
new LambdaQueryWrapper<CcdiProject>()
|
||||
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAGGING)
|
||||
);
|
||||
vo.setStatus3(status3Count);
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProjectStatus(Long projectId, String status, String operator) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())
|
||||
&& !CcdiProjectStatusConstants.ARCHIVED.equals(status)) {
|
||||
throw new ServiceException("已归档项目不允许重新进入打标流程");
|
||||
}
|
||||
project.setStatus(status);
|
||||
project.setUpdateBy(operator);
|
||||
project.setUpdateTime(new Date());
|
||||
projectMapper.updateById(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ensureProjectCanStartTagging(Long projectId) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
|
||||
throw new ServiceException("已归档项目不允许重新进入打标流程");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ensureProjectWritable(Long projectId, String message) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
if (CcdiProjectStatusConstants.TAGGING.equals(project.getStatus())) {
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private CcdiProject getRequiredProject(Long projectId) {
|
||||
CcdiProject project = projectMapper.selectById(projectId);
|
||||
if (project == null) {
|
||||
throw new ServiceException("项目不存在");
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用流水分析平台获取projectId
|
||||
*
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.project.service.impl;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask;
|
||||
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -27,6 +28,9 @@ public class ProjectBankTagRebuildCoordinator {
|
||||
@Resource
|
||||
private CcdiBankTagServiceImpl bankTagService;
|
||||
|
||||
@Resource
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
/**
|
||||
* 提交手动重算
|
||||
*
|
||||
@@ -43,6 +47,7 @@ public class ProjectBankTagRebuildCoordinator {
|
||||
throw new ServiceException("当前项目标签正在重算中,请稍后再试");
|
||||
}
|
||||
|
||||
projectService.ensureProjectCanStartTagging(projectId);
|
||||
executeWithLock(projectId, () -> bankTagService.rebuildProject(projectId, modelCode, operator, TriggerType.MANUAL));
|
||||
}
|
||||
|
||||
@@ -62,6 +67,7 @@ public class ProjectBankTagRebuildCoordinator {
|
||||
}
|
||||
|
||||
executeWithLock(projectId, () -> {
|
||||
projectService.ensureProjectCanStartTagging(projectId);
|
||||
boolean needRerun;
|
||||
do {
|
||||
Long taskId = bankTagService.rebuildProject(projectId, null, "system", triggerType);
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.ruoyi.ccdi.project.mapper.CcdiBankTagAnalysisMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagRuleMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InOrder;
|
||||
@@ -56,6 +57,9 @@ class CcdiBankTagServiceImplTest {
|
||||
@Mock
|
||||
private BankTagRuleConfigResolver configResolver;
|
||||
|
||||
@Mock
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
@Test
|
||||
void rebuildProject_shouldDeleteOldResultsBeforeSubmittingRuleTasks() {
|
||||
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
|
||||
@@ -218,6 +222,42 @@ class CcdiBankTagServiceImplTest {
|
||||
verify(taskMapper).updateTask(argThat(task -> "SUCCESS".equals(task.getStatus()) && task.getFailedRuleCount() == 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMarkProjectTaggingBeforeExecutingAndCompletedAfterSuccess() {
|
||||
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
|
||||
|
||||
CcdiBankTagRule rule = buildRule("LARGE_TRANSACTION", "大额交易",
|
||||
"HOUSE_OR_CAR_EXPENSE", "房车消费支出交易", "STATEMENT");
|
||||
BankTagRuleExecutionConfig config = buildConfig(40L, rule);
|
||||
|
||||
when(ruleMapper.selectEnabledRules(null)).thenReturn(List.of(rule));
|
||||
when(configResolver.resolve(40L, rule)).thenReturn(config);
|
||||
when(analysisMapper.selectHouseOrCarExpenseStatements(40L)).thenReturn(List.of());
|
||||
|
||||
service.rebuildProject(40L, null, "tester", TriggerType.MANUAL);
|
||||
|
||||
InOrder inOrder = inOrder(projectService, taskMapper);
|
||||
inOrder.verify(projectService).updateProjectStatus(40L, "3", "tester");
|
||||
inOrder.verify(taskMapper).updateTask(argThat(task -> "SUCCESS".equals(task.getStatus())));
|
||||
inOrder.verify(projectService).updateProjectStatus(40L, "1", "tester");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRollbackProjectStatusToProcessingWhenRebuildFails() {
|
||||
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
|
||||
|
||||
CcdiBankTagRule rule = buildRule("LARGE_TRANSACTION", "大额交易",
|
||||
"HOUSE_OR_CAR_EXPENSE", "房车消费支出交易", "STATEMENT");
|
||||
|
||||
when(ruleMapper.selectEnabledRules(null)).thenReturn(List.of(rule));
|
||||
when(configResolver.resolve(40L, rule)).thenThrow(new RuntimeException("threshold missing"));
|
||||
|
||||
assertThrows(RuntimeException.class,
|
||||
() -> service.rebuildProject(40L, null, "tester", TriggerType.MANUAL));
|
||||
|
||||
verify(projectService).updateProjectStatus(40L, "0", "tester");
|
||||
}
|
||||
|
||||
private CcdiBankTagRule buildRule(String modelCode, String modelName, String ruleCode, String ruleName, String resultType) {
|
||||
CcdiBankTagRule rule = new CcdiBankTagRule();
|
||||
rule.setModelCode(modelCode);
|
||||
|
||||
@@ -12,6 +12,8 @@ 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.ICcdiProjectService;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
||||
import com.ruoyi.lsfx.domain.response.CheckParseStatusResponse;
|
||||
@@ -89,6 +91,9 @@ class CcdiFileUploadServiceImplTest {
|
||||
@Mock
|
||||
private ICcdiBankTagService bankTagService;
|
||||
|
||||
@Mock
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@@ -154,6 +159,38 @@ class CcdiFileUploadServiceImplTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectPullBankInfoWhenProjectIsTagging() {
|
||||
org.mockito.Mockito.doThrow(new ServiceException("当前项目正在进行银行流水打标,暂不允许上传或拉取数据"))
|
||||
.when(projectService).ensureProjectWritable(PROJECT_ID, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
||||
|
||||
assertThrows(ServiceException.class,
|
||||
() -> service.submitPullBankInfo(
|
||||
PROJECT_ID,
|
||||
List.of("3301"),
|
||||
"2026-01-01",
|
||||
"2026-01-31",
|
||||
1L,
|
||||
"tester"
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectBatchUploadWhenProjectIsTagging() {
|
||||
org.mockito.Mockito.doThrow(new ServiceException("当前项目正在进行银行流水打标,暂不允许上传或拉取数据"))
|
||||
.when(projectService).ensureProjectWritable(PROJECT_ID, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据");
|
||||
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"files",
|
||||
"test.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"content".getBytes()
|
||||
);
|
||||
|
||||
assertThrows(ServiceException.class,
|
||||
() -> service.batchUploadFiles(PROJECT_ID, new MultipartFile[]{file}, "tester"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void submitTasksAsync_shouldNotCreateLocalBatchLogFiles() throws Exception {
|
||||
setField("uploadPath", tempDir.toString());
|
||||
|
||||
@@ -2,10 +2,15 @@ package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
import com.ruoyi.ccdi.project.domain.CcdiModelParam;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.dto.ModelParamGroupDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveAllDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.ParamValueItem;
|
||||
import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiModelParamMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -18,6 +23,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -37,6 +43,9 @@ class CcdiModelParamServiceImplTest {
|
||||
@Mock
|
||||
private CcdiProjectMapper projectMapper;
|
||||
|
||||
@Mock
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
@Test
|
||||
void selectAllParams_shouldReadSystemDefaultsForDefaultProject() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
@@ -95,6 +104,30 @@ class CcdiModelParamServiceImplTest {
|
||||
verify(modelParamMapper).updateParamValue(123L, "LARGE_TRANSACTION", "SINGLE_TRANSACTION_AMOUNT", "2222", "admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSaveAllParamsWhenProjectIsTagging() {
|
||||
org.mockito.Mockito.doThrow(new ServiceException("当前项目正在进行银行流水打标,暂不允许修改参数"))
|
||||
.when(projectService).ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.saveAllParams(buildSaveAllDto()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSaveParamsWhenProjectIsTagging() {
|
||||
ModelParamSaveDTO saveDTO = new ModelParamSaveDTO();
|
||||
saveDTO.setProjectId(40L);
|
||||
saveDTO.setModelCode("LARGE_TRANSACTION");
|
||||
ModelParamSaveDTO.ParamValueItem item = new ModelParamSaveDTO.ParamValueItem();
|
||||
item.setParamCode("SINGLE_TRANSACTION_AMOUNT");
|
||||
item.setParamValue("2000");
|
||||
saveDTO.setParams(List.of(item));
|
||||
|
||||
org.mockito.Mockito.doThrow(new ServiceException("当前项目正在进行银行流水打标,暂不允许修改参数"))
|
||||
.when(projectService).ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数");
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.saveParams(saveDTO));
|
||||
}
|
||||
|
||||
private CcdiModelParam buildParam(
|
||||
Long id,
|
||||
Long projectId,
|
||||
@@ -114,4 +147,19 @@ class CcdiModelParamServiceImplTest {
|
||||
param.setSortOrder(1);
|
||||
return param;
|
||||
}
|
||||
|
||||
private ModelParamSaveAllDTO buildSaveAllDto() {
|
||||
ParamValueItem item = new ParamValueItem();
|
||||
item.setParamCode("SINGLE_TRANSACTION_AMOUNT");
|
||||
item.setParamValue("2000");
|
||||
|
||||
ModelParamGroupDTO group = new ModelParamGroupDTO();
|
||||
group.setModelCode("LARGE_TRANSACTION");
|
||||
group.setParams(List.of(item));
|
||||
|
||||
ModelParamSaveAllDTO dto = new ModelParamSaveAllDTO();
|
||||
dto.setProjectId(40L);
|
||||
dto.setModels(List.of(group));
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiProjectServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiProjectServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private CcdiProjectMapper projectMapper;
|
||||
|
||||
@Mock
|
||||
private LsfxAnalysisClient lsfxAnalysisClient;
|
||||
|
||||
@Test
|
||||
void shouldCountTaggingProjectsSeparately() {
|
||||
when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L);
|
||||
|
||||
CcdiProjectStatusCountsVO counts = service.getStatusCounts();
|
||||
|
||||
assertEquals(1L, counts.getStatus3());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectUpdatingArchivedProjectToTagging() {
|
||||
CcdiProject archived = new CcdiProject();
|
||||
archived.setProjectId(99L);
|
||||
archived.setStatus("2");
|
||||
when(projectMapper.selectById(99L)).thenReturn(archived);
|
||||
|
||||
assertThrows(ServiceException.class,
|
||||
() -> service.updateProjectStatus(99L, "3", "system"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectWritingWhenProjectIsTagging() {
|
||||
CcdiProject tagging = new CcdiProject();
|
||||
tagging.setProjectId(40L);
|
||||
tagging.setStatus("3");
|
||||
when(projectMapper.selectById(40L)).thenReturn(tagging);
|
||||
|
||||
assertThrows(ServiceException.class,
|
||||
() -> service.ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数"));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import ch.qos.logback.core.read.ListAppender;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask;
|
||||
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -33,6 +34,9 @@ class ProjectBankTagRebuildCoordinatorTest {
|
||||
@Mock
|
||||
private CcdiBankTagServiceImpl bankTagService;
|
||||
|
||||
@Mock
|
||||
private ICcdiProjectService projectService;
|
||||
|
||||
@Test
|
||||
void submitManualRebuild_shouldRejectWhenProjectAlreadyRunning() {
|
||||
CcdiBankTagTask runningTask = new CcdiBankTagTask();
|
||||
@@ -109,4 +113,13 @@ class ProjectBankTagRebuildCoordinatorTest {
|
||||
logger.detachAppender(logAppender);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSubmittingRebuildForArchivedProject() {
|
||||
org.mockito.Mockito.doThrow(new ServiceException("已归档项目不允许重新进入打标流程"))
|
||||
.when(projectService).ensureProjectCanStartTagging(40L);
|
||||
|
||||
assertThrows(ServiceException.class,
|
||||
() -> coordinator.submitManual(40L, null, "tester"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ruoyi.ccdi.project.sql;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiProjectStatusSqlTest {
|
||||
|
||||
@Test
|
||||
void shouldContainTaggingStatusInInitAndMigrationSql() throws IOException {
|
||||
Path repoRoot = Path.of("..");
|
||||
String initSql = Files.readString(repoRoot.resolve("sql/ccdi_project.sql"));
|
||||
String migrationSql = Files.readString(repoRoot.resolve("sql/migration/2026-03-18-add-project-tagging-status.sql"));
|
||||
|
||||
assertTrue(initSql.contains("打标中"));
|
||||
assertTrue(initSql.contains("'3'"));
|
||||
assertTrue(migrationSql.contains("ccdi_project_status"));
|
||||
assertTrue(migrationSql.contains("打标中"));
|
||||
assertTrue(migrationSql.contains("'3'"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# 项目打标状态联动后端实施记录
|
||||
|
||||
## 本次改动
|
||||
|
||||
- 新增项目状态常量 `3-打标中`,并补齐初始化 SQL 与增量 SQL。
|
||||
- 在项目服务中新增统一状态更新、打标准入校验、打标中写保护能力,并把状态统计扩展到 `status3`。
|
||||
- 在银行流水打标主链路中接入项目状态流转:
|
||||
- 开始打标前置为 `3`
|
||||
- 打标成功后置为 `1`
|
||||
- 打标失败后回退为 `0`
|
||||
- 在上传流水、拉取本行信息、参数保存链路中统一接入项目写保护,打标中直接拒绝写操作。
|
||||
- 增补 SQL 基线测试、项目状态服务测试、打标状态流转测试、上传/参数写保护测试。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- `sql/ccdi_project.sql`
|
||||
- `sql/migration/2026-03-18-add-project-tagging-status.sql`
|
||||
- `ccdi-project` 模块中的项目服务、打标服务、协调器、文件上传服务、模型参数服务及对应测试
|
||||
|
||||
## 验证结果
|
||||
|
||||
- 后端聚焦回归命令执行通过:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiBankTagServiceImplTest,ProjectBankTagRebuildCoordinatorTest,CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest,CcdiProjectStatusSqlTest test
|
||||
```
|
||||
|
||||
- 测试结果:`Tests run: 44, Failures: 0, Errors: 0, Skipped: 0`
|
||||
|
||||
## SQL 执行方式
|
||||
|
||||
- 联调或生产前执行状态增量脚本时,必须使用仓库脚本:
|
||||
|
||||
```bash
|
||||
bin/mysql_utf8_exec.sh sql/migration/2026-03-18-add-project-tagging-status.sql
|
||||
```
|
||||
|
||||
- 这样可以保证中文字典值“打标中”在 MySQL 会话中按 `utf8mb4` 正确写入,避免乱码。
|
||||
@@ -0,0 +1,46 @@
|
||||
# 项目打标状态联动前端实施记录
|
||||
|
||||
## 本次改动
|
||||
|
||||
- 在项目列表、详情页和顶部状态统计中补充 `3-打标中` 展示。
|
||||
- 在上传数据页增加统一受限态:
|
||||
- “拉取本行信息”按钮禁用
|
||||
- “上传流水”入口禁用
|
||||
- 页面顶部展示受限提示文案
|
||||
- 在参数模型页增加只读态:
|
||||
- 参数输入框禁用
|
||||
- “保存所有修改”按钮禁用
|
||||
- 页面顶部展示只读提示文案
|
||||
- 在项目详情页接入 `refresh-project` 事件,上传页在以下时机触发父组件刷新项目状态:
|
||||
- 批量上传提交成功后
|
||||
- 拉取本行信息提交成功后
|
||||
- 文件轮询检测到状态变化后
|
||||
- 手工刷新文件列表后
|
||||
|
||||
## 影响范围
|
||||
|
||||
- `ruoyi-ui/src/views/ccdiProject/detail.vue`
|
||||
- `ruoyi-ui/src/views/ccdiProject/index.vue`
|
||||
- `ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue`
|
||||
- `ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue`
|
||||
- `ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue`
|
||||
- `ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue`
|
||||
- `docs/tests/records/2026-03-18-project-bank-tag-status-lock-frontend-verification.md`
|
||||
|
||||
## 验证结果
|
||||
|
||||
- 基线构建通过:
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
- 改动后构建通过:
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
- 当前已完成构建验证,尚未在本次记录中执行真实页面联调。
|
||||
@@ -0,0 +1,29 @@
|
||||
# 项目打标状态联动后端验证记录
|
||||
|
||||
## 验证项
|
||||
|
||||
- [x] 状态 `3-打标中` SQL 已同步
|
||||
- [x] 打标成功后状态为 `1`
|
||||
- [x] 打标失败后状态回退为 `0`
|
||||
- [x] 打标中拒绝上传/拉取本行信息
|
||||
- [x] 打标中拒绝参数保存
|
||||
|
||||
## 自动化验证
|
||||
|
||||
- 执行时间:2026-03-18 14:56:22 +08:00
|
||||
- 执行命令:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiBankTagServiceImplTest,ProjectBankTagRebuildCoordinatorTest,CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest,CcdiProjectStatusSqlTest test
|
||||
```
|
||||
|
||||
- 结果:`PASS`
|
||||
- 统计:`Tests run: 44, Failures: 0, Errors: 0, Skipped: 0`
|
||||
|
||||
## SQL 执行说明
|
||||
|
||||
- 联调环境执行增量脚本时,必须使用以下命令,确保中文内容以 `utf8mb4` 写入:
|
||||
|
||||
```bash
|
||||
bin/mysql_utf8_exec.sh sql/migration/2026-03-18-add-project-tagging-status.sql
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
# 项目打标状态联动前端验证记录
|
||||
|
||||
## 验证范围
|
||||
|
||||
- 列表页状态与统计显示
|
||||
- 详情页状态标签
|
||||
- 上传数据页禁用
|
||||
- 参数模型页只读
|
||||
|
||||
## 验证结果
|
||||
|
||||
- [ ] 列表页出现“打标中”
|
||||
- [ ] 顶部统计出现“打标中”
|
||||
- [ ] 详情页出现“打标中”
|
||||
- [ ] 打标中时“拉取本行信息”按钮禁用
|
||||
- [ ] 打标中时“上传流水”按钮禁用
|
||||
- [ ] 打标中时仍可查看上传记录列表
|
||||
- [ ] 页面有明确提示文案
|
||||
- [ ] 打标中时参数输入框禁用
|
||||
- [ ] 打标中时“保存所有修改”按钮禁用
|
||||
- [ ] 参数页有只读提示文案
|
||||
- [ ] 提交上传或拉取任务后,详情页能重新获取项目状态
|
||||
- [ ] 文件轮询期间如后端状态切为“打标中”,页面会同步受限
|
||||
|
||||
## 构建验证
|
||||
|
||||
- [x] `npm run build:prod` 基线构建通过(2026-03-18 14:56 +08:00)
|
||||
- [x] `npm run build:prod` 改动后构建通过(2026-03-18 15:00 +08:00)
|
||||
|
||||
## 手工联调说明
|
||||
|
||||
- [ ] 已启动前后端与依赖服务进行联调
|
||||
- [ ] 联调完成后已关闭测试过程中启动的进程
|
||||
|
||||
## 说明
|
||||
|
||||
- 本次已完成前端静态实现与两轮生产构建验证。
|
||||
- 真实页面联调尚未在本记录中勾选,如需补齐,请按计划启动前后端与依赖服务后继续验证。
|
||||
@@ -98,7 +98,7 @@
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-if="scope.row.status === '0'"
|
||||
v-if="scope.row.status === '0' || scope.row.status === '3'"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-right"
|
||||
@@ -192,6 +192,7 @@ export default {
|
||||
0: "#1890ff",
|
||||
1: "#52c41a",
|
||||
2: "#8c8c8c",
|
||||
3: "#fa8c16",
|
||||
};
|
||||
return colorMap[status] || "#8c8c8c";
|
||||
},
|
||||
|
||||
@@ -39,7 +39,8 @@ export default {
|
||||
all: 0,
|
||||
'0': 0,
|
||||
'1': 0,
|
||||
'2': 0
|
||||
'2': 0,
|
||||
'3': 0
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -51,7 +52,8 @@ export default {
|
||||
{ label: '全部项目', value: 'all', count: 0 },
|
||||
{ label: '进行中', value: '0', count: 0 },
|
||||
{ label: '已完成', value: '1', count: 0 },
|
||||
{ label: '已归档', value: '2', count: 0 }
|
||||
{ label: '已归档', value: '2', count: 0 },
|
||||
{ label: '打标中', value: '3', count: 0 }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="param-config-container" v-loading="loading" element-loading-text="加载中...">
|
||||
<div v-if="isProjectTagging" class="readonly-tip">
|
||||
项目正在进行银行流水打标,参数暂不可修改。
|
||||
</div>
|
||||
|
||||
<!-- 模型参数卡片组(垂直堆叠) -->
|
||||
<div class="model-cards-container" v-if="!loading && modelGroups.length > 0">
|
||||
<div
|
||||
@@ -20,6 +24,7 @@
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.paramValue"
|
||||
:disabled="isProjectTagging"
|
||||
placeholder="请输入阈值"
|
||||
@input="markAsModified(model.modelCode, row)"
|
||||
/>
|
||||
@@ -37,7 +42,7 @@
|
||||
|
||||
<!-- 统一保存按钮 -->
|
||||
<div class="button-section" v-if="!loading && modelGroups.length > 0">
|
||||
<el-button type="primary" @click="handleSaveAll" :loading="saving">
|
||||
<el-button type="primary" :disabled="isProjectTagging || saving" @click="handleSaveAll" :loading="saving">
|
||||
保存所有修改
|
||||
</el-button>
|
||||
<span v-if="modifiedCount > 0" class="modified-tip">
|
||||
@@ -74,6 +79,9 @@ export default {
|
||||
computed: {
|
||||
modifiedCount() {
|
||||
return Object.keys(this.modifiedParams).length;
|
||||
},
|
||||
isProjectTagging() {
|
||||
return String(this.projectInfo.projectStatus) === "3";
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -151,6 +159,10 @@ export default {
|
||||
},
|
||||
|
||||
async handleSaveAll() {
|
||||
if (this.isProjectTagging) {
|
||||
this.$message.warning("项目正在进行银行流水打标,参数暂不可修改");
|
||||
return;
|
||||
}
|
||||
if (this.modifiedCount === 0) {
|
||||
this.$message.info('没有需要保存的修改');
|
||||
return;
|
||||
@@ -201,6 +213,15 @@ export default {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.readonly-tip {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px 16px;
|
||||
color: #ad6800;
|
||||
background: #fff7e6;
|
||||
border: 1px solid #ffd591;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.model-cards-container {
|
||||
margin-bottom: 20px;
|
||||
min-height: 300px;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-download"
|
||||
:disabled="isProjectTagging"
|
||||
@click="handleFetchBankInfo"
|
||||
>
|
||||
拉取本行信息
|
||||
@@ -24,6 +25,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isProjectTagging" class="tagging-lock-tip">
|
||||
项目正在进行银行流水打标,暂不可上传或拉取数据。
|
||||
</div>
|
||||
|
||||
<!-- 上传模块 -->
|
||||
<div class="upload-section">
|
||||
<div class="upload-cards">
|
||||
@@ -165,6 +170,7 @@
|
||||
class="upload-area"
|
||||
drag
|
||||
action="#"
|
||||
:disabled="isProjectTagging"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:file-list="fileList"
|
||||
@@ -179,6 +185,7 @@
|
||||
<el-button @click="showUploadDialog = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="isProjectTagging"
|
||||
@click="handleConfirmUpload"
|
||||
:loading="uploading"
|
||||
>确定</el-button
|
||||
@@ -231,6 +238,7 @@
|
||||
v-model="pullBankInfoForm.idCardText"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
:disabled="isProjectTagging"
|
||||
placeholder="支持逗号、中文逗号、换行分隔"
|
||||
/>
|
||||
<div class="pull-bank-field-tip">
|
||||
@@ -248,6 +256,7 @@
|
||||
:show-file-list="false"
|
||||
:file-list="idCardFileList"
|
||||
accept=".xls,.xlsx"
|
||||
:disabled="isProjectTagging"
|
||||
:on-change="handleIdCardFileChange"
|
||||
:on-remove="handleIdCardFileRemove"
|
||||
>
|
||||
@@ -279,6 +288,7 @@
|
||||
<el-date-picker
|
||||
class="pull-bank-range-picker"
|
||||
v-model="pullBankInfoForm.dateRange"
|
||||
:disabled="isProjectTagging"
|
||||
type="daterange"
|
||||
value-format="yyyy-MM-dd"
|
||||
:picker-options="pullBankInfoDatePickerOptions"
|
||||
@@ -292,6 +302,7 @@
|
||||
<el-button @click="pullBankInfoDialogVisible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="isProjectTagging"
|
||||
:loading="pullBankInfoLoading"
|
||||
@click="handleConfirmPullBankInfo"
|
||||
>
|
||||
@@ -312,6 +323,7 @@
|
||||
drag
|
||||
action="#"
|
||||
multiple
|
||||
:disabled="isProjectTagging"
|
||||
:auto-upload="false"
|
||||
:on-change="handleBatchFileChange"
|
||||
:show-file-list="false"
|
||||
@@ -350,8 +362,8 @@
|
||||
<el-button @click="batchUploadDialogVisible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="isProjectTagging || selectedFiles.length === 0"
|
||||
:loading="uploadLoading"
|
||||
:disabled="selectedFiles.length === 0"
|
||||
@click="handleBatchUpload"
|
||||
>开始上传</el-button
|
||||
>
|
||||
@@ -518,6 +530,7 @@ export default {
|
||||
pollingTimer: null,
|
||||
pollingEnabled: false,
|
||||
pollingInterval: 5000,
|
||||
lastFileStatusSignature: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -526,12 +539,21 @@ export default {
|
||||
disabledDate: (time) => this.isPullBankInfoDateDisabled(time),
|
||||
};
|
||||
},
|
||||
isProjectTagging() {
|
||||
return String(this.projectInfo.projectStatus) === "3";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"projectInfo.projectStatus"() {
|
||||
this.syncUploadCardDisabledState();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// 加载初始数据
|
||||
// this.loadInitialData();
|
||||
// 监听路由变化更新选中菜单
|
||||
this.updateActiveMenu();
|
||||
this.syncUploadCardDisabledState();
|
||||
},
|
||||
mounted() {
|
||||
// 组件挂载后监听项目ID变化
|
||||
@@ -597,6 +619,19 @@ export default {
|
||||
: card.btnText;
|
||||
}
|
||||
});
|
||||
|
||||
this.syncUploadCardDisabledState();
|
||||
},
|
||||
syncUploadCardDisabledState() {
|
||||
this.uploadCards = this.uploadCards.map((card) => {
|
||||
if (card.key === "transaction") {
|
||||
return {
|
||||
...card,
|
||||
disabled: this.isProjectTagging,
|
||||
};
|
||||
}
|
||||
return card;
|
||||
});
|
||||
},
|
||||
|
||||
/** 更新质量指标 */
|
||||
@@ -660,6 +695,10 @@ export default {
|
||||
},
|
||||
/** 确认上传 */
|
||||
async handleConfirmUpload() {
|
||||
if (this.isProjectTagging) {
|
||||
this.$message.warning("项目正在进行银行流水打标,暂不可上传或拉取数据");
|
||||
return;
|
||||
}
|
||||
if (this.fileList.length === 0) {
|
||||
this.$message.warning("请选择要上传的文件");
|
||||
return;
|
||||
@@ -919,6 +958,10 @@ export default {
|
||||
return this.parseIdCardText(this.pullBankInfoForm.idCardText);
|
||||
},
|
||||
async handleConfirmPullBankInfo() {
|
||||
if (this.isProjectTagging) {
|
||||
this.$message.warning("项目正在进行银行流水打标,暂不可上传或拉取数据");
|
||||
return;
|
||||
}
|
||||
const idCards = this.buildFinalIdCardList();
|
||||
const [startDate, endDate] = this.pullBankInfoForm.dateRange || [];
|
||||
|
||||
@@ -953,6 +996,8 @@ export default {
|
||||
this.$message.success((res && res.msg) || "拉取任务已提交");
|
||||
|
||||
await Promise.all([this.loadStatistics(), this.loadFileList()]);
|
||||
this.lastFileStatusSignature = this.buildFileStatusSignature();
|
||||
this.$emit("refresh-project");
|
||||
|
||||
const hasPollingRecords =
|
||||
this.statistics.uploading > 0 ||
|
||||
@@ -973,6 +1018,10 @@ export default {
|
||||
},
|
||||
/** 拉取本行信息 */
|
||||
handleFetchBankInfo() {
|
||||
if (this.isProjectTagging) {
|
||||
this.$message.warning("项目正在进行银行流水打标,暂不可上传或拉取数据");
|
||||
return;
|
||||
}
|
||||
this.resetPullBankInfoForm();
|
||||
this.openPullBankInfoDialog();
|
||||
},
|
||||
@@ -1009,6 +1058,7 @@ export default {
|
||||
0: "processing",
|
||||
1: "success",
|
||||
2: "archived",
|
||||
3: "tagging",
|
||||
};
|
||||
return statusMap[status] || "processing";
|
||||
},
|
||||
@@ -1019,6 +1069,7 @@ export default {
|
||||
0: "进行中",
|
||||
1: "已完成",
|
||||
2: "已归档",
|
||||
3: "打标中",
|
||||
};
|
||||
return statusMap[status] || "未知";
|
||||
},
|
||||
@@ -1059,6 +1110,10 @@ export default {
|
||||
|
||||
/** 开始批量上传 */
|
||||
async handleBatchUpload() {
|
||||
if (this.isProjectTagging) {
|
||||
this.$message.warning("项目正在进行银行流水打标,暂不可上传或拉取数据");
|
||||
return;
|
||||
}
|
||||
if (this.selectedFiles.length === 0) {
|
||||
this.$message.warning("请选择要上传的文件");
|
||||
return;
|
||||
@@ -1079,6 +1134,8 @@ export default {
|
||||
|
||||
// 刷新数据并启动轮询
|
||||
await Promise.all([this.loadStatistics(), this.loadFileList()]);
|
||||
this.lastFileStatusSignature = this.buildFileStatusSignature();
|
||||
this.$emit("refresh-project");
|
||||
|
||||
this.startPolling();
|
||||
} catch (error) {
|
||||
@@ -1118,6 +1175,9 @@ export default {
|
||||
const res = await getFileUploadList(params);
|
||||
this.fileUploadList = res.rows || [];
|
||||
this.total = res.total || 0;
|
||||
if (!this.lastFileStatusSignature) {
|
||||
this.lastFileStatusSignature = this.buildFileStatusSignature();
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("加载文件列表失败");
|
||||
console.error(error);
|
||||
@@ -1138,6 +1198,11 @@ export default {
|
||||
|
||||
Promise.all([this.loadStatistics(), this.loadFileList()])
|
||||
.then(() => {
|
||||
const currentSignature = this.buildFileStatusSignature();
|
||||
if (currentSignature !== this.lastFileStatusSignature) {
|
||||
this.lastFileStatusSignature = currentSignature;
|
||||
this.$emit("refresh-project");
|
||||
}
|
||||
if (
|
||||
this.statistics.uploading === 0 &&
|
||||
this.statistics.parsing === 0
|
||||
@@ -1168,6 +1233,8 @@ export default {
|
||||
/** 手动刷新 */
|
||||
async handleManualRefresh() {
|
||||
await Promise.all([this.loadStatistics(), this.loadFileList()]);
|
||||
this.lastFileStatusSignature = this.buildFileStatusSignature();
|
||||
this.$emit("refresh-project");
|
||||
|
||||
this.$message.success("刷新成功");
|
||||
|
||||
@@ -1183,6 +1250,11 @@ export default {
|
||||
this.queryParams.pageNum = pageNum;
|
||||
this.loadFileList();
|
||||
},
|
||||
buildFileStatusSignature() {
|
||||
return (this.fileUploadList || [])
|
||||
.map((item) => `${item.id || ""}:${item.fileStatus || ""}`)
|
||||
.join("|");
|
||||
},
|
||||
|
||||
getRowAction(row) {
|
||||
return getUploadFileAction(row.fileStatus);
|
||||
@@ -1401,6 +1473,15 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.tagging-lock-tip {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px 16px;
|
||||
color: #ad6800;
|
||||
background: #fff7e6;
|
||||
border: 1px solid #ffd591;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
// 上传模块
|
||||
.upload-section {
|
||||
background: #ffffff;
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
:project-id="projectId"
|
||||
:project-info="projectInfo"
|
||||
@menu-change="handleMenuChange"
|
||||
@refresh-project="handleRefreshProject"
|
||||
@data-uploaded="handleDataUploaded"
|
||||
@name-selected="handleNameSelected"
|
||||
@generate-report="handleGenerateReport"
|
||||
@@ -215,6 +216,7 @@ export default {
|
||||
0: "primary", // 进行中
|
||||
1: "success", // 已完成
|
||||
2: "info", // 已归档
|
||||
3: "warning", // 打标中
|
||||
};
|
||||
return statusMap[status] || "info";
|
||||
},
|
||||
@@ -224,6 +226,7 @@ export default {
|
||||
0: "进行中",
|
||||
1: "已完成",
|
||||
2: "已归档",
|
||||
3: "打标中",
|
||||
};
|
||||
return statusMap[status] || "未知";
|
||||
},
|
||||
@@ -292,6 +295,9 @@ export default {
|
||||
this.initPageData();
|
||||
this.$message.success("刷新成功");
|
||||
},
|
||||
handleRefreshProject() {
|
||||
this.initPageData();
|
||||
},
|
||||
/** 导出报告 */
|
||||
handleExport() {
|
||||
console.log("导出报告");
|
||||
|
||||
@@ -101,7 +101,8 @@ export default {
|
||||
all: 0,
|
||||
'0': 0,
|
||||
'1': 0,
|
||||
'2': 0
|
||||
'2': 0,
|
||||
'3': 0
|
||||
},
|
||||
// 新增/编辑弹窗
|
||||
addDialogVisible: false,
|
||||
@@ -137,7 +138,8 @@ export default {
|
||||
all: counts.all || 0,
|
||||
'0': counts.status0 || 0,
|
||||
'1': counts.status1 || 0,
|
||||
'2': counts.status2 || 0
|
||||
'2': counts.status2 || 0,
|
||||
'3': counts.status3 || 0
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
|
||||
@@ -11,7 +11,7 @@ CREATE TABLE `ccdi_project` (
|
||||
`project_name` VARCHAR(200) NOT NULL COMMENT '项目名称',
|
||||
`description` VARCHAR(500) DEFAULT NULL COMMENT '项目描述',
|
||||
`config_type` VARCHAR(20) NOT NULL DEFAULT 'default' COMMENT '配置方式:default-全局默认,custom-自定义',
|
||||
`status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档',
|
||||
`status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档,3-打标中',
|
||||
`is_archived` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否归档:0-未归档,1-已归档',
|
||||
`target_count` INT NOT NULL DEFAULT 0 COMMENT '目标人数',
|
||||
`high_risk_count` INT NOT NULL DEFAULT 0 COMMENT '高风险人数',
|
||||
@@ -41,7 +41,8 @@ INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_cla
|
||||
VALUES
|
||||
(1, '进行中', '0', 'ccdi_project_status', '', 'primary', 'Y', '0', 'admin', NOW()),
|
||||
(2, '已完成', '1', 'ccdi_project_status', '', 'success', 'N', '0', 'admin', NOW()),
|
||||
(3, '已归档', '2', 'ccdi_project_status', '', 'info', 'N', '0', 'admin', NOW());
|
||||
(3, '已归档', '2', 'ccdi_project_status', '', 'info', 'N', '0', 'admin', NOW()),
|
||||
(4, '打标中', '3', 'ccdi_project_status', '', 'warning', 'N', '0', 'admin', NOW());
|
||||
|
||||
-- ----------------------------
|
||||
-- 4. 插入配置方式字典
|
||||
|
||||
32
sql/migration/2026-03-18-add-project-tagging-status.sql
Normal file
32
sql/migration/2026-03-18-add-project-tagging-status.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
ALTER TABLE ccdi_project
|
||||
MODIFY COLUMN status CHAR(1) NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档,3-打标中';
|
||||
|
||||
INSERT INTO sys_dict_data (
|
||||
dict_sort,
|
||||
dict_label,
|
||||
dict_value,
|
||||
dict_type,
|
||||
css_class,
|
||||
list_class,
|
||||
is_default,
|
||||
status,
|
||||
create_by,
|
||||
create_time
|
||||
)
|
||||
SELECT
|
||||
4,
|
||||
'打标中',
|
||||
'3',
|
||||
'ccdi_project_status',
|
||||
'',
|
||||
'warning',
|
||||
'N',
|
||||
'0',
|
||||
'admin',
|
||||
NOW()
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys_dict_data
|
||||
WHERE dict_type = 'ccdi_project_status'
|
||||
AND dict_value = '3'
|
||||
);
|
||||
Reference in New Issue
Block a user