优化无外部人员涉疑交易查询

This commit is contained in:
wjj
2026-07-16 15:26:14 +08:00
parent 4a7cfde4af
commit 6d90bb4e58
8 changed files with 232 additions and 20 deletions

View File

@@ -22,4 +22,7 @@ public class CcdiProjectSuspiciousTransactionQueryDTO {
/** 每页数量 */
private Integer pageSize;
/** 是否包含外部人员预警分支 */
private Boolean includeExternalPerson;
}

View File

@@ -163,6 +163,14 @@ public interface CcdiProjectOverviewMapper {
*/
CcdiProjectExternalRiskSummaryVO selectExternalRiskSummaryByProjectId(@Param("projectId") Long projectId);
/**
* 判断项目是否存在外部人员主体
*
* @param projectId 项目ID
* @return 存在时返回1否则返回空
*/
Integer selectExternalPersonSubjectExistsByProjectId(@Param("projectId") Long projectId);
/**
* 查询外部人员预警模型卡片
*

View File

@@ -86,6 +86,12 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
private static final String ACTION_LABEL = "查看详情";
private static final String SUSPICIOUS_TYPE_ALL = "ALL";
private static final String SUSPICIOUS_TYPE_MODEL_RULE = "MODEL_RULE";
private static final String SUSPICIOUS_TYPE_EXTERNAL_PERSON = "EXTERNAL_PERSON";
private static final String EXCLUSION_TYPE_STATEMENT = "STATEMENT";
private static final String EXCLUSION_TYPE_OBJECT = "OBJECT";
@@ -369,6 +375,11 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
) {
ensureProjectExists(queryDTO.getProjectId());
normalizeSuspiciousTransactionQuery(queryDTO);
prepareSuspiciousTransactionExternalBranch(queryDTO);
if (isExternalPersonOnlyWithoutSubject(queryDTO)) {
return emptySuspiciousTransactionPage();
}
Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(
defaultPageNum(queryDTO.getPageNum()),
@@ -389,6 +400,11 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
) {
ensureProjectExists(queryDTO.getProjectId());
normalizeSuspiciousTransactionQuery(queryDTO);
prepareSuspiciousTransactionExternalBranch(queryDTO);
if (isExternalPersonOnlyWithoutSubject(queryDTO)) {
return List.of();
}
return defaultList(overviewMapper.selectReportSuspiciousTransactionList(queryDTO)).stream()
.map(this::buildSuspiciousTransactionExcelRow)
@@ -719,12 +735,42 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
queryDTO.setModelCode(null);
}
if (queryDTO.getSuspiciousType() == null || queryDTO.getSuspiciousType().isBlank()) {
queryDTO.setSuspiciousType("ALL");
queryDTO.setSuspiciousType(SUSPICIOUS_TYPE_ALL);
return;
}
queryDTO.setSuspiciousType(queryDTO.getSuspiciousType().trim().toUpperCase());
}
private void prepareSuspiciousTransactionExternalBranch(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
if (!shouldCheckExternalPersonBranch(queryDTO.getSuspiciousType())) {
queryDTO.setIncludeExternalPerson(false);
return;
}
queryDTO.setIncludeExternalPerson(hasExternalPersonSubject(queryDTO.getProjectId()));
}
private boolean shouldCheckExternalPersonBranch(String suspiciousType) {
return SUSPICIOUS_TYPE_ALL.equals(suspiciousType)
|| SUSPICIOUS_TYPE_MODEL_RULE.equals(suspiciousType)
|| SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(suspiciousType);
}
private boolean hasExternalPersonSubject(Long projectId) {
return overviewMapper.selectExternalPersonSubjectExistsByProjectId(projectId) != null;
}
private boolean isExternalPersonOnlyWithoutSubject(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
return SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(queryDTO.getSuspiciousType())
&& !Boolean.TRUE.equals(queryDTO.getIncludeExternalPerson());
}
private CcdiProjectSuspiciousTransactionPageVO emptySuspiciousTransactionPage() {
CcdiProjectSuspiciousTransactionPageVO result = new CcdiProjectSuspiciousTransactionPageVO();
result.setRows(List.of());
result.setTotal(0L);
return result;
}
private CcdiProjectOverviewStatVO buildStat(String key, String label, Integer value) {
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
stat.setKey(key);

View File

@@ -811,6 +811,22 @@
and risk.cert_no = subject.cert_no
</select>
<select id="selectExternalPersonSubjectExistsByProjectId" resultType="java.lang.Integer">
select 1
from ccdi_bank_statement bs
left join ccdi_base_staff staff
on staff.id_card = bs.cret_no
left join ccdi_staff_fmy_relation relation
on relation.status = 1
and relation.relation_cert_no = bs.cret_no
where bs.project_id = #{projectId}
and bs.cret_no is not null
and trim(bs.cret_no) != ''
and staff.id_card is null
and relation.relation_cert_no is null
limit 1
</select>
<select id="selectExternalRiskModelCardsByProjectId" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO">
<bind name="externalProjectId" value="projectId"/>
select
@@ -1161,26 +1177,28 @@
<include refid="suspiciousTransactionNameHitSql"/>
) name_hits on name_hits.bankStatementId = base.bankStatementId
union all
<if test="query.includeExternalPerson == true">
union all
select
external_hits.bankStatementId,
external_hits.trxDate,
external_hits.relatedPersonName,
external_hits.relatedStaffName,
external_hits.relatedStaffCode,
external_hits.relationType,
external_hits.userMemo,
external_hits.cashType,
external_hits.displayAmount,
external_hits.hasModelRuleHit,
external_hits.hasNameListHit,
external_hits.suspiciousPersonName,
external_hits.matchPriority,
external_hits.nameListHitType
from (
<include refid="externalSuspiciousTransactionSql"/>
) external_hits
select
external_hits.bankStatementId,
external_hits.trxDate,
external_hits.relatedPersonName,
external_hits.relatedStaffName,
external_hits.relatedStaffCode,
external_hits.relationType,
external_hits.userMemo,
external_hits.cashType,
external_hits.displayAmount,
external_hits.hasModelRuleHit,
external_hits.hasNameListHit,
external_hits.suspiciousPersonName,
external_hits.matchPriority,
external_hits.nameListHitType
from (
<include refid="externalSuspiciousTransactionSql"/>
) external_hits
</if>
</sql>
<sql id="suspiciousTransactionAggregatedSql">

View File

@@ -91,7 +91,9 @@ class CcdiProjectOverviewMapperSqlTest {
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
String suspiciousSql = extractSelect(xml, "selectSuspiciousTransactionPage");
String modelHitSql = extractSqlFragment(xml, "suspiciousTransactionModelHitSql");
String mergedSql = extractSqlFragment(xml, "suspiciousTransactionMergedSql");
String aggregatedSql = extractSqlFragment(xml, "suspiciousTransactionAggregatedSql");
String externalSubjectExistsSql = extractSelect(xml, "selectExternalPersonSubjectExistsByProjectId");
assertTrue(modelHitSql.contains("from ccdi_bank_statement_tag_result tr"), modelHitSql);
assertTrue(modelHitSql.contains("tr.bank_statement_id is not null"), modelHitSql);
@@ -104,6 +106,11 @@ class CcdiProjectOverviewMapperSqlTest {
assertTrue(suspiciousSql.contains("hasModelRuleHit"), suspiciousSql);
assertTrue(suspiciousSql.contains("hasNameListHit"), suspiciousSql);
assertTrue(suspiciousSql.contains("final_result.nameListHitType"), suspiciousSql);
assertTrue(mergedSql.contains("<if test=\"query.includeExternalPerson == true\">"), mergedSql);
assertTrue(mergedSql.contains("<include refid=\"externalSuspiciousTransactionSql\"/>"), mergedSql);
assertTrue(externalSubjectExistsSql.contains("limit 1"), externalSubjectExistsSql);
assertTrue(externalSubjectExistsSql.contains("staff.id_card is null"), externalSubjectExistsSql);
assertTrue(externalSubjectExistsSql.contains("relation.relation_cert_no is null"), externalSubjectExistsSql);
String reportSuspiciousSql = extractSelect(xml, "selectReportSuspiciousTransactionList");
assertTrue(reportSuspiciousSql.contains("final_result.nameListHitType = '中介'"), reportSuspiciousSql);

View File

@@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -93,6 +94,70 @@ class CcdiProjectOverviewServiceSuspiciousTransactionTest {
);
}
@Test
void shouldIncludeExternalBranchWhenExternalSubjectExistsForAllSuspiciousTransactions() {
CcdiProject project = new CcdiProject();
project.setProjectId(40L);
when(projectMapper.selectById(40L)).thenReturn(project);
when(overviewMapper.selectExternalPersonSubjectExistsByProjectId(40L)).thenReturn(1);
Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(1, 10);
page.setRecords(List.of());
page.setTotal(0);
when(overviewMapper.selectSuspiciousTransactionPage(any(Page.class), any(CcdiProjectSuspiciousTransactionQueryDTO.class)))
.thenReturn(page);
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
queryDTO.setProjectId(40L);
CcdiProjectSuspiciousTransactionPageVO result = service.getSuspiciousTransactions(queryDTO);
assertEquals(0L, result.getTotal());
verify(overviewMapper).selectSuspiciousTransactionPage(
any(Page.class),
argThat(query -> "ALL".equals(query.getSuspiciousType())
&& Boolean.TRUE.equals(query.getIncludeExternalPerson()))
);
}
@Test
void shouldReturnEmptyExternalSuspiciousTransactionsWhenExternalSubjectDoesNotExist() {
CcdiProject project = new CcdiProject();
project.setProjectId(40L);
when(projectMapper.selectById(40L)).thenReturn(project);
when(overviewMapper.selectExternalPersonSubjectExistsByProjectId(40L)).thenReturn(null);
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
queryDTO.setProjectId(40L);
queryDTO.setSuspiciousType("external_person");
CcdiProjectSuspiciousTransactionPageVO result = service.getSuspiciousTransactions(queryDTO);
assertEquals(0L, result.getTotal());
assertTrue(result.getRows().isEmpty());
verify(overviewMapper, never()).selectSuspiciousTransactionPage(
any(Page.class),
any(CcdiProjectSuspiciousTransactionQueryDTO.class)
);
}
@Test
void shouldReturnEmptyExternalSuspiciousTransactionExportWhenExternalSubjectDoesNotExist() {
CcdiProject project = new CcdiProject();
project.setProjectId(40L);
when(projectMapper.selectById(40L)).thenReturn(project);
when(overviewMapper.selectExternalPersonSubjectExistsByProjectId(40L)).thenReturn(null);
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
queryDTO.setProjectId(40L);
queryDTO.setSuspiciousType("EXTERNAL_PERSON");
List<CcdiProjectSuspiciousTransactionExcel> rows = service.exportSuspiciousTransactions(queryDTO);
assertTrue(rows.isEmpty());
verify(overviewMapper, never()).selectReportSuspiciousTransactionList(any(CcdiProjectSuspiciousTransactionQueryDTO.class));
}
@Test
void shouldExportSuspiciousTransactionsWithCurrentFilter() {
CcdiProject project = new CcdiProject();