修复涉疑交易筛选与标签错乱
This commit is contained in:
@@ -72,9 +72,13 @@ public class CcdiBankStatementController extends BaseController {
|
||||
*/
|
||||
@GetMapping("/detail/{bankStatementId}")
|
||||
@Operation(summary = "查询流水详情")
|
||||
public AjaxResult getDetail(@PathVariable Long bankStatementId) {
|
||||
public AjaxResult getDetail(@PathVariable Long bankStatementId, String modelCode, String suspiciousType) {
|
||||
projectAccessService.assertCanReadByBankStatementId(bankStatementId);
|
||||
CcdiBankStatementDetailVO detail = bankStatementService.getStatementDetail(bankStatementId);
|
||||
CcdiBankStatementDetailVO detail = bankStatementService.getStatementDetail(
|
||||
bankStatementId,
|
||||
modelCode,
|
||||
suspiciousType
|
||||
);
|
||||
return AjaxResult.success(detail);
|
||||
}
|
||||
|
||||
|
||||
@@ -308,9 +308,12 @@ public class CcdiProjectOverviewController extends BaseController {
|
||||
@PostMapping("/risk-details/export")
|
||||
@Operation(summary = "导出风险明细")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||
projectAccessService.assertCanRead(projectId);
|
||||
overviewService.exportRiskDetails(response, projectId);
|
||||
public void exportRiskDetails(
|
||||
HttpServletResponse response,
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||
) {
|
||||
projectAccessService.assertCanRead(queryDTO.getProjectId());
|
||||
overviewService.exportRiskDetails(response, queryDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,9 @@ import lombok.Data;
|
||||
@Data
|
||||
public class CcdiBankStatementHitTagVO {
|
||||
|
||||
/** 模型编码 */
|
||||
private String modelCode;
|
||||
|
||||
/** 规则编码 */
|
||||
private String ruleCode;
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.ruoyi.ccdi.project.domain.vo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 涉疑交易明细行
|
||||
@@ -14,6 +16,14 @@ public class CcdiProjectSuspiciousTransactionItemVO {
|
||||
|
||||
private String trxDate;
|
||||
|
||||
private String leAccountNo;
|
||||
|
||||
private String leAccountName;
|
||||
|
||||
private String customerAccountName;
|
||||
|
||||
private String customerAccountNo;
|
||||
|
||||
private String suspiciousPersonName;
|
||||
|
||||
private String relatedPersonName;
|
||||
@@ -35,4 +45,6 @@ public class CcdiProjectSuspiciousTransactionItemVO {
|
||||
private Boolean hasNameListHit;
|
||||
|
||||
private String nameListHitType;
|
||||
|
||||
private List<CcdiBankStatementHitTagVO> hitTags = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -26,11 +26,15 @@ public interface CcdiBankTagResultMapper extends BaseMapper<CcdiBankTagResult> {
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param bankStatementIds 流水ID列表
|
||||
* @param modelCode 模型编码
|
||||
* @param suspiciousType 预警类型
|
||||
* @return 命中的异常标签列表
|
||||
*/
|
||||
List<CcdiBankStatementHitTagVO> selectStatementTagsByProjectAndStatementIds(
|
||||
@Param("projectId") Long projectId,
|
||||
@Param("bankStatementIds") List<Long> bankStatementIds
|
||||
@Param("bankStatementIds") List<Long> bankStatementIds,
|
||||
@Param("modelCode") String modelCode,
|
||||
@Param("suspiciousType") String suspiciousType
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,4 +49,18 @@ public interface ICcdiBankStatementService {
|
||||
* @return 详情
|
||||
*/
|
||||
CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId);
|
||||
|
||||
/**
|
||||
* 按涉疑交易筛选范围查询流水详情
|
||||
*
|
||||
* @param bankStatementId 流水ID
|
||||
* @param modelCode 模型编码
|
||||
* @param suspiciousType 预警类型
|
||||
* @return 详情
|
||||
*/
|
||||
CcdiBankStatementDetailVO getStatementDetail(
|
||||
Long bankStatementId,
|
||||
String modelCode,
|
||||
String suspiciousType
|
||||
);
|
||||
}
|
||||
|
||||
@@ -213,6 +213,18 @@ public interface ICcdiProjectOverviewService {
|
||||
default void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 按涉疑交易筛选范围统一导出风险明细
|
||||
*
|
||||
* @param response 响应流
|
||||
* @param queryDTO 涉疑交易筛选条件
|
||||
*/
|
||||
default void exportRiskDetails(
|
||||
HttpServletResponse response,
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 一键导出结果总览报告
|
||||
*
|
||||
|
||||
@@ -32,6 +32,8 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
|
||||
private static final Set<String> ALLOWED_TAB_TYPES = Set.of("all", "in", "out");
|
||||
private static final Set<String> ALLOWED_ORDER_DIRECTIONS = Set.of("asc", "desc");
|
||||
private static final Set<String> ALLOWED_SUSPICIOUS_TYPES =
|
||||
Set.of("ALL", "MODEL_RULE", "EXTERNAL_PERSON", "NAME_LIST");
|
||||
|
||||
@Resource
|
||||
private CcdiBankStatementMapper bankStatementMapper;
|
||||
@@ -69,13 +71,24 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
|
||||
@Override
|
||||
public CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId) {
|
||||
return getStatementDetail(bankStatementId, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiBankStatementDetailVO getStatementDetail(
|
||||
Long bankStatementId,
|
||||
String modelCode,
|
||||
String suspiciousType
|
||||
) {
|
||||
CcdiBankStatementDetailVO detail = bankStatementMapper.selectStatementDetailById(bankStatementId);
|
||||
if (detail == null || detail.getProjectId() == null || detail.getBankStatementId() == null) {
|
||||
return detail;
|
||||
}
|
||||
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(
|
||||
detail.getProjectId(),
|
||||
List.of(detail.getBankStatementId())
|
||||
detail.getProjectId(),
|
||||
List.of(detail.getBankStatementId()),
|
||||
normalizeUpperCase(modelCode),
|
||||
normalizeSuspiciousType(suspiciousType)
|
||||
);
|
||||
detail.setHitTags(new ArrayList<>(hitTagMap.getOrDefault(detail.getBankStatementId(), Collections.emptyList())));
|
||||
return detail;
|
||||
@@ -93,18 +106,33 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
if (bankStatementIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(projectId, bankStatementIds);
|
||||
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(
|
||||
projectId,
|
||||
bankStatementIds,
|
||||
null,
|
||||
null
|
||||
);
|
||||
rows.forEach(row -> row.setHitTags(new ArrayList<>(
|
||||
hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList())
|
||||
)));
|
||||
}
|
||||
|
||||
private Map<Long, List<CcdiBankStatementHitTagVO>> loadHitTagMap(Long projectId, List<Long> bankStatementIds) {
|
||||
private Map<Long, List<CcdiBankStatementHitTagVO>> loadHitTagMap(
|
||||
Long projectId,
|
||||
List<Long> bankStatementIds,
|
||||
String modelCode,
|
||||
String suspiciousType
|
||||
) {
|
||||
if (projectId == null || bankStatementIds == null || bankStatementIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<CcdiBankStatementHitTagVO> hitTags =
|
||||
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds);
|
||||
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
projectId,
|
||||
bankStatementIds,
|
||||
modelCode,
|
||||
suspiciousType
|
||||
);
|
||||
if (hitTags == null || hitTags.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
@@ -165,6 +193,16 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
return normalized == null ? null : normalized.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String normalizeUpperCase(String value) {
|
||||
String normalized = normalizeText(value);
|
||||
return normalized == null ? null : normalized.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private String normalizeSuspiciousType(String suspiciousType) {
|
||||
String normalized = normalizeUpperCase(suspiciousType);
|
||||
return normalized != null && ALLOWED_SUSPICIOUS_TYPES.contains(normalized) ? normalized : null;
|
||||
}
|
||||
|
||||
private String normalizeText(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
|
||||
@@ -375,9 +375,12 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
) {
|
||||
ensureProjectExists(queryDTO.getProjectId());
|
||||
normalizeSuspiciousTransactionQuery(queryDTO);
|
||||
if (isSuspiciousTransactionScopeMismatch(queryDTO)) {
|
||||
return emptySuspiciousTransactionPage();
|
||||
}
|
||||
prepareSuspiciousTransactionExternalBranch(queryDTO);
|
||||
|
||||
if (isExternalPersonOnlyWithoutSubject(queryDTO)) {
|
||||
if (isExternalScopeWithoutSubject(queryDTO)) {
|
||||
return emptySuspiciousTransactionPage();
|
||||
}
|
||||
|
||||
@@ -387,9 +390,12 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
);
|
||||
Page<CcdiProjectSuspiciousTransactionItemVO> resultPage =
|
||||
overviewMapper.selectSuspiciousTransactionPage(page, queryDTO);
|
||||
List<CcdiProjectSuspiciousTransactionItemVO> rows =
|
||||
defaultList(resultPage == null ? null : resultPage.getRecords());
|
||||
attachSuspiciousTransactionHitTags(rows, queryDTO);
|
||||
|
||||
CcdiProjectSuspiciousTransactionPageVO result = new CcdiProjectSuspiciousTransactionPageVO();
|
||||
result.setRows(defaultList(resultPage == null ? null : resultPage.getRecords()));
|
||||
result.setRows(rows);
|
||||
result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||
return result;
|
||||
}
|
||||
@@ -400,9 +406,12 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
) {
|
||||
ensureProjectExists(queryDTO.getProjectId());
|
||||
normalizeSuspiciousTransactionQuery(queryDTO);
|
||||
if (isSuspiciousTransactionScopeMismatch(queryDTO)) {
|
||||
return List.of();
|
||||
}
|
||||
prepareSuspiciousTransactionExternalBranch(queryDTO);
|
||||
|
||||
if (isExternalPersonOnlyWithoutSubject(queryDTO)) {
|
||||
if (isExternalScopeWithoutSubject(queryDTO)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@@ -469,9 +478,17 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||
queryDTO.setProjectId(projectId);
|
||||
queryDTO.setSuspiciousType("ALL");
|
||||
queryDTO.setSuspiciousType(SUSPICIOUS_TYPE_ALL);
|
||||
exportRiskDetails(response, queryDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportRiskDetails(
|
||||
HttpServletResponse response,
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||
) {
|
||||
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows = exportSuspiciousTransactions(queryDTO);
|
||||
Long projectId = queryDTO.getProjectId();
|
||||
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows = exportEmployeeCreditNegative(projectId);
|
||||
List<CcdiProjectAbnormalAccountExcel> abnormalRows = exportAbnormalAccountPeople(projectId);
|
||||
try {
|
||||
@@ -742,28 +759,44 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
}
|
||||
|
||||
private void prepareSuspiciousTransactionExternalBranch(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
|
||||
if (!shouldCheckExternalPersonBranch(queryDTO.getSuspiciousType())) {
|
||||
if (!shouldCheckExternalPersonBranch(queryDTO)) {
|
||||
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 shouldCheckExternalPersonBranch(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
|
||||
if (SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(queryDTO.getSuspiciousType())) {
|
||||
return true;
|
||||
}
|
||||
return SUSPICIOUS_TYPE_ALL.equals(queryDTO.getSuspiciousType())
|
||||
&& (queryDTO.getModelCode() == null || isExternalModelCode(queryDTO.getModelCode()));
|
||||
}
|
||||
|
||||
private boolean hasExternalPersonSubject(Long projectId) {
|
||||
return overviewMapper.selectExternalPersonSubjectExistsByProjectId(projectId) != null;
|
||||
}
|
||||
|
||||
private boolean isExternalPersonOnlyWithoutSubject(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
|
||||
return SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(queryDTO.getSuspiciousType())
|
||||
private boolean isExternalScopeWithoutSubject(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
|
||||
return (SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(queryDTO.getSuspiciousType())
|
||||
|| isExternalModelCode(queryDTO.getModelCode()))
|
||||
&& !Boolean.TRUE.equals(queryDTO.getIncludeExternalPerson());
|
||||
}
|
||||
|
||||
private boolean isSuspiciousTransactionScopeMismatch(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
|
||||
if (queryDTO.getModelCode() == null) {
|
||||
return false;
|
||||
}
|
||||
boolean externalModel = isExternalModelCode(queryDTO.getModelCode());
|
||||
return (SUSPICIOUS_TYPE_MODEL_RULE.equals(queryDTO.getSuspiciousType()) && externalModel)
|
||||
|| (SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(queryDTO.getSuspiciousType()) && !externalModel);
|
||||
}
|
||||
|
||||
private boolean isExternalModelCode(String modelCode) {
|
||||
return modelCode != null && modelCode.startsWith("EXTERNAL_");
|
||||
}
|
||||
|
||||
private CcdiProjectSuspiciousTransactionPageVO emptySuspiciousTransactionPage() {
|
||||
CcdiProjectSuspiciousTransactionPageVO result = new CcdiProjectSuspiciousTransactionPageVO();
|
||||
result.setRows(List.of());
|
||||
@@ -976,7 +1009,12 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
return;
|
||||
}
|
||||
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = defaultList(
|
||||
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds)
|
||||
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
projectId,
|
||||
bankStatementIds,
|
||||
null,
|
||||
null
|
||||
)
|
||||
).stream().filter(item -> item.getBankStatementId() != null)
|
||||
.collect(Collectors.groupingBy(
|
||||
CcdiBankStatementHitTagVO::getBankStatementId,
|
||||
@@ -988,6 +1026,39 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
)));
|
||||
}
|
||||
|
||||
private void attachSuspiciousTransactionHitTags(
|
||||
List<CcdiProjectSuspiciousTransactionItemVO> rows,
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||
) {
|
||||
if (rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Long> bankStatementIds = rows.stream()
|
||||
.map(CcdiProjectSuspiciousTransactionItemVO::getBankStatementId)
|
||||
.filter(item -> item != null)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (bankStatementIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = defaultList(
|
||||
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
queryDTO.getProjectId(),
|
||||
bankStatementIds,
|
||||
queryDTO.getModelCode(),
|
||||
queryDTO.getSuspiciousType()
|
||||
)
|
||||
).stream().filter(item -> item.getBankStatementId() != null)
|
||||
.collect(Collectors.groupingBy(
|
||||
CcdiBankStatementHitTagVO::getBankStatementId,
|
||||
LinkedHashMap::new,
|
||||
Collectors.toList()
|
||||
));
|
||||
rows.forEach(row -> row.setHitTags(new ArrayList<>(
|
||||
hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList())
|
||||
)));
|
||||
}
|
||||
|
||||
private void normalizeObjectRows(List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows) {
|
||||
objectRows.forEach(row -> {
|
||||
if (row.getRiskTags() == null) {
|
||||
|
||||
@@ -30,6 +30,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="CcdiBankStatementHitTagVOResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO">
|
||||
<result property="modelCode" column="model_code"/>
|
||||
<result property="ruleCode" column="rule_code"/>
|
||||
<result property="ruleName" column="rule_name"/>
|
||||
<result property="riskLevel" column="risk_level"/>
|
||||
@@ -47,6 +48,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<select id="selectStatementTagsByProjectAndStatementIds" resultMap="CcdiBankStatementHitTagVOResultMap">
|
||||
select
|
||||
model_code,
|
||||
rule_code,
|
||||
rule_name,
|
||||
risk_level,
|
||||
@@ -67,6 +69,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<foreach collection="bankStatementIds" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
<if test="modelCode != null and modelCode != ''">
|
||||
and model_code = #{modelCode}
|
||||
</if>
|
||||
<choose>
|
||||
<when test="suspiciousType == 'MODEL_RULE'">
|
||||
and left(model_code, 9) != 'EXTERNAL_'
|
||||
</when>
|
||||
<when test="suspiciousType == 'EXTERNAL_PERSON'">
|
||||
and left(model_code, 9) = 'EXTERNAL_'
|
||||
</when>
|
||||
</choose>
|
||||
order by bank_statement_id asc, id asc
|
||||
</select>
|
||||
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
<resultMap id="SuspiciousTransactionItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO">
|
||||
<id property="bankStatementId" column="bankStatementId"/>
|
||||
<result property="trxDate" column="trxDate"/>
|
||||
<result property="leAccountNo" column="leAccountNo"/>
|
||||
<result property="leAccountName" column="leAccountName"/>
|
||||
<result property="customerAccountName" column="customerAccountName"/>
|
||||
<result property="customerAccountNo" column="customerAccountNo"/>
|
||||
<result property="suspiciousPersonName" column="suspiciousPersonName"/>
|
||||
<result property="relatedPersonName" column="relatedPersonName"/>
|
||||
<result property="relatedStaffName" column="relatedStaffName"/>
|
||||
@@ -1024,9 +1028,21 @@
|
||||
and ex.exclusion_type = 'STATEMENT'
|
||||
and ex.bank_statement_id = tr.bank_statement_id
|
||||
)
|
||||
<include refid="suspiciousTransactionModelScopeSql"/>
|
||||
</sql>
|
||||
|
||||
<sql id="suspiciousTransactionModelScopeSql">
|
||||
<if test="query.modelCode != null and query.modelCode != ''">
|
||||
and tr.model_code = #{query.modelCode}
|
||||
and tr.model_code = #{query.modelCode}
|
||||
</if>
|
||||
<choose>
|
||||
<when test="query.suspiciousType == 'MODEL_RULE'">
|
||||
and left(tr.model_code, 9) != 'EXTERNAL_'
|
||||
</when>
|
||||
<when test="query.suspiciousType == 'EXTERNAL_PERSON'">
|
||||
and left(tr.model_code, 9) = 'EXTERNAL_'
|
||||
</when>
|
||||
</choose>
|
||||
</sql>
|
||||
|
||||
<sql id="suspiciousTransactionNameHitSql">
|
||||
@@ -1287,6 +1303,10 @@
|
||||
select
|
||||
final_result.bankStatementId,
|
||||
final_result.trxDate,
|
||||
bs.LE_ACCOUNT_NO as leAccountNo,
|
||||
bs.LE_ACCOUNT_NAME as leAccountName,
|
||||
bs.CUSTOMER_ACCOUNT_NAME as customerAccountName,
|
||||
bs.CUSTOMER_ACCOUNT_NO as customerAccountNo,
|
||||
final_result.suspiciousPersonName,
|
||||
final_result.relatedPersonName,
|
||||
final_result.relatedStaffName,
|
||||
@@ -1301,6 +1321,8 @@
|
||||
from (
|
||||
<include refid="suspiciousTransactionAggregatedSql"/>
|
||||
) final_result
|
||||
inner join ccdi_bank_statement bs
|
||||
on bs.bank_statement_id = final_result.bankStatementId
|
||||
<include refid="suspiciousTransactionFilterSql"/>
|
||||
order by final_result.trxDate desc, final_result.bankStatementId desc
|
||||
</select>
|
||||
@@ -1309,6 +1331,10 @@
|
||||
select
|
||||
final_result.bankStatementId,
|
||||
final_result.trxDate,
|
||||
bs.LE_ACCOUNT_NO as leAccountNo,
|
||||
bs.LE_ACCOUNT_NAME as leAccountName,
|
||||
bs.CUSTOMER_ACCOUNT_NAME as customerAccountName,
|
||||
bs.CUSTOMER_ACCOUNT_NO as customerAccountNo,
|
||||
final_result.suspiciousPersonName,
|
||||
final_result.relatedPersonName,
|
||||
final_result.relatedStaffName,
|
||||
@@ -1323,6 +1349,8 @@
|
||||
from (
|
||||
<include refid="suspiciousTransactionAggregatedSql"/>
|
||||
) final_result
|
||||
inner join ccdi_bank_statement bs
|
||||
on bs.bank_statement_id = final_result.bankStatementId
|
||||
<include refid="suspiciousTransactionFilterSql"/>
|
||||
order by final_result.trxDate desc, final_result.bankStatementId desc
|
||||
</select>
|
||||
@@ -1375,6 +1403,7 @@
|
||||
and ex.exclusion_type = 'STATEMENT'
|
||||
and ex.bank_statement_id = tr.bank_statement_id
|
||||
)
|
||||
<include refid="suspiciousTransactionModelScopeSql"/>
|
||||
group by tr.bank_statement_id
|
||||
) tag_result on tag_result.bank_statement_id = final_result.bankStatementId
|
||||
<include refid="suspiciousTransactionFilterSql"/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiBankStatementService;
|
||||
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -39,6 +40,9 @@ class CcdiBankStatementControllerTest {
|
||||
@Mock
|
||||
private ICcdiBankStatementService bankStatementService;
|
||||
|
||||
@Mock
|
||||
private CcdiProjectAccessService projectAccessService;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
@@ -80,12 +84,17 @@ class CcdiBankStatementControllerTest {
|
||||
void detail_shouldReturnAjaxResultSuccess() {
|
||||
CcdiBankStatementDetailVO detailVO = new CcdiBankStatementDetailVO();
|
||||
detailVO.setBankStatementId(1000L);
|
||||
when(bankStatementService.getStatementDetail(1000L)).thenReturn(detailVO);
|
||||
when(bankStatementService.getStatementDetail(
|
||||
1000L, "SUSPICIOUS_GAMBLING", "MODEL_RULE"
|
||||
)).thenReturn(detailVO);
|
||||
|
||||
AjaxResult result = controller.getDetail(1000L);
|
||||
AjaxResult result = controller.getDetail(1000L, "SUSPICIOUS_GAMBLING", "MODEL_RULE");
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
assertEquals(detailVO, result.get("data"));
|
||||
verify(bankStatementService).getStatementDetail(
|
||||
1000L, "SUSPICIOUS_GAMBLING", "MODEL_RULE"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -172,7 +172,7 @@ class CcdiProjectOverviewControllerContractTest {
|
||||
Method method = controllerClass.getMethod(
|
||||
"exportRiskDetails",
|
||||
HttpServletResponse.class,
|
||||
Long.class
|
||||
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO")
|
||||
);
|
||||
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||
Operation operation = method.getAnnotation(Operation.class);
|
||||
@@ -198,7 +198,7 @@ class CcdiProjectOverviewControllerContractTest {
|
||||
assertEquals("/report/export", requestMapping.value()[0]);
|
||||
assertEquals(List.of(RequestMethod.GET, RequestMethod.POST), Arrays.asList(requestMapping.method()));
|
||||
assertNotNull(operation);
|
||||
assertEquals("一键导出结果总览报告", operation.summary());
|
||||
assertEquals("导出结果总览报告", operation.summary());
|
||||
assertNotNull(preAuthorize);
|
||||
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||
}
|
||||
@@ -211,7 +211,10 @@ class CcdiProjectOverviewControllerContractTest {
|
||||
.map(Field::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertEquals(List.of("projectId", "suspiciousType", "pageNum", "pageSize"), fieldNames);
|
||||
assertEquals(
|
||||
List.of("projectId", "modelCode", "suspiciousType", "pageNum", "pageSize", "includeExternalPerson"),
|
||||
fieldNames
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
||||
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -52,6 +53,9 @@ class CcdiProjectOverviewControllerTest {
|
||||
@Mock
|
||||
private ICcdiProjectOverviewService overviewService;
|
||||
|
||||
@Mock
|
||||
private CcdiProjectAccessService projectAccessService;
|
||||
|
||||
@Test
|
||||
void shouldExposeDashboardEndpoint() throws Exception {
|
||||
when(overviewService.getDashboard(40L)).thenReturn(new CcdiProjectOverviewDashboardVO());
|
||||
@@ -308,15 +312,20 @@ class CcdiProjectOverviewControllerTest {
|
||||
@Test
|
||||
void shouldExposeRiskDetailsExportEndpoint() throws Exception {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO =
|
||||
new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||
queryDTO.setProjectId(40L);
|
||||
queryDTO.setModelCode("SUSPICIOUS_GAMBLING");
|
||||
queryDTO.setSuspiciousType("MODEL_RULE");
|
||||
|
||||
controller.exportRiskDetails(response, 40L);
|
||||
controller.exportRiskDetails(response, queryDTO);
|
||||
|
||||
verify(overviewService).exportRiskDetails(same(response), same(40L));
|
||||
verify(overviewService).exportRiskDetails(same(response), same(queryDTO));
|
||||
|
||||
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||
"exportRiskDetails",
|
||||
jakarta.servlet.http.HttpServletResponse.class,
|
||||
Long.class
|
||||
CcdiProjectSuspiciousTransactionQueryDTO.class
|
||||
);
|
||||
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||
Operation operation = method.getAnnotation(Operation.class);
|
||||
|
||||
@@ -32,6 +32,7 @@ class CcdiBankTagResultMapperXmlTest {
|
||||
.orElse(null);
|
||||
|
||||
assertNotNull(method, "应提供按项目和流水ID批量查询异常标签的方法");
|
||||
assertTrue(method.getParameterCount() == 4, "标签批量查询应接收模型和预警类型范围");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -42,6 +43,9 @@ class CcdiBankTagResultMapperXmlTest {
|
||||
assertTrue(xml.contains("selectStatementTagsByProjectAndStatementIds"), xml);
|
||||
assertTrue(xml.contains("bank_statement_id IN"), xml);
|
||||
assertTrue(xml.contains("project_id = #{projectId}"), xml);
|
||||
assertTrue(xml.contains("model_code = #{modelCode}"), xml);
|
||||
assertTrue(xml.contains("left(model_code, 9) != 'EXTERNAL_'"), xml);
|
||||
assertTrue(xml.contains("left(model_code, 9) = 'EXTERNAL_'"), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ 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 modelScopeSql = extractSqlFragment(xml, "suspiciousTransactionModelScopeSql");
|
||||
String mergedSql = extractSqlFragment(xml, "suspiciousTransactionMergedSql");
|
||||
String aggregatedSql = extractSqlFragment(xml, "suspiciousTransactionAggregatedSql");
|
||||
String externalSubjectExistsSql = extractSelect(xml, "selectExternalPersonSubjectExistsByProjectId");
|
||||
@@ -99,6 +100,10 @@ class CcdiProjectOverviewMapperSqlTest {
|
||||
assertTrue(modelHitSql.contains("tr.bank_statement_id is not null"), modelHitSql);
|
||||
assertFalse(modelHitSql.contains("rule_name like '%可疑%'"), modelHitSql);
|
||||
assertFalse(modelHitSql.contains("ABNORMAL_CUSTOMER_TRANSACTION"), modelHitSql);
|
||||
assertTrue(modelHitSql.contains("suspiciousTransactionModelScopeSql"), modelHitSql);
|
||||
assertTrue(modelScopeSql.contains("tr.model_code = #{query.modelCode}"), modelScopeSql);
|
||||
assertTrue(modelScopeSql.contains("left(tr.model_code, 9) != 'EXTERNAL_'"), modelScopeSql);
|
||||
assertTrue(modelScopeSql.contains("left(tr.model_code, 9) = 'EXTERNAL_'"), modelScopeSql);
|
||||
assertTrue(suspiciousSql.contains("ccdi_biz_intermediary"), suspiciousSql);
|
||||
assertTrue(suspiciousSql.contains("ccdi_enterprise_base_info"), suspiciousSql);
|
||||
assertTrue(suspiciousSql.contains("group by merged.bankStatementId"), suspiciousSql);
|
||||
@@ -106,6 +111,9 @@ class CcdiProjectOverviewMapperSqlTest {
|
||||
assertTrue(suspiciousSql.contains("hasModelRuleHit"), suspiciousSql);
|
||||
assertTrue(suspiciousSql.contains("hasNameListHit"), suspiciousSql);
|
||||
assertTrue(suspiciousSql.contains("final_result.nameListHitType"), suspiciousSql);
|
||||
assertTrue(suspiciousSql.contains("bs.LE_ACCOUNT_NO as leAccountNo"), suspiciousSql);
|
||||
assertTrue(suspiciousSql.contains("bs.CUSTOMER_ACCOUNT_NO as customerAccountNo"), suspiciousSql);
|
||||
assertTrue(suspiciousSql.contains("bs.bank_statement_id = final_result.bankStatementId"), suspiciousSql);
|
||||
assertTrue(mergedSql.contains("<if test=\"query.includeExternalPerson == true\">"), mergedSql);
|
||||
assertTrue(mergedSql.contains("<include refid=\"externalSuspiciousTransactionSql\"/>"), mergedSql);
|
||||
assertTrue(externalSubjectExistsSql.contains("limit 1"), externalSubjectExistsSql);
|
||||
@@ -117,6 +125,7 @@ class CcdiProjectOverviewMapperSqlTest {
|
||||
assertTrue(reportSuspiciousSql.contains("疑似与中介往来"), reportSuspiciousSql);
|
||||
assertTrue(reportSuspiciousSql.contains("final_result.nameListHitType = '信贷客户'"), reportSuspiciousSql);
|
||||
assertTrue(reportSuspiciousSql.contains("与信贷客户之间非正常资金往来"), reportSuspiciousSql);
|
||||
assertTrue(reportSuspiciousSql.contains("suspiciousTransactionModelScopeSql"), reportSuspiciousSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -129,7 +129,9 @@ class CcdiBankStatementServiceImplTest {
|
||||
CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
|
||||
hitTag.setBankStatementId(51274L);
|
||||
hitTag.setRuleName("大额存现交易");
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(51274L)))
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
43L, List.of(51274L), null, null
|
||||
))
|
||||
.thenReturn(List.of(hitTag));
|
||||
|
||||
List<CcdiBankStatementExcel> result = service.selectStatementListForExport(queryDTO);
|
||||
@@ -152,7 +154,9 @@ class CcdiBankStatementServiceImplTest {
|
||||
hitTag.setBankStatementId(51274L);
|
||||
hitTag.setRuleCode("LARGE_CASH_DEPOSIT");
|
||||
hitTag.setRuleName("大额存现交易");
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(51274L)))
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
43L, List.of(51274L), null, null
|
||||
))
|
||||
.thenReturn(List.of(hitTag));
|
||||
|
||||
Page<CcdiBankStatementListVO> result = service.selectStatementPage(page, queryDTO);
|
||||
@@ -170,7 +174,9 @@ class CcdiBankStatementServiceImplTest {
|
||||
CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
|
||||
hitTag.setBankStatementId(200L);
|
||||
hitTag.setRuleName("大额存现交易");
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(200L)))
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
43L, List.of(200L), null, null
|
||||
))
|
||||
.thenReturn(List.of(hitTag));
|
||||
|
||||
CcdiBankStatementDetailVO result = service.getStatementDetail(200L);
|
||||
@@ -179,4 +185,21 @@ class CcdiBankStatementServiceImplTest {
|
||||
assertEquals(1, result.getHitTags().size());
|
||||
assertEquals("大额存现交易", result.getHitTags().get(0).getRuleName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStatementDetail_shouldApplySuspiciousTransactionScope() {
|
||||
CcdiBankStatementDetailVO detailVO = new CcdiBankStatementDetailVO();
|
||||
detailVO.setBankStatementId(200L);
|
||||
detailVO.setProjectId(43L);
|
||||
when(bankStatementMapper.selectStatementDetailById(200L)).thenReturn(detailVO);
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
43L, List.of(200L), "SUSPICIOUS_GAMBLING", "MODEL_RULE"
|
||||
)).thenReturn(List.of());
|
||||
|
||||
service.getStatementDetail(200L, "suspicious_gambling", "model_rule");
|
||||
|
||||
verify(bankTagResultMapper).selectStatementTagsByProjectAndStatementIds(
|
||||
43L, List.of(200L), "SUSPICIOUS_GAMBLING", "MODEL_RULE"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +387,9 @@ class CcdiProjectOverviewServiceImplTest {
|
||||
hitTag.setRuleCode("RULE_A");
|
||||
hitTag.setRuleName("大额转账");
|
||||
hitTag.setRiskLevel("HIGH");
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(40L, List.of(1L)))
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
40L, List.of(1L), null, null
|
||||
))
|
||||
.thenReturn(List.of(hitTag));
|
||||
|
||||
CcdiProjectPersonAnalysisObjectRecordVO objectRow = new CcdiProjectPersonAnalysisObjectRecordVO();
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||
@@ -120,6 +121,71 @@ class CcdiProjectOverviewServiceSuspiciousTransactionTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipExternalBranchAndAttachSelectedEmployeeModelTags() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(40L);
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
|
||||
CcdiProjectSuspiciousTransactionItemVO item = new CcdiProjectSuspiciousTransactionItemVO();
|
||||
item.setBankStatementId(101L);
|
||||
Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(1, 10);
|
||||
page.setRecords(List.of(item));
|
||||
page.setTotal(1);
|
||||
when(overviewMapper.selectSuspiciousTransactionPage(
|
||||
any(Page.class), any(CcdiProjectSuspiciousTransactionQueryDTO.class)
|
||||
)).thenReturn(page);
|
||||
|
||||
CcdiBankStatementHitTagVO tag = new CcdiBankStatementHitTagVO();
|
||||
tag.setBankStatementId(101L);
|
||||
tag.setModelCode("SUSPICIOUS_GAMBLING");
|
||||
tag.setRuleName("疑似赌博");
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
|
||||
40L, List.of(101L), "SUSPICIOUS_GAMBLING", "ALL"
|
||||
)).thenReturn(List.of(tag));
|
||||
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||
queryDTO.setProjectId(40L);
|
||||
queryDTO.setModelCode("suspicious_gambling");
|
||||
queryDTO.setSuspiciousType("all");
|
||||
|
||||
CcdiProjectSuspiciousTransactionPageVO result = service.getSuspiciousTransactions(queryDTO);
|
||||
|
||||
assertEquals("疑似赌博", result.getRows().getFirst().getHitTags().getFirst().getRuleName());
|
||||
verify(overviewMapper, never()).selectExternalPersonSubjectExistsByProjectId(40L);
|
||||
verify(overviewMapper).selectSuspiciousTransactionPage(
|
||||
any(Page.class),
|
||||
argThat(query -> !Boolean.TRUE.equals(query.getIncludeExternalPerson()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepEmployeeRuleScopeSeparateFromExternalModels() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(40L);
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
|
||||
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);
|
||||
queryDTO.setSuspiciousType("MODEL_RULE");
|
||||
|
||||
service.getSuspiciousTransactions(queryDTO);
|
||||
|
||||
verify(overviewMapper, never()).selectExternalPersonSubjectExistsByProjectId(40L);
|
||||
verify(overviewMapper).selectSuspiciousTransactionPage(
|
||||
any(Page.class),
|
||||
argThat(query -> "MODEL_RULE".equals(query.getSuspiciousType())
|
||||
&& !Boolean.TRUE.equals(query.getIncludeExternalPerson()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyExternalSuspiciousTransactionsWhenExternalSubjectDoesNotExist() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
|
||||
Reference in New Issue
Block a user