完善外部人员预警与项目分析上线内容
This commit is contained in:
@@ -2,14 +2,20 @@ package com.ruoyi.ccdi.project.controller;
|
||||
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskModelPeopleExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
||||
@@ -67,6 +73,28 @@ public class CcdiProjectOverviewController extends BaseController {
|
||||
return AjaxResult.success(overview);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询外部人员预警
|
||||
*/
|
||||
@GetMapping("/external-persons")
|
||||
@Operation(summary = "查询外部人员预警")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public AjaxResult getExternalPersons(CcdiProjectExternalPersonQueryDTO queryDTO) {
|
||||
CcdiProjectExternalPersonWarningVO warnings = overviewService.getExternalPersonWarnings(queryDTO);
|
||||
return AjaxResult.success(warnings);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询外部人员风险汇总
|
||||
*/
|
||||
@GetMapping("/external-persons/summary")
|
||||
@Operation(summary = "查询外部人员风险汇总")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public AjaxResult getExternalRiskSummary(Long projectId) {
|
||||
CcdiProjectExternalRiskSummaryVO summary = overviewService.getExternalRiskSummary(projectId);
|
||||
return AjaxResult.success(summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询中高风险人员TOP10
|
||||
*/
|
||||
@@ -100,6 +128,28 @@ public class CcdiProjectOverviewController extends BaseController {
|
||||
return AjaxResult.success(people);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询外部人员风险模型卡片
|
||||
*/
|
||||
@GetMapping("/external-risk-models/cards")
|
||||
@Operation(summary = "查询外部人员风险模型卡片")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public AjaxResult getExternalRiskModelCards(Long projectId) {
|
||||
CcdiProjectRiskModelCardsVO cards = overviewService.getExternalRiskModelCards(projectId);
|
||||
return AjaxResult.success(cards);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询外部人员风险模型命中人员
|
||||
*/
|
||||
@GetMapping("/external-risk-models/people")
|
||||
@Operation(summary = "查询外部人员风险模型命中人员")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public AjaxResult getExternalRiskModelPeople(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
|
||||
CcdiProjectRiskModelPeopleVO people = overviewService.getExternalRiskModelPeople(queryDTO);
|
||||
return AjaxResult.success(people);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询项目分析详情
|
||||
*/
|
||||
@@ -173,6 +223,48 @@ public class CcdiProjectOverviewController extends BaseController {
|
||||
util.exportExcel(response, rows, "风险人员总览");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出外部人员预警
|
||||
*/
|
||||
@PostMapping("/external-persons/export")
|
||||
@Operation(summary = "导出外部人员预警")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public void exportExternalPersons(HttpServletResponse response, Long projectId) {
|
||||
List<CcdiProjectExternalPersonWarningExcel> rows = overviewService.exportExternalPersonWarnings(projectId);
|
||||
ExcelUtil<CcdiProjectExternalPersonWarningExcel> util =
|
||||
new ExcelUtil<>(CcdiProjectExternalPersonWarningExcel.class);
|
||||
util.exportExcel(response, rows, "外部人员预警");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出风险模型命中人员
|
||||
*/
|
||||
@PostMapping("/risk-models/people/export")
|
||||
@Operation(summary = "导出风险模型命中人员")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public void exportRiskModelPeople(HttpServletResponse response, CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
|
||||
List<CcdiProjectRiskModelPeopleExcel> rows = overviewService.exportRiskModelPeople(queryDTO);
|
||||
ExcelUtil<CcdiProjectRiskModelPeopleExcel> util =
|
||||
new ExcelUtil<>(CcdiProjectRiskModelPeopleExcel.class);
|
||||
util.exportExcel(response, rows, "风险模型命中人员");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出外部人员风险模型命中人员
|
||||
*/
|
||||
@PostMapping("/external-risk-models/people/export")
|
||||
@Operation(summary = "导出外部人员风险模型命中人员")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public void exportExternalRiskModelPeople(
|
||||
HttpServletResponse response,
|
||||
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
|
||||
) {
|
||||
List<CcdiProjectRiskModelPeopleExcel> rows = overviewService.exportExternalRiskModelPeople(queryDTO);
|
||||
ExcelUtil<CcdiProjectRiskModelPeopleExcel> util =
|
||||
new ExcelUtil<>(CcdiProjectRiskModelPeopleExcel.class);
|
||||
util.exportExcel(response, rows, "外部人员风险模型命中人员");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出风险明细
|
||||
*/
|
||||
|
||||
@@ -40,6 +40,9 @@ public class CcdiBankStatementQueryDTO {
|
||||
/** 本方主体 */
|
||||
private List<String> ourSubjects;
|
||||
|
||||
/** 本方证件号 */
|
||||
private List<String> ourCertNos;
|
||||
|
||||
/** 本方银行 */
|
||||
private List<String> ourBanks;
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.ccdi.project.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 外部人员预警查询DTO
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectExternalPersonQueryDTO {
|
||||
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 页码 */
|
||||
private Integer pageNum;
|
||||
|
||||
/** 每页数量 */
|
||||
private Integer pageSize;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.ruoyi.ccdi.project.domain.dto;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 外部人员模型命中人员查询DTO
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectExternalRiskModelPeopleQueryDTO {
|
||||
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 模型编码 */
|
||||
private List<String> modelCodes;
|
||||
|
||||
/** 匹配方式 */
|
||||
private String matchMode;
|
||||
|
||||
/** 关键字 */
|
||||
private String keyword;
|
||||
|
||||
/** 页码 */
|
||||
private Integer pageNum;
|
||||
|
||||
/** 每页数量 */
|
||||
private Integer pageSize;
|
||||
|
||||
public String getModelCodesCsv() {
|
||||
if (modelCodes == null || modelCodes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return modelCodes.stream()
|
||||
.filter(item -> item != null && !item.isBlank())
|
||||
.map(String::trim)
|
||||
.distinct()
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ruoyi.ccdi.project.domain.excel;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 外部人员预警导出对象
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectExternalPersonWarningExcel {
|
||||
|
||||
@Excel(name = "姓名")
|
||||
private String name;
|
||||
|
||||
@Excel(name = "证件号")
|
||||
private String idNo;
|
||||
|
||||
@Excel(name = "主体类型")
|
||||
private String subjectType;
|
||||
|
||||
@Excel(name = "风险等级")
|
||||
private String riskLevel;
|
||||
|
||||
@Excel(name = "命中模型数")
|
||||
private Integer modelCount;
|
||||
|
||||
@Excel(name = "核心异常点")
|
||||
private String riskPoint;
|
||||
|
||||
@Excel(name = "涉及对象")
|
||||
private String relatedObject;
|
||||
|
||||
@Excel(name = "最近交易时间")
|
||||
private String latestTradeTime;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.ccdi.project.domain.excel;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 风险模型命中人员导出对象
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectRiskModelPeopleExcel {
|
||||
|
||||
@Excel(name = "风险主体")
|
||||
private String personName;
|
||||
|
||||
@Excel(name = "主体类型")
|
||||
private String subjectType;
|
||||
|
||||
@Excel(name = "证件号")
|
||||
private String idNo;
|
||||
|
||||
@Excel(name = "部门/涉及对象")
|
||||
private String scopeName;
|
||||
|
||||
@Excel(name = "命中模型")
|
||||
private String modelNames;
|
||||
|
||||
@Excel(name = "异常标签")
|
||||
private String hitTags;
|
||||
}
|
||||
@@ -14,20 +14,29 @@ public class CcdiProjectSuspiciousTransactionExcel {
|
||||
@Excel(name = "交易时间")
|
||||
private String trxDate;
|
||||
|
||||
@Excel(name = "可疑人员")
|
||||
private String suspiciousPersonName;
|
||||
@Excel(name = "本方账户")
|
||||
private String leAccountNo;
|
||||
|
||||
@Excel(name = "关联人")
|
||||
private String relatedPersonName;
|
||||
@Excel(name = "本方主体")
|
||||
private String leAccountName;
|
||||
|
||||
@Excel(name = "对方名称")
|
||||
private String customerAccountName;
|
||||
|
||||
@Excel(name = "对方账户")
|
||||
private String customerAccountNo;
|
||||
|
||||
@Excel(name = "关联员工")
|
||||
private String relatedStaffDisplay;
|
||||
|
||||
@Excel(name = "关系")
|
||||
private String relationType;
|
||||
@Excel(name = "摘要")
|
||||
private String userMemo;
|
||||
|
||||
@Excel(name = "摘要/交易类型")
|
||||
private String summaryAndCashType;
|
||||
@Excel(name = "交易类型")
|
||||
private String cashType;
|
||||
|
||||
@Excel(name = "异常标签")
|
||||
private String hitTags;
|
||||
|
||||
@Excel(name = "交易金额")
|
||||
private BigDecimal displayAmount;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 外部人员预警项
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectExternalPersonWarningItemVO {
|
||||
|
||||
private String name;
|
||||
|
||||
private String idNo;
|
||||
|
||||
private String subjectType;
|
||||
|
||||
private String riskLevel;
|
||||
|
||||
private String riskLevelType;
|
||||
|
||||
private Integer riskCount;
|
||||
|
||||
private Integer modelCount;
|
||||
|
||||
private String riskPoint;
|
||||
|
||||
private String relatedObject;
|
||||
|
||||
private String latestTradeTime;
|
||||
|
||||
private List<CcdiProjectRiskHitTagVO> riskPointTagList;
|
||||
|
||||
private String actionLabel;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 外部人员预警分页
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectExternalPersonWarningVO {
|
||||
|
||||
private List<CcdiProjectExternalPersonWarningItemVO> rows;
|
||||
|
||||
private Long total;
|
||||
|
||||
private Long pageNum;
|
||||
|
||||
private Long pageSize;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 外部人员风险等级汇总
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectExternalRiskSummaryVO {
|
||||
|
||||
private Integer total;
|
||||
|
||||
private Integer high;
|
||||
|
||||
private Integer medium;
|
||||
|
||||
private Integer low;
|
||||
|
||||
private Integer noRisk;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.project.domain.vo;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
@@ -21,10 +22,16 @@ public class CcdiProjectOverviewReportVO {
|
||||
|
||||
private CcdiProjectOverviewDashboardVO dashboard = new CcdiProjectOverviewDashboardVO();
|
||||
|
||||
private CcdiProjectExternalRiskSummaryVO externalRiskSummary = new CcdiProjectExternalRiskSummaryVO();
|
||||
|
||||
private List<CcdiProjectOverviewReportModelSummaryVO> modelSummaries = new ArrayList<>();
|
||||
|
||||
private List<CcdiProjectOverviewReportModelSummaryVO> externalModelSummaries = new ArrayList<>();
|
||||
|
||||
private List<CcdiProjectRiskModelPeopleItemVO> riskPeople = new ArrayList<>();
|
||||
|
||||
private List<CcdiProjectExternalPersonWarningExcel> externalPersonWarnings = new ArrayList<>();
|
||||
|
||||
private List<CcdiProjectOverviewReportSuspiciousTransactionVO> suspiciousTransactions = new ArrayList<>();
|
||||
|
||||
private List<CcdiProjectEmployeeCreditNegativeExcel> illegalPeople = new ArrayList<>();
|
||||
|
||||
@@ -13,4 +13,8 @@ public class CcdiProjectOverviewStatVO {
|
||||
private String label;
|
||||
|
||||
private Integer value;
|
||||
|
||||
private Integer employeeValue;
|
||||
|
||||
private Integer externalValue;
|
||||
}
|
||||
|
||||
@@ -90,6 +90,36 @@ public interface CcdiBankTagAnalysisMapper {
|
||||
List<BankTagStatementHitVO> selectLargeTransferStatements(@Param("projectId") Long projectId,
|
||||
@Param("threshold") BigDecimal threshold);
|
||||
|
||||
/**
|
||||
* 外部人员单笔大额交易
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param threshold 单笔大额阈值
|
||||
* @return 流水命中结果
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectExternalSingleLargeAmountStatements(@Param("projectId") Long projectId,
|
||||
@Param("threshold") BigDecimal threshold);
|
||||
|
||||
/**
|
||||
* 外部人员累计交易超限
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param threshold 累计交易阈值
|
||||
* @return 对象命中结果
|
||||
*/
|
||||
List<BankTagObjectHitVO> selectExternalCumulativeTransactionAmountObjects(@Param("projectId") Long projectId,
|
||||
@Param("threshold") BigDecimal threshold);
|
||||
|
||||
/**
|
||||
* 外部人员年流水超限
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param threshold 年流水阈值
|
||||
* @return 对象命中结果
|
||||
*/
|
||||
List<BankTagObjectHitVO> selectExternalAnnualTurnoverObjects(@Param("projectId") Long projectId,
|
||||
@Param("threshold") BigDecimal threshold);
|
||||
|
||||
/**
|
||||
* 与客户之间非正常资金往来
|
||||
*
|
||||
@@ -126,6 +156,26 @@ public interface CcdiBankTagAnalysisMapper {
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectGamblingSensitiveKeywordStatements(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 外部人员疑似赌博摘要
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 流水命中结果
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectExternalGamblingMemoStatements(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 外部人员同日多对手方疑似赌博交易
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param amountMinThreshold 可疑金额下限
|
||||
* @param amountMaxThreshold 可疑金额上限
|
||||
* @return 对象命中结果
|
||||
*/
|
||||
List<BankTagObjectHitVO> selectExternalMultiPartyGamblingTransferObjects(@Param("projectId") Long projectId,
|
||||
@Param("amountMinThreshold") BigDecimal amountMinThreshold,
|
||||
@Param("amountMaxThreshold") BigDecimal amountMaxThreshold);
|
||||
|
||||
/**
|
||||
* 特殊金额交易
|
||||
*
|
||||
@@ -134,6 +184,22 @@ public interface CcdiBankTagAnalysisMapper {
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectSpecialAmountTransactionStatements(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 外部人员与员工或员工亲属交易
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 流水命中结果
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectExternalToStaffOrFamilyTransactionStatements(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 外部人员夜间交易
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 流水命中结果
|
||||
*/
|
||||
List<BankTagStatementHitVO> selectExternalNightTransactionStatements(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 月度固定收入疑似兼职
|
||||
*
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||
@@ -11,6 +13,8 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
|
||||
@@ -121,6 +125,88 @@ public interface CcdiProjectOverviewMapper {
|
||||
@Param("query") CcdiProjectRiskModelPeopleQueryDTO query
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询风险模型命中人员导出列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 命中人员列表
|
||||
*/
|
||||
List<CcdiProjectRiskModelPeopleItemVO> selectRiskModelPeopleList(
|
||||
@Param("query") CcdiProjectRiskModelPeopleQueryDTO query
|
||||
);
|
||||
|
||||
/**
|
||||
* 分页查询外部人员预警
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param query 查询条件
|
||||
* @return 外部人员预警分页
|
||||
*/
|
||||
Page<CcdiProjectExternalPersonWarningItemVO> selectExternalPersonWarningPage(
|
||||
Page<CcdiProjectExternalPersonWarningItemVO> page,
|
||||
@Param("query") CcdiProjectExternalPersonQueryDTO query
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询外部人员预警导出列表
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 外部人员预警列表
|
||||
*/
|
||||
List<CcdiProjectExternalPersonWarningItemVO> selectExternalPersonWarningList(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 查询外部人员风险等级汇总
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 外部人员风险等级汇总
|
||||
*/
|
||||
CcdiProjectExternalRiskSummaryVO selectExternalRiskSummaryByProjectId(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 查询外部人员预警模型卡片
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 模型卡片
|
||||
*/
|
||||
List<CcdiProjectRiskModelCardVO> selectExternalRiskModelCardsByProjectId(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 分页查询外部人员模型命中人员
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param query 查询条件
|
||||
* @return 命中人员分页
|
||||
*/
|
||||
Page<CcdiProjectRiskModelPeopleItemVO> selectExternalRiskModelPeoplePage(
|
||||
Page<CcdiProjectRiskModelPeopleItemVO> page,
|
||||
@Param("query") CcdiProjectExternalRiskModelPeopleQueryDTO query
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询外部人员模型命中人员导出列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 命中人员列表
|
||||
*/
|
||||
List<CcdiProjectRiskModelPeopleItemVO> selectExternalRiskModelPeopleList(
|
||||
@Param("query") CcdiProjectExternalRiskModelPeopleQueryDTO query
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询外部人员命中标签
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param certNo 外部人员证件号
|
||||
* @param selectedModelCodes 已选模型编码CSV,可为空
|
||||
* @return 命中标签列表
|
||||
*/
|
||||
List<CcdiProjectRiskHitTagVO> selectExternalRiskHitTagsByScope(
|
||||
@Param("projectId") Long projectId,
|
||||
@Param("certNo") String certNo,
|
||||
@Param("selectedModelCodes") String selectedModelCodes
|
||||
);
|
||||
|
||||
/**
|
||||
* 分页查询涉疑交易明细
|
||||
*
|
||||
@@ -240,4 +326,5 @@ public interface CcdiProjectOverviewMapper {
|
||||
* @return 风险人数汇总
|
||||
*/
|
||||
Map<String, Object> selectRiskCountSummaryByProjectId(@Param("projectId") Long projectId);
|
||||
|
||||
}
|
||||
|
||||
@@ -2,16 +2,22 @@ package com.ruoyi.ccdi.project.service;
|
||||
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskModelPeopleExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
||||
@@ -82,6 +88,80 @@ public interface ICcdiProjectOverviewService {
|
||||
return new CcdiProjectRiskModelPeopleVO();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出风险模型命中人员
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 导出列表
|
||||
*/
|
||||
default List<CcdiProjectRiskModelPeopleExcel> exportRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询外部人员预警
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 外部人员预警
|
||||
*/
|
||||
default CcdiProjectExternalPersonWarningVO getExternalPersonWarnings(CcdiProjectExternalPersonQueryDTO queryDTO) {
|
||||
return new CcdiProjectExternalPersonWarningVO();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询外部人员风险等级汇总
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 外部人员风险等级汇总
|
||||
*/
|
||||
default CcdiProjectExternalRiskSummaryVO getExternalRiskSummary(Long projectId) {
|
||||
return new CcdiProjectExternalRiskSummaryVO();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出外部人员预警
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 导出列表
|
||||
*/
|
||||
default List<CcdiProjectExternalPersonWarningExcel> exportExternalPersonWarnings(Long projectId) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询外部人员风险模型卡片
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 风险模型卡片
|
||||
*/
|
||||
default CcdiProjectRiskModelCardsVO getExternalRiskModelCards(Long projectId) {
|
||||
return new CcdiProjectRiskModelCardsVO();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询外部人员风险模型命中人员
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 命中人员
|
||||
*/
|
||||
default CcdiProjectRiskModelPeopleVO getExternalRiskModelPeople(
|
||||
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
|
||||
) {
|
||||
return new CcdiProjectRiskModelPeopleVO();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出外部人员风险模型命中人员
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 导出列表
|
||||
*/
|
||||
default List<CcdiProjectRiskModelPeopleExcel> exportExternalRiskModelPeople(
|
||||
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
|
||||
) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询涉疑交易明细
|
||||
*
|
||||
|
||||
@@ -27,10 +27,13 @@ public class BankTagRuleConfigResolver {
|
||||
private static final Map<String, Set<String>> RULE_PARAM_MAPPING = Map.ofEntries(
|
||||
Map.entry("SINGLE_LARGE_INCOME", Set.of("SINGLE_TRANSACTION_AMOUNT")),
|
||||
Map.entry("CUMULATIVE_INCOME", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")),
|
||||
Map.entry("EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")),
|
||||
Map.entry("ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")),
|
||||
Map.entry("EXTERNAL_ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")),
|
||||
Map.entry("LARGE_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT")),
|
||||
Map.entry("FREQUENT_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT", "FREQUENT_CASH_DEPOSIT")),
|
||||
Map.entry("LARGE_TRANSFER", Set.of("FREQUENT_TRANSFER")),
|
||||
Map.entry("EXTERNAL_SINGLE_LARGE_AMOUNT", Set.of("FREQUENT_TRANSFER")),
|
||||
Map.entry("FOREX_BUY_AMT", Set.of("SINGLE_PURCHASE_AMOUNT")),
|
||||
Map.entry("FOREX_SELL_AMT", Set.of("SINGLE_SETTLEMENT_AMOUNT")),
|
||||
Map.entry("WITHDRAW_CNT", Set.of("WITHDRAW_CNT")),
|
||||
@@ -38,10 +41,18 @@ public class BankTagRuleConfigResolver {
|
||||
Map.entry("STOCK_TFR_LARGE", Set.of("STOCK_TFR_LARGE")),
|
||||
Map.entry("LARGE_STOCK_TRADING", Set.of("STOCK_TFR_LARGE")),
|
||||
Map.entry("MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")),
|
||||
Map.entry("EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")),
|
||||
Map.entry("MONTHLY_FIXED_INCOME", Set.of("MONTHLY_FIXED_INCOME")),
|
||||
Map.entry("FIXED_COUNTERPARTY_TRANSFER", Set.of("FIXED_COUNTERPARTY_TRANSFER_MIN", "FIXED_COUNTERPARTY_TRANSFER_MAX"))
|
||||
);
|
||||
|
||||
private static final Map<String, String> RULE_PARAM_MODEL_MAPPING = Map.of(
|
||||
"EXTERNAL_SINGLE_LARGE_AMOUNT", "LARGE_TRANSACTION",
|
||||
"EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT", "LARGE_TRANSACTION",
|
||||
"EXTERNAL_ANNUAL_TURNOVER", "LARGE_TRANSACTION",
|
||||
"EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER", "SUSPICIOUS_GAMBLING"
|
||||
);
|
||||
|
||||
@Resource
|
||||
private CcdiProjectMapper projectMapper;
|
||||
|
||||
@@ -69,12 +80,13 @@ public class BankTagRuleConfigResolver {
|
||||
}
|
||||
|
||||
Long effectiveProjectId = "default".equals(project.getConfigType()) ? 0L : projectId;
|
||||
List<CcdiModelParam> params = modelParamMapper.selectByProjectAndModel(effectiveProjectId, ruleMeta.getModelCode());
|
||||
String paramModelCode = RULE_PARAM_MODEL_MAPPING.getOrDefault(ruleMeta.getRuleCode(), ruleMeta.getModelCode());
|
||||
List<CcdiModelParam> params = modelParamMapper.selectByProjectAndModel(effectiveProjectId, paramModelCode);
|
||||
|
||||
Map<String, String> thresholdValues = new LinkedHashMap<>();
|
||||
Set<String> requiredParamCodes = RULE_PARAM_MAPPING.getOrDefault(ruleMeta.getRuleCode(), Set.of());
|
||||
log.info("【流水标签】解析规则参数: projectId={}, effectiveProjectId={}, ruleCode={}, requiredParams={}",
|
||||
projectId, effectiveProjectId, ruleMeta.getRuleCode(), requiredParamCodes);
|
||||
log.info("【流水标签】解析规则参数: projectId={}, effectiveProjectId={}, ruleCode={}, paramModelCode={}, requiredParams={}",
|
||||
projectId, effectiveProjectId, ruleMeta.getRuleCode(), paramModelCode, requiredParamCodes);
|
||||
for (CcdiModelParam param : params) {
|
||||
if (requiredParamCodes.contains(param.getParamCode())) {
|
||||
thresholdValues.put(param.getParamCode(), param.getParamValue());
|
||||
|
||||
@@ -225,9 +225,15 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
case "LARGE_TRANSFER" -> analysisMapper.selectLargeTransferStatements(
|
||||
projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER"))
|
||||
);
|
||||
case "EXTERNAL_SINGLE_LARGE_AMOUNT" -> analysisMapper.selectExternalSingleLargeAmountStatements(
|
||||
projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER"))
|
||||
);
|
||||
case "ABNORMAL_CUSTOMER_TRANSACTION" -> analysisMapper.selectAbnormalCustomerTransactionStatements(projectId);
|
||||
case "EXTERNAL_NIGHT_TRANSACTION" -> analysisMapper.selectExternalNightTransactionStatements(projectId);
|
||||
case "GAMBLING_SENSITIVE_KEYWORD" -> analysisMapper.selectGamblingSensitiveKeywordStatements(projectId);
|
||||
case "EXTERNAL_GAMBLING_MEMO" -> analysisMapper.selectExternalGamblingMemoStatements(projectId);
|
||||
case "SPECIAL_AMOUNT_TRANSACTION" -> analysisMapper.selectSpecialAmountTransactionStatements(projectId);
|
||||
case "EXTERNAL_TO_STAFF_FAMILY_TRANSACTION" -> analysisMapper.selectExternalToStaffOrFamilyTransactionStatements(projectId);
|
||||
case "SUSPICIOUS_INCOME_KEYWORD" -> analysisMapper.selectSuspiciousIncomeKeywordStatements(projectId);
|
||||
case "HOUSE_REGISTRATION_MISMATCH" -> analysisMapper.selectHouseRegistrationMismatchStatements(projectId);
|
||||
case "PROPERTY_FEE_REGISTRATION_MISMATCH" -> analysisMapper.selectPropertyFeeRegistrationMismatchStatements(projectId);
|
||||
@@ -258,9 +264,15 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
case "CUMULATIVE_INCOME" -> analysisMapper.selectCumulativeIncomeObjects(
|
||||
projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT"))
|
||||
);
|
||||
case "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT" -> analysisMapper.selectExternalCumulativeTransactionAmountObjects(
|
||||
projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT"))
|
||||
);
|
||||
case "ANNUAL_TURNOVER" -> analysisMapper.selectAnnualTurnoverObjects(
|
||||
projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER"))
|
||||
);
|
||||
case "EXTERNAL_ANNUAL_TURNOVER" -> analysisMapper.selectExternalAnnualTurnoverObjects(
|
||||
projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER"))
|
||||
);
|
||||
case "FREQUENT_CASH_DEPOSIT" -> analysisMapper.selectFrequentCashDepositObjects(
|
||||
projectId,
|
||||
toBigDecimal(config.getThresholdValue("LARGE_CASH_DEPOSIT")),
|
||||
@@ -272,6 +284,11 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")),
|
||||
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX"))
|
||||
);
|
||||
case "EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER" -> analysisMapper.selectExternalMultiPartyGamblingTransferObjects(
|
||||
projectId,
|
||||
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")),
|
||||
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX"))
|
||||
);
|
||||
case "MONTHLY_FIXED_INCOME" -> analysisMapper.selectMonthlyFixedIncomeObjects(
|
||||
projectId, toBigDecimal(config.getThresholdValue("MONTHLY_FIXED_INCOME"))
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
|
||||
@@ -58,7 +59,7 @@ public class CcdiProjectOverviewReportPdfExporter {
|
||||
writeCover(writer, report);
|
||||
writeUploadSubjects(writer, report.getUploadSubjects());
|
||||
writeParams(writer, report.getParams());
|
||||
writeRiskModels(writer, report);
|
||||
writeRiskOverview(writer, report);
|
||||
writeRiskDetails(writer, report);
|
||||
writer.close();
|
||||
document.save(response.getOutputStream());
|
||||
@@ -111,9 +112,9 @@ public class CcdiProjectOverviewReportPdfExporter {
|
||||
);
|
||||
}
|
||||
|
||||
private void writeRiskModels(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException {
|
||||
writer.section("三、风险模型");
|
||||
writer.metrics(report.getDashboard().getStats());
|
||||
private void writeRiskOverview(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException {
|
||||
writer.section("三、风险总览");
|
||||
writer.metrics(buildOverallRiskMetrics(report));
|
||||
writer.subsection("风险模型汇总");
|
||||
writer.table(
|
||||
List.of("模型名称", "预警数量", "涉及人员"),
|
||||
@@ -144,6 +145,40 @@ public class CcdiProjectOverviewReportPdfExporter {
|
||||
new float[] { 0.1F, 0.11F, 0.16F, 0.14F, 0.24F, 0.25F },
|
||||
"暂无风险人员与异常点数据"
|
||||
);
|
||||
|
||||
if (hasExternalRisk(report)) {
|
||||
writer.subsection("外部人员预警");
|
||||
writer.metrics(buildExternalMetrics(report));
|
||||
writer.table(
|
||||
List.of("外部模型", "预警数量", "涉及人数"),
|
||||
report.getExternalModelSummaries().stream()
|
||||
.map(item -> List.of(
|
||||
safeText(item.getModelName()),
|
||||
String.valueOf(defaultZero(item.getWarningCount())),
|
||||
formatCount(item.getPeopleCount(), "人")
|
||||
))
|
||||
.collect(Collectors.toList()),
|
||||
new float[] { 0.5F, 0.2F, 0.3F },
|
||||
"暂无外部人员模型汇总数据"
|
||||
);
|
||||
writer.table(
|
||||
List.of("姓名", "证件号", "主体类型", "风险等级", "命中模型数", "核心异常点", "涉及对象", "最近交易时间"),
|
||||
report.getExternalPersonWarnings().stream()
|
||||
.map(item -> List.of(
|
||||
safeText(item.getName()),
|
||||
maskIdCard(item.getIdNo()),
|
||||
safeText(item.getSubjectType()),
|
||||
safeText(item.getRiskLevel()),
|
||||
String.valueOf(defaultZero(item.getModelCount())),
|
||||
safeText(item.getRiskPoint()),
|
||||
safeText(item.getRelatedObject()),
|
||||
safeText(item.getLatestTradeTime())
|
||||
))
|
||||
.collect(Collectors.toList()),
|
||||
new float[] { 0.09F, 0.15F, 0.1F, 0.09F, 0.1F, 0.25F, 0.12F, 0.1F },
|
||||
"暂无外部人员预警数据"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRiskDetails(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException {
|
||||
@@ -252,6 +287,69 @@ public class CcdiProjectOverviewReportPdfExporter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<CcdiProjectOverviewStatVO> buildOverallRiskMetrics(CcdiProjectOverviewReportVO report) {
|
||||
List<CcdiProjectOverviewStatVO> employeeStats = report.getDashboard().getStats();
|
||||
if (employeeStats == null || employeeStats.isEmpty()) {
|
||||
return List.of(
|
||||
buildMetric("总人数", report.getExternalRiskSummary().getTotal()),
|
||||
buildMetric("高风险", report.getExternalRiskSummary().getHigh()),
|
||||
buildMetric("中风险", report.getExternalRiskSummary().getMedium()),
|
||||
buildMetric("低风险", report.getExternalRiskSummary().getLow()),
|
||||
buildMetric("无风险", report.getExternalRiskSummary().getNoRisk())
|
||||
);
|
||||
}
|
||||
int employeeTotal = metricValue(employeeStats, "people");
|
||||
int high = metricValue(employeeStats, "riskPeople");
|
||||
int medium = metricValue(employeeStats, "medium");
|
||||
int low = metricValue(employeeStats, "low");
|
||||
int noRisk = metricValue(employeeStats, "count");
|
||||
int externalTotal = defaultZero(report.getExternalRiskSummary().getTotal());
|
||||
int externalHigh = defaultZero(report.getExternalRiskSummary().getHigh());
|
||||
int externalMedium = defaultZero(report.getExternalRiskSummary().getMedium());
|
||||
int externalLow = defaultZero(report.getExternalRiskSummary().getLow());
|
||||
int externalNoRisk = defaultZero(report.getExternalRiskSummary().getNoRisk());
|
||||
return List.of(
|
||||
buildMetric("总人数", employeeTotal + externalTotal),
|
||||
buildMetric("高风险", high + externalHigh),
|
||||
buildMetric("中风险", medium + externalMedium),
|
||||
buildMetric("低风险", low + externalLow),
|
||||
buildMetric("无风险", noRisk + externalNoRisk)
|
||||
);
|
||||
}
|
||||
|
||||
private Integer metricValue(List<CcdiProjectOverviewStatVO> stats, String key) {
|
||||
return defaultZero(stats.stream()
|
||||
.filter(stat -> key.equals(stat.getKey()))
|
||||
.findFirst()
|
||||
.map(CcdiProjectOverviewStatVO::getValue)
|
||||
.orElse(0));
|
||||
}
|
||||
|
||||
private boolean hasExternalRisk(CcdiProjectOverviewReportVO report) {
|
||||
return defaultZero(report.getExternalRiskSummary().getHigh()) > 0
|
||||
|| defaultZero(report.getExternalRiskSummary().getMedium()) > 0
|
||||
|| defaultZero(report.getExternalRiskSummary().getLow()) > 0
|
||||
|| !report.getExternalModelSummaries().isEmpty()
|
||||
|| !report.getExternalPersonWarnings().isEmpty();
|
||||
}
|
||||
|
||||
private List<CcdiProjectOverviewStatVO> buildExternalMetrics(CcdiProjectOverviewReportVO report) {
|
||||
return List.of(
|
||||
buildMetric("外部人员", report.getExternalRiskSummary().getTotal()),
|
||||
buildMetric("高风险", report.getExternalRiskSummary().getHigh()),
|
||||
buildMetric("中风险", report.getExternalRiskSummary().getMedium()),
|
||||
buildMetric("低风险", report.getExternalRiskSummary().getLow()),
|
||||
buildMetric("无风险人员", report.getExternalRiskSummary().getNoRisk())
|
||||
);
|
||||
}
|
||||
|
||||
private CcdiProjectOverviewStatVO buildMetric(String label, Integer value) {
|
||||
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
|
||||
stat.setLabel(label);
|
||||
stat.setValue(defaultZero(value));
|
||||
return stat;
|
||||
}
|
||||
|
||||
private String formatPeopleSummary(CcdiProjectOverviewReportModelSummaryVO item) {
|
||||
String names = safeText(item.getPeopleNames());
|
||||
if ("-".equals(names)) {
|
||||
|
||||
@@ -4,12 +4,16 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskModelPeopleExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||
@@ -25,14 +29,21 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisAbnormalGroupVO
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewStatVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
|
||||
@@ -214,6 +225,108 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
return people;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CcdiProjectRiskModelPeopleExcel> exportRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
|
||||
ensureProjectExists(queryDTO.getProjectId());
|
||||
normalizeRiskModelPeopleQuery(queryDTO);
|
||||
|
||||
return defaultList(overviewMapper.selectRiskModelPeopleList(queryDTO)).stream()
|
||||
.map(item -> buildRiskModelPeopleExcelRow(item, "员工"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiProjectExternalPersonWarningVO getExternalPersonWarnings(CcdiProjectExternalPersonQueryDTO queryDTO) {
|
||||
ensureProjectExists(queryDTO.getProjectId());
|
||||
|
||||
Page<CcdiProjectExternalPersonWarningItemVO> page = new Page<>(
|
||||
defaultRiskPeoplePageNum(queryDTO.getPageNum()),
|
||||
defaultRiskPeoplePageSize(queryDTO.getPageSize())
|
||||
);
|
||||
Page<CcdiProjectExternalPersonWarningItemVO> resultPage =
|
||||
overviewMapper.selectExternalPersonWarningPage(page, queryDTO);
|
||||
|
||||
List<CcdiProjectExternalPersonWarningItemVO> rows =
|
||||
defaultList(resultPage == null ? null : resultPage.getRecords()).stream()
|
||||
.peek(item -> item.setActionLabel(ACTION_LABEL))
|
||||
.toList();
|
||||
|
||||
CcdiProjectExternalPersonWarningVO warnings = new CcdiProjectExternalPersonWarningVO();
|
||||
warnings.setRows(rows);
|
||||
warnings.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||
warnings.setPageNum(page.getCurrent());
|
||||
warnings.setPageSize(page.getSize());
|
||||
return warnings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiProjectExternalRiskSummaryVO getExternalRiskSummary(Long projectId) {
|
||||
ensureProjectExists(projectId);
|
||||
CcdiProjectExternalRiskSummaryVO summary = overviewMapper.selectExternalRiskSummaryByProjectId(projectId);
|
||||
if (summary == null) {
|
||||
return new CcdiProjectExternalRiskSummaryVO();
|
||||
}
|
||||
summary.setTotal(defaultZero(summary.getTotal()));
|
||||
summary.setHigh(defaultZero(summary.getHigh()));
|
||||
summary.setMedium(defaultZero(summary.getMedium()));
|
||||
summary.setLow(defaultZero(summary.getLow()));
|
||||
summary.setNoRisk(defaultZero(summary.getNoRisk()));
|
||||
return summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CcdiProjectExternalPersonWarningExcel> exportExternalPersonWarnings(Long projectId) {
|
||||
ensureProjectExists(projectId);
|
||||
|
||||
return defaultList(overviewMapper.selectExternalPersonWarningList(projectId)).stream()
|
||||
.map(this::buildExternalPersonWarningExcelRow)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiProjectRiskModelCardsVO getExternalRiskModelCards(Long projectId) {
|
||||
ensureProjectExists(projectId);
|
||||
|
||||
CcdiProjectRiskModelCardsVO cards = new CcdiProjectRiskModelCardsVO();
|
||||
cards.setCardList(defaultList(overviewMapper.selectExternalRiskModelCardsByProjectId(projectId)));
|
||||
return cards;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiProjectRiskModelPeopleVO getExternalRiskModelPeople(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
|
||||
ensureProjectExists(queryDTO.getProjectId());
|
||||
normalizeExternalRiskModelPeopleQuery(queryDTO);
|
||||
|
||||
Page<CcdiProjectRiskModelPeopleItemVO> page = new Page<>(
|
||||
defaultPageNum(queryDTO.getPageNum()),
|
||||
defaultPageSize(queryDTO.getPageSize())
|
||||
);
|
||||
Page<CcdiProjectRiskModelPeopleItemVO> resultPage =
|
||||
overviewMapper.selectExternalRiskModelPeoplePage(page, queryDTO);
|
||||
|
||||
List<CcdiProjectRiskModelPeopleItemVO> rows = defaultList(resultPage == null ? null : resultPage.getRecords())
|
||||
.stream()
|
||||
.peek(item -> item.setActionLabel(ACTION_LABEL))
|
||||
.toList();
|
||||
|
||||
CcdiProjectRiskModelPeopleVO people = new CcdiProjectRiskModelPeopleVO();
|
||||
people.setRows(rows);
|
||||
people.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||
return people;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CcdiProjectRiskModelPeopleExcel> exportExternalRiskModelPeople(
|
||||
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
|
||||
) {
|
||||
ensureProjectExists(queryDTO.getProjectId());
|
||||
normalizeExternalRiskModelPeopleQuery(queryDTO);
|
||||
|
||||
return defaultList(overviewMapper.selectExternalRiskModelPeopleList(queryDTO)).stream()
|
||||
.map(item -> buildRiskModelPeopleExcelRow(item, item.getStaffCode()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiProjectSuspiciousTransactionPageVO getSuspiciousTransactions(
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
|
||||
@@ -241,7 +354,7 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
ensureProjectExists(queryDTO.getProjectId());
|
||||
normalizeSuspiciousTransactionQuery(queryDTO);
|
||||
|
||||
return defaultList(overviewMapper.selectSuspiciousTransactionList(queryDTO)).stream()
|
||||
return defaultList(overviewMapper.selectReportSuspiciousTransactionList(queryDTO)).stream()
|
||||
.map(this::buildSuspiciousTransactionExcelRow)
|
||||
.toList();
|
||||
}
|
||||
@@ -332,9 +445,12 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
.toList());
|
||||
report.setParams(buildReportParams(projectId));
|
||||
report.setModelSummaries(defaultList(overviewMapper.selectReportRiskModelSummaries(projectId)));
|
||||
report.setExternalRiskSummary(getExternalRiskSummary(projectId));
|
||||
report.setExternalModelSummaries(buildExternalReportModelSummaries(projectId));
|
||||
report.setRiskPeople(defaultList(overviewMapper.selectReportRiskPeople(projectId)).stream()
|
||||
.peek(item -> item.setActionLabel(ACTION_LABEL))
|
||||
.toList());
|
||||
report.setExternalPersonWarnings(exportExternalPersonWarnings(projectId));
|
||||
report.setSuspiciousTransactions(defaultList(
|
||||
overviewMapper.selectReportSuspiciousTransactionList(suspiciousQuery)
|
||||
));
|
||||
@@ -435,6 +551,51 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
return row;
|
||||
}
|
||||
|
||||
private CcdiProjectExternalPersonWarningExcel buildExternalPersonWarningExcelRow(
|
||||
CcdiProjectExternalPersonWarningItemVO item
|
||||
) {
|
||||
CcdiProjectExternalPersonWarningExcel row = new CcdiProjectExternalPersonWarningExcel();
|
||||
row.setName(item.getName());
|
||||
row.setIdNo(item.getIdNo());
|
||||
row.setSubjectType(item.getSubjectType());
|
||||
row.setRiskLevel(item.getRiskLevel());
|
||||
row.setModelCount(item.getModelCount());
|
||||
row.setRiskPoint(item.getRiskPoint());
|
||||
row.setRelatedObject(item.getRelatedObject());
|
||||
row.setLatestTradeTime(item.getLatestTradeTime());
|
||||
return row;
|
||||
}
|
||||
|
||||
private List<CcdiProjectOverviewReportModelSummaryVO> buildExternalReportModelSummaries(Long projectId) {
|
||||
return defaultList(overviewMapper.selectExternalRiskModelCardsByProjectId(projectId)).stream()
|
||||
.map(this::buildExternalReportModelSummary)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private CcdiProjectOverviewReportModelSummaryVO buildExternalReportModelSummary(CcdiProjectRiskModelCardVO card) {
|
||||
CcdiProjectOverviewReportModelSummaryVO row = new CcdiProjectOverviewReportModelSummaryVO();
|
||||
row.setModelCode(card.getModelCode());
|
||||
row.setModelName(card.getModelName());
|
||||
row.setWarningCount(card.getWarningCount());
|
||||
row.setPeopleCount(card.getPeopleCount());
|
||||
row.setPeopleNames("-");
|
||||
return row;
|
||||
}
|
||||
|
||||
private CcdiProjectRiskModelPeopleExcel buildRiskModelPeopleExcelRow(
|
||||
CcdiProjectRiskModelPeopleItemVO item,
|
||||
String subjectType
|
||||
) {
|
||||
CcdiProjectRiskModelPeopleExcel row = new CcdiProjectRiskModelPeopleExcel();
|
||||
row.setPersonName(item.getStaffName());
|
||||
row.setSubjectType(subjectType);
|
||||
row.setIdNo(item.getIdNo());
|
||||
row.setScopeName(item.getDepartment());
|
||||
row.setModelNames(joinModelNames(item.getModelNames()));
|
||||
row.setHitTags(joinHitTagNames(item.getHitTagList()));
|
||||
return row;
|
||||
}
|
||||
|
||||
private void ensureProjectExists(Long projectId) {
|
||||
getRequiredProject(projectId);
|
||||
}
|
||||
@@ -447,6 +608,14 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase());
|
||||
}
|
||||
|
||||
private void normalizeExternalRiskModelPeopleQuery(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
|
||||
if (queryDTO.getMatchMode() == null || queryDTO.getMatchMode().isBlank()) {
|
||||
queryDTO.setMatchMode("ANY");
|
||||
return;
|
||||
}
|
||||
queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase());
|
||||
}
|
||||
|
||||
private void normalizeSuspiciousTransactionQuery(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
|
||||
if (queryDTO.getSuspiciousType() == null || queryDTO.getSuspiciousType().isBlank()) {
|
||||
queryDTO.setSuspiciousType("ALL");
|
||||
@@ -516,15 +685,18 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
}
|
||||
|
||||
private CcdiProjectSuspiciousTransactionExcel buildSuspiciousTransactionExcelRow(
|
||||
CcdiProjectSuspiciousTransactionItemVO item
|
||||
CcdiProjectOverviewReportSuspiciousTransactionVO item
|
||||
) {
|
||||
CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel();
|
||||
row.setTrxDate(item.getTrxDate());
|
||||
row.setSuspiciousPersonName(item.getSuspiciousPersonName());
|
||||
row.setRelatedPersonName(item.getRelatedPersonName());
|
||||
row.setLeAccountNo(item.getLeAccountNo());
|
||||
row.setLeAccountName(item.getLeAccountName());
|
||||
row.setCustomerAccountName(item.getCustomerAccountName());
|
||||
row.setCustomerAccountNo(item.getCustomerAccountNo());
|
||||
row.setRelatedStaffDisplay(formatRelatedStaff(item.getRelatedStaffName(), item.getRelatedStaffCode()));
|
||||
row.setRelationType(item.getRelationType());
|
||||
row.setSummaryAndCashType(formatSummaryAndCashType(item.getUserMemo(), item.getCashType()));
|
||||
row.setUserMemo(item.getUserMemo());
|
||||
row.setCashType(item.getCashType());
|
||||
row.setHitTags(item.getHitTags());
|
||||
row.setDisplayAmount(item.getDisplayAmount());
|
||||
return row;
|
||||
}
|
||||
@@ -597,6 +769,21 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
return safeMemo + "/" + safeCashType;
|
||||
}
|
||||
|
||||
private String joinModelNames(List<String> modelNames) {
|
||||
return defaultList(modelNames).stream()
|
||||
.filter(item -> item != null && !item.isBlank())
|
||||
.distinct()
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
private String joinHitTagNames(List<CcdiProjectRiskHitTagVO> hitTags) {
|
||||
return defaultList(hitTags).stream()
|
||||
.map(CcdiProjectRiskHitTagVO::getRuleName)
|
||||
.filter(item -> item != null && !item.isBlank())
|
||||
.distinct()
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
|
||||
private CcdiProjectPersonAnalysisAbnormalDetailVO buildAbnormalDetail(
|
||||
List<CcdiBankStatementListVO> statementRows,
|
||||
List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows
|
||||
|
||||
@@ -44,19 +44,33 @@ public class CcdiProjectRiskDetailWorkbookExporter {
|
||||
|
||||
private void writeSuspiciousSheet(Sheet sheet, List<CcdiProjectSuspiciousTransactionExcel> rows) {
|
||||
Row header = sheet.createRow(0);
|
||||
String[] headers = { "交易时间", "可疑人员", "关联人", "关联员工", "关系", "摘要/交易类型", "交易金额" };
|
||||
String[] headers = {
|
||||
"交易时间",
|
||||
"本方账户",
|
||||
"本方主体",
|
||||
"对方名称",
|
||||
"对方账户",
|
||||
"关联员工",
|
||||
"摘要",
|
||||
"交易类型",
|
||||
"异常标签",
|
||||
"交易金额"
|
||||
};
|
||||
writeHeader(header, headers);
|
||||
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
CcdiProjectSuspiciousTransactionExcel item = rows.get(i);
|
||||
Row row = sheet.createRow(i + 1);
|
||||
row.createCell(0).setCellValue(safeText(item.getTrxDate()));
|
||||
row.createCell(1).setCellValue(safeText(item.getSuspiciousPersonName()));
|
||||
row.createCell(2).setCellValue(safeText(item.getRelatedPersonName()));
|
||||
row.createCell(3).setCellValue(safeText(item.getRelatedStaffDisplay()));
|
||||
row.createCell(4).setCellValue(safeText(item.getRelationType()));
|
||||
row.createCell(5).setCellValue(safeText(item.getSummaryAndCashType()));
|
||||
row.createCell(6).setCellValue(safeNumber(item.getDisplayAmount()));
|
||||
row.createCell(1).setCellValue(safeText(item.getLeAccountNo()));
|
||||
row.createCell(2).setCellValue(safeText(item.getLeAccountName()));
|
||||
row.createCell(3).setCellValue(safeText(item.getCustomerAccountName()));
|
||||
row.createCell(4).setCellValue(safeText(item.getCustomerAccountNo()));
|
||||
row.createCell(5).setCellValue(safeText(item.getRelatedStaffDisplay()));
|
||||
row.createCell(6).setCellValue(safeText(item.getUserMemo()));
|
||||
row.createCell(7).setCellValue(safeText(item.getCashType()));
|
||||
row.createCell(8).setCellValue(safeText(item.getHitTags()));
|
||||
row.createCell(9).setCellValue(safeNumber(item.getDisplayAmount()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="query.ourCertNos != null and query.ourCertNos.size() > 0">
|
||||
AND bs.cret_no IN
|
||||
<foreach collection="query.ourCertNos" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="query.ourBanks != null and query.ourBanks.size() > 0">
|
||||
AND bs.BANK IN
|
||||
<foreach collection="query.ourBanks" item="item" open="(" separator="," close=")">
|
||||
|
||||
@@ -41,6 +41,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and trim(bs.cret_no) != ''
|
||||
</sql>
|
||||
|
||||
<sql id="externalPersonPredicateSql">
|
||||
bs.cret_no is not null
|
||||
and trim(bs.cret_no) != ''
|
||||
and not exists (
|
||||
select 1
|
||||
from ccdi_base_staff staff
|
||||
where staff.id_card = bs.cret_no
|
||||
)
|
||||
and not exists (
|
||||
select 1
|
||||
from ccdi_staff_fmy_relation relation
|
||||
where relation.status = 1
|
||||
and relation.relation_cert_no = bs.cret_no
|
||||
)
|
||||
and trim(IFNULL(bs.LE_ACCOUNT_NAME, '')) <> trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''))
|
||||
</sql>
|
||||
|
||||
<sql id="cashDepositPredicate">
|
||||
(
|
||||
(
|
||||
@@ -466,6 +483,74 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectExternalSingleLargeAmountStatements" resultMap="BankTagStatementHitResultMap">
|
||||
select
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
CONCAT(
|
||||
'外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
|
||||
'”单笔交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
|
||||
' 元,超过阈值 ', CAST(#{threshold} AS CHAR),
|
||||
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where bs.project_id = #{projectId}
|
||||
and <include refid="externalPersonPredicateSql"/>
|
||||
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
|
||||
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
</select>
|
||||
|
||||
<select id="selectExternalCumulativeTransactionAmountObjects" resultMap="BankTagObjectHitResultMap">
|
||||
select
|
||||
'EXTERNAL_CERT_NO' AS objectType,
|
||||
t.certNo AS objectKey,
|
||||
CONCAT(
|
||||
'外部人员“', IFNULL(t.personName, ''),
|
||||
'”累计交易金额 ', CAST(t.totalAmount AS CHAR),
|
||||
' 元,超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
|
||||
) AS reasonDetail
|
||||
from (
|
||||
select
|
||||
bs.cret_no AS certNo,
|
||||
max(IFNULL(bs.LE_ACCOUNT_NAME, '')) AS personName,
|
||||
ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) AS totalAmount
|
||||
from ccdi_bank_statement bs
|
||||
where bs.project_id = #{projectId}
|
||||
and <include refid="externalPersonPredicateSql"/>
|
||||
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
group by bs.cret_no
|
||||
having ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) > #{threshold}
|
||||
) t
|
||||
</select>
|
||||
|
||||
<select id="selectExternalAnnualTurnoverObjects" resultMap="BankTagObjectHitResultMap">
|
||||
select
|
||||
'EXTERNAL_CERT_NO' AS objectType,
|
||||
t.certNo AS objectKey,
|
||||
CONCAT(
|
||||
'外部人员“', IFNULL(t.personName, ''),
|
||||
'”近一年流水交易额 ', CAST(t.annualAmount AS CHAR),
|
||||
' 元,超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
|
||||
) AS reasonDetail
|
||||
from (
|
||||
select
|
||||
bs.cret_no AS certNo,
|
||||
max(IFNULL(bs.LE_ACCOUNT_NAME, '')) AS personName,
|
||||
ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) AS annualAmount
|
||||
from ccdi_bank_statement bs
|
||||
where bs.project_id = #{projectId}
|
||||
and <include refid="externalPersonPredicateSql"/>
|
||||
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
and STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d') >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
|
||||
group by bs.cret_no
|
||||
having ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) > #{threshold}
|
||||
) t
|
||||
</select>
|
||||
|
||||
<select id="selectAbnormalCustomerTransactionStatements" resultMap="BankTagStatementHitResultMap">
|
||||
select
|
||||
hit.bankStatementId AS bankStatementId,
|
||||
@@ -702,6 +787,72 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectExternalGamblingMemoStatements" resultMap="BankTagStatementHitResultMap">
|
||||
select
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
CONCAT(
|
||||
'外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
|
||||
'”摘要/对手方命中疑似赌博关键词,摘要“', IFNULL(bs.USER_MEMO, ''),
|
||||
'”,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where bs.project_id = #{projectId}
|
||||
and <include refid="externalPersonPredicateSql"/>
|
||||
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
|
||||
and (
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectExternalMultiPartyGamblingTransferObjects" resultMap="BankTagObjectHitResultMap">
|
||||
select
|
||||
'EXTERNAL_CERT_NO' AS objectType,
|
||||
t.certNo AS objectKey,
|
||||
CONCAT(
|
||||
'外部人员“', IFNULL(MAX(t.personName), ''),
|
||||
'”交易日 ', MAX(t.tradeDate),
|
||||
' 发生 ', CAST(MAX(t.hitCount) AS CHAR),
|
||||
' 笔疑似赌博交易,涉及 ', CAST(MAX(t.partyCount) AS CHAR),
|
||||
' 个对手方,金额合计 ', CAST(MAX(t.totalAmount) AS CHAR), ' 元'
|
||||
) AS reasonDetail
|
||||
from (
|
||||
select
|
||||
source.certNo AS certNo,
|
||||
max(source.personName) AS personName,
|
||||
source.tradeDate AS tradeDate,
|
||||
COUNT(1) AS hitCount,
|
||||
COUNT(DISTINCT source.customerAccountName) AS partyCount,
|
||||
ROUND(SUM(source.tradeAmount), 2) AS totalAmount
|
||||
from (
|
||||
select
|
||||
bs.cret_no AS certNo,
|
||||
IFNULL(bs.LE_ACCOUNT_NAME, '') AS personName,
|
||||
LEFT(TRIM(bs.TRX_DATE), 10) AS tradeDate,
|
||||
bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName,
|
||||
GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS tradeAmount
|
||||
from ccdi_bank_statement bs
|
||||
where bs.project_id = #{projectId}
|
||||
and <include refid="externalPersonPredicateSql"/>
|
||||
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) between #{amountMinThreshold} and #{amountMaxThreshold}
|
||||
and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') <> ''
|
||||
and (
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包|牌局|赌'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包|牌局|赌'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay'
|
||||
)
|
||||
) source
|
||||
group by source.certNo, source.tradeDate
|
||||
having COUNT(1) > 2
|
||||
and COUNT(DISTINCT source.customerAccountName) >= 2
|
||||
) t
|
||||
group by t.certNo
|
||||
</select>
|
||||
|
||||
<select id="selectSpecialAmountTransactionStatements" resultMap="BankTagStatementHitResultMap">
|
||||
select
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
@@ -728,6 +879,74 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectExternalToStaffOrFamilyTransactionStatements" resultMap="BankTagStatementHitResultMap">
|
||||
select distinct
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
CONCAT(
|
||||
'外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
|
||||
'”与', CASE
|
||||
WHEN counter_account.owner_type = 'EMPLOYEE' THEN '员工'
|
||||
WHEN counter_account.owner_type = 'RELATION' THEN '员工亲属'
|
||||
WHEN counter_staff.id_card is not null THEN '员工'
|
||||
WHEN counter_relation.relation_cert_no is not null THEN '员工亲属'
|
||||
ELSE '员工/员工亲属'
|
||||
END,
|
||||
'“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”发生资金往来,交易金额 ',
|
||||
CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
left join ccdi_account_info counter_account
|
||||
on trim(bs.CUSTOMER_ACCOUNT_NO) != ''
|
||||
and counter_account.account_no = trim(bs.CUSTOMER_ACCOUNT_NO)
|
||||
and counter_account.owner_type in ('EMPLOYEE', 'RELATION', 'INTERMEDIARY', 'CREDIT_CUSTOMER')
|
||||
left join ccdi_base_staff counter_staff
|
||||
on counter_account.account_no is null
|
||||
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
|
||||
and counter_staff.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
|
||||
left join ccdi_staff_fmy_relation counter_relation
|
||||
on counter_account.account_no is null
|
||||
and counter_relation.status = 1
|
||||
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
|
||||
and counter_relation.relation_name = trim(bs.CUSTOMER_ACCOUNT_NAME)
|
||||
where bs.project_id = #{projectId}
|
||||
and <include refid="externalPersonPredicateSql"/>
|
||||
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
|
||||
and (
|
||||
counter_account.owner_type in ('EMPLOYEE', 'RELATION')
|
||||
or (
|
||||
counter_account.account_no is null
|
||||
and (
|
||||
counter_staff.id_card is not null
|
||||
or counter_relation.relation_cert_no is not null
|
||||
)
|
||||
)
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectExternalNightTransactionStatements" resultMap="BankTagStatementHitResultMap">
|
||||
select
|
||||
bs.bank_statement_id AS bankStatementId,
|
||||
bs.group_id AS groupId,
|
||||
bs.batch_id AS logId,
|
||||
CONCAT(
|
||||
'外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
|
||||
'”夜间交易,交易时间 ', IFNULL(bs.TRX_DATE, ''),
|
||||
',对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
|
||||
'”,交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
|
||||
) AS reasonDetail
|
||||
from ccdi_bank_statement bs
|
||||
where bs.project_id = #{projectId}
|
||||
and <include refid="externalPersonPredicateSql"/>
|
||||
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
|
||||
and (
|
||||
HOUR(STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s')) >= 22
|
||||
or HOUR(STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s')) < 6
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectMonthlyFixedIncomeObjects" resultMap="BankTagObjectHitResultMap">
|
||||
select
|
||||
'STAFF_ID_CARD' AS objectType,
|
||||
|
||||
@@ -33,6 +33,38 @@
|
||||
select="selectRiskHitTagsByScope"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="ExternalRiskModelPeopleItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO">
|
||||
<id property="idNo" column="cert_no"/>
|
||||
<result property="staffName" column="person_name"/>
|
||||
<result property="staffCode" column="subject_type"/>
|
||||
<result property="department" column="related_object"/>
|
||||
<collection property="modelNames"
|
||||
column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
|
||||
ofType="java.lang.String"
|
||||
select="selectExternalRiskModelNamesByScope"/>
|
||||
<collection property="hitTagList"
|
||||
column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
|
||||
ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO"
|
||||
select="selectExternalRiskHitTagsByScope"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="ExternalPersonWarningItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO">
|
||||
<result property="name" column="person_name"/>
|
||||
<result property="idNo" column="cert_no"/>
|
||||
<result property="subjectType" column="subject_type"/>
|
||||
<result property="riskLevel" column="risk_level_name"/>
|
||||
<result property="riskLevelType" column="risk_level_type"/>
|
||||
<result property="riskCount" column="risk_count"/>
|
||||
<result property="modelCount" column="model_count"/>
|
||||
<result property="riskPoint" column="risk_point"/>
|
||||
<result property="relatedObject" column="related_object"/>
|
||||
<result property="latestTradeTime" column="latest_trade_time"/>
|
||||
<collection property="riskPointTagList"
|
||||
column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
|
||||
ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO"
|
||||
select="selectExternalRiskHitTagsByScope"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="SuspiciousTransactionItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO">
|
||||
<id property="bankStatementId" column="bankStatementId"/>
|
||||
<result property="trxDate" column="trxDate"/>
|
||||
@@ -115,6 +147,13 @@
|
||||
) tens
|
||||
</sql>
|
||||
|
||||
<sql id="externalModelCodeFilterSql">
|
||||
('EXTERNAL_LARGE_TRANSACTION',
|
||||
'EXTERNAL_ABNORMAL_TRANSACTION',
|
||||
'EXTERNAL_SUSPICIOUS_GAMBLING',
|
||||
'EXTERNAL_SUSPICIOUS_RELATION')
|
||||
</sql>
|
||||
|
||||
<sql id="resolvedEmployeeRiskBaseSql">
|
||||
select distinct
|
||||
tr.id,
|
||||
@@ -494,6 +533,401 @@
|
||||
order by result.staff_name asc, result.staff_id_card asc
|
||||
</select>
|
||||
|
||||
<select id="selectRiskModelPeopleList" resultMap="RiskModelPeopleItemResultMap">
|
||||
<bind name="projectId" value="query.projectId"/>
|
||||
select
|
||||
result.project_id,
|
||||
result.staff_id_card,
|
||||
result.staff_name,
|
||||
result.staff_code,
|
||||
result.dept_name as department,
|
||||
#{query.modelCodesCsv} as selected_model_codes
|
||||
from ccdi_project_overview_employee_result result
|
||||
where 1 = 1
|
||||
and result.project_id = #{query.projectId}
|
||||
<if test="query.modelCodes != null and query.modelCodes.size() > 0">
|
||||
<choose>
|
||||
<when test="query.matchMode == 'ALL'">
|
||||
<foreach collection="query.modelCodes" item="modelCode">
|
||||
and find_in_set(#{modelCode}, result.model_codes_csv)
|
||||
</foreach>
|
||||
</when>
|
||||
<otherwise>
|
||||
and (
|
||||
<foreach collection="query.modelCodes" item="modelCode" separator=" or ">
|
||||
find_in_set(#{modelCode}, result.model_codes_csv)
|
||||
</foreach>
|
||||
)
|
||||
</otherwise>
|
||||
</choose>
|
||||
</if>
|
||||
<if test="query.keyword != null and query.keyword != ''">
|
||||
and (
|
||||
result.staff_name like concat('%', trim(#{query.keyword}), '%')
|
||||
or result.staff_code like concat('%', trim(#{query.keyword}), '%')
|
||||
)
|
||||
</if>
|
||||
<if test="query.deptId != null">
|
||||
and result.dept_id = #{query.deptId}
|
||||
</if>
|
||||
order by result.staff_name asc, result.staff_id_card asc
|
||||
</select>
|
||||
|
||||
<sql id="externalPersonSubjectSql">
|
||||
select
|
||||
bs.project_id,
|
||||
bs.cret_no as cert_no,
|
||||
coalesce(
|
||||
max(intermediary.name),
|
||||
max(customer.name),
|
||||
max(nullif(trim(bs.LE_ACCOUNT_NAME), '')),
|
||||
'外部人员'
|
||||
) as person_name,
|
||||
case
|
||||
when max(case when intermediary.person_id is not null then 1 else 0 end) > 0 then '中介'
|
||||
when max(case when customer.person_id is not null then 1 else 0 end) > 0 then '客户'
|
||||
else '外部人员'
|
||||
end as subject_type
|
||||
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
|
||||
left join ccdi_biz_intermediary intermediary
|
||||
on intermediary.person_sub_type = '本人'
|
||||
and intermediary.person_id = bs.cret_no
|
||||
left join ccdi_credit_customer_base customer
|
||||
on customer.person_id = bs.cret_no
|
||||
where bs.project_id = #{externalProjectId}
|
||||
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
|
||||
group by bs.project_id, bs.cret_no
|
||||
</sql>
|
||||
|
||||
<sql id="externalPersonSourceSql">
|
||||
select
|
||||
subject.project_id,
|
||||
subject.cert_no,
|
||||
subject.person_name,
|
||||
subject.subject_type,
|
||||
bs.bank_statement_id,
|
||||
bs.TRX_DATE as trx_date,
|
||||
bs.CUSTOMER_ACCOUNT_NAME as customer_account_name,
|
||||
bs.customer_cert_no,
|
||||
case
|
||||
when counter_account.owner_type = 'EMPLOYEE' then '员工'
|
||||
when counter_account.owner_type = 'RELATION' then '员工亲属'
|
||||
when counter_account.owner_type = 'CREDIT_CUSTOMER' then '信贷客户'
|
||||
when counter_account.owner_type = 'INTERMEDIARY' then '中介库人员'
|
||||
when counter_staff.id_card is not null then '员工'
|
||||
when counter_relation.relation_cert_no is not null then '员工亲属'
|
||||
when counter_intermediary.person_id is not null then '中介库人员'
|
||||
else null
|
||||
end as related_object,
|
||||
tr.model_code,
|
||||
tr.model_name,
|
||||
tr.rule_code,
|
||||
tr.rule_name,
|
||||
tr.risk_level
|
||||
from (
|
||||
<include refid="externalPersonSubjectSql"/>
|
||||
) subject
|
||||
inner join ccdi_bank_statement bs
|
||||
on bs.project_id = subject.project_id
|
||||
and bs.cret_no = subject.cert_no
|
||||
inner join ccdi_bank_statement_tag_result tr
|
||||
on tr.project_id = bs.project_id
|
||||
and tr.bank_statement_id = bs.bank_statement_id
|
||||
and tr.model_code in <include refid="externalModelCodeFilterSql"/>
|
||||
left join ccdi_account_info counter_account
|
||||
on trim(bs.CUSTOMER_ACCOUNT_NO) != ''
|
||||
and counter_account.account_no = trim(bs.CUSTOMER_ACCOUNT_NO)
|
||||
and counter_account.owner_type in ('EMPLOYEE', 'RELATION', 'INTERMEDIARY', 'CREDIT_CUSTOMER')
|
||||
left join ccdi_base_staff counter_staff
|
||||
on counter_account.account_no is null
|
||||
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
|
||||
and counter_staff.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
|
||||
left join ccdi_staff_fmy_relation counter_relation
|
||||
on counter_account.account_no is null
|
||||
and counter_relation.status = 1
|
||||
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
|
||||
and counter_relation.relation_name = trim(bs.CUSTOMER_ACCOUNT_NAME)
|
||||
left join ccdi_biz_intermediary counter_intermediary
|
||||
on counter_account.account_no is null
|
||||
and counter_intermediary.person_sub_type = '本人'
|
||||
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
|
||||
and counter_intermediary.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
|
||||
where trim(ifnull(bs.LE_ACCOUNT_NAME, '')) != trim(ifnull(bs.CUSTOMER_ACCOUNT_NAME, ''))
|
||||
|
||||
union all
|
||||
|
||||
select
|
||||
subject.project_id,
|
||||
subject.cert_no,
|
||||
subject.person_name,
|
||||
subject.subject_type,
|
||||
null as bank_statement_id,
|
||||
null as trx_date,
|
||||
null as customer_account_name,
|
||||
null as customer_cert_no,
|
||||
'资金' as related_object,
|
||||
tr.model_code,
|
||||
tr.model_name,
|
||||
tr.rule_code,
|
||||
tr.rule_name,
|
||||
tr.risk_level
|
||||
from (
|
||||
<include refid="externalPersonSubjectSql"/>
|
||||
) subject
|
||||
inner join ccdi_bank_statement_tag_result tr
|
||||
on tr.project_id = subject.project_id
|
||||
and tr.object_type = 'EXTERNAL_CERT_NO'
|
||||
and tr.object_key = subject.cert_no
|
||||
and tr.model_code in <include refid="externalModelCodeFilterSql"/>
|
||||
</sql>
|
||||
|
||||
<sql id="externalPersonAggregateSql">
|
||||
select
|
||||
source.project_id,
|
||||
source.cert_no,
|
||||
max(source.person_name) as person_name,
|
||||
max(source.subject_type) as subject_type,
|
||||
count(*) as risk_count,
|
||||
count(distinct source.model_code) as model_count,
|
||||
group_concat(distinct source.rule_name order by source.rule_name separator '、') as risk_point,
|
||||
group_concat(distinct source.related_object order by source.related_object separator '、') as related_object,
|
||||
max(source.trx_date) as latest_trade_time,
|
||||
case
|
||||
when sum(case when source.risk_level = 'HIGH' then 1 else 0 end) > 0 then 'HIGH'
|
||||
when sum(case when source.risk_level = 'MEDIUM' then 1 else 0 end) > 0 then 'MEDIUM'
|
||||
else 'LOW'
|
||||
end as risk_level_code,
|
||||
null as selected_model_codes
|
||||
from (
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) source
|
||||
group by source.project_id, source.cert_no
|
||||
</sql>
|
||||
|
||||
<sql id="externalPersonWarningSelectSql">
|
||||
select
|
||||
agg.project_id,
|
||||
agg.cert_no,
|
||||
agg.person_name,
|
||||
agg.subject_type,
|
||||
agg.risk_count,
|
||||
agg.model_count,
|
||||
agg.risk_point,
|
||||
coalesce(agg.related_object, '-') as related_object,
|
||||
agg.latest_trade_time,
|
||||
agg.risk_level_code,
|
||||
case
|
||||
when agg.risk_level_code = 'HIGH' then '高风险'
|
||||
when agg.risk_level_code = 'MEDIUM' then '中风险'
|
||||
else '低风险'
|
||||
end as risk_level_name,
|
||||
case
|
||||
when agg.risk_level_code = 'HIGH' then 'danger'
|
||||
when agg.risk_level_code = 'MEDIUM' then 'warning'
|
||||
else 'info'
|
||||
end as risk_level_type,
|
||||
case
|
||||
when agg.risk_level_code = 'HIGH' then 1
|
||||
when agg.risk_level_code = 'MEDIUM' then 2
|
||||
else 3
|
||||
end as risk_level_sort,
|
||||
agg.selected_model_codes
|
||||
from (
|
||||
<include refid="externalPersonAggregateSql"/>
|
||||
) agg
|
||||
</sql>
|
||||
|
||||
<select id="selectExternalPersonWarningPage" resultMap="ExternalPersonWarningItemResultMap">
|
||||
<bind name="externalProjectId" value="query.projectId"/>
|
||||
select *
|
||||
from (
|
||||
<include refid="externalPersonWarningSelectSql"/>
|
||||
) warning
|
||||
order by warning.risk_level_sort asc, warning.model_count desc, warning.risk_count desc, warning.latest_trade_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectExternalPersonWarningList" resultMap="ExternalPersonWarningItemResultMap">
|
||||
<bind name="externalProjectId" value="projectId"/>
|
||||
select *
|
||||
from (
|
||||
<include refid="externalPersonWarningSelectSql"/>
|
||||
) warning
|
||||
order by warning.risk_level_sort asc, warning.model_count desc, warning.risk_count desc, warning.latest_trade_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectExternalRiskSummaryByProjectId" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO">
|
||||
<bind name="externalProjectId" value="projectId"/>
|
||||
select
|
||||
count(*) as total,
|
||||
coalesce(sum(case when risk.risk_level_code = 'HIGH' then 1 else 0 end), 0) as high,
|
||||
coalesce(sum(case when risk.risk_level_code = 'MEDIUM' then 1 else 0 end), 0) as medium,
|
||||
coalesce(sum(case when risk.risk_level_code = 'LOW' then 1 else 0 end), 0) as low,
|
||||
coalesce(sum(case when risk.risk_level_code is null then 1 else 0 end), 0) as noRisk
|
||||
from (
|
||||
<include refid="externalPersonSubjectSql"/>
|
||||
) subject
|
||||
left join (
|
||||
<include refid="externalPersonAggregateSql"/>
|
||||
) risk
|
||||
on risk.project_id = subject.project_id
|
||||
and risk.cert_no = subject.cert_no
|
||||
</select>
|
||||
|
||||
<select id="selectExternalRiskModelCardsByProjectId" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO">
|
||||
<bind name="externalProjectId" value="projectId"/>
|
||||
select
|
||||
model_scope.model_code,
|
||||
max(model_scope.model_name) as model_name,
|
||||
count(*) as warning_count,
|
||||
count(distinct model_scope.cert_no) as people_count
|
||||
from (
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) model_scope
|
||||
group by model_scope.model_code
|
||||
order by warning_count desc, model_scope.model_code asc
|
||||
</select>
|
||||
|
||||
<select id="selectExternalRiskModelPeoplePage" resultMap="ExternalRiskModelPeopleItemResultMap">
|
||||
<bind name="externalProjectId" value="query.projectId"/>
|
||||
select
|
||||
warning.project_id,
|
||||
warning.cert_no,
|
||||
warning.person_name,
|
||||
warning.subject_type,
|
||||
warning.related_object,
|
||||
#{query.modelCodesCsv} as selected_model_codes
|
||||
from (
|
||||
<include refid="externalPersonWarningSelectSql"/>
|
||||
) warning
|
||||
where 1 = 1
|
||||
<if test="query.modelCodes != null and query.modelCodes.size() > 0">
|
||||
<choose>
|
||||
<when test="query.matchMode == 'ALL'">
|
||||
<foreach collection="query.modelCodes" item="modelCode">
|
||||
and exists (
|
||||
select 1
|
||||
from (
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) source
|
||||
where source.cert_no = warning.cert_no
|
||||
and source.model_code = #{modelCode}
|
||||
)
|
||||
</foreach>
|
||||
</when>
|
||||
<otherwise>
|
||||
and exists (
|
||||
select 1
|
||||
from (
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) source
|
||||
where source.cert_no = warning.cert_no
|
||||
and source.model_code in
|
||||
<foreach collection="query.modelCodes" item="modelCode" open="(" separator="," close=")">
|
||||
#{modelCode}
|
||||
</foreach>
|
||||
)
|
||||
</otherwise>
|
||||
</choose>
|
||||
</if>
|
||||
<if test="query.keyword != null and query.keyword != ''">
|
||||
and (
|
||||
warning.person_name like concat('%', trim(#{query.keyword}), '%')
|
||||
or warning.cert_no like concat('%', trim(#{query.keyword}), '%')
|
||||
)
|
||||
</if>
|
||||
order by warning.person_name asc, warning.cert_no asc
|
||||
</select>
|
||||
|
||||
<select id="selectExternalRiskModelPeopleList" resultMap="ExternalRiskModelPeopleItemResultMap">
|
||||
<bind name="externalProjectId" value="query.projectId"/>
|
||||
select
|
||||
warning.project_id,
|
||||
warning.cert_no,
|
||||
warning.person_name,
|
||||
warning.subject_type,
|
||||
warning.related_object,
|
||||
#{query.modelCodesCsv} as selected_model_codes
|
||||
from (
|
||||
<include refid="externalPersonWarningSelectSql"/>
|
||||
) warning
|
||||
where 1 = 1
|
||||
<if test="query.modelCodes != null and query.modelCodes.size() > 0">
|
||||
<choose>
|
||||
<when test="query.matchMode == 'ALL'">
|
||||
<foreach collection="query.modelCodes" item="modelCode">
|
||||
and exists (
|
||||
select 1
|
||||
from (
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) source
|
||||
where source.cert_no = warning.cert_no
|
||||
and source.model_code = #{modelCode}
|
||||
)
|
||||
</foreach>
|
||||
</when>
|
||||
<otherwise>
|
||||
and exists (
|
||||
select 1
|
||||
from (
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) source
|
||||
where source.cert_no = warning.cert_no
|
||||
and source.model_code in
|
||||
<foreach collection="query.modelCodes" item="modelCode" open="(" separator="," close=")">
|
||||
#{modelCode}
|
||||
</foreach>
|
||||
)
|
||||
</otherwise>
|
||||
</choose>
|
||||
</if>
|
||||
<if test="query.keyword != null and query.keyword != ''">
|
||||
and (
|
||||
warning.person_name like concat('%', trim(#{query.keyword}), '%')
|
||||
or warning.cert_no like concat('%', trim(#{query.keyword}), '%')
|
||||
)
|
||||
</if>
|
||||
order by warning.person_name asc, warning.cert_no asc
|
||||
</select>
|
||||
|
||||
<select id="selectExternalRiskModelNamesByScope" resultType="java.lang.String">
|
||||
<bind name="externalProjectId" value="projectId"/>
|
||||
select distinct source.model_name
|
||||
from (
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) source
|
||||
where source.cert_no = #{certNo}
|
||||
<if test="selectedModelCodes != null and selectedModelCodes != ''">
|
||||
and find_in_set(source.model_code, #{selectedModelCodes})
|
||||
</if>
|
||||
order by source.model_name asc
|
||||
</select>
|
||||
|
||||
<select id="selectExternalRiskHitTagsByScope" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO">
|
||||
<bind name="externalProjectId" value="projectId"/>
|
||||
select distinct
|
||||
source.model_code as modelCode,
|
||||
source.model_name as modelName,
|
||||
source.rule_code as ruleCode,
|
||||
source.rule_name as ruleName,
|
||||
source.risk_level as riskLevel
|
||||
from (
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) source
|
||||
where source.cert_no = #{certNo}
|
||||
<if test="selectedModelCodes != null and selectedModelCodes != ''">
|
||||
and find_in_set(source.model_code, #{selectedModelCodes})
|
||||
</if>
|
||||
order by source.model_code asc, source.rule_code asc
|
||||
</select>
|
||||
|
||||
<sql id="suspiciousTransactionBaseSql">
|
||||
select
|
||||
bs.bank_statement_id as bankStatementId,
|
||||
@@ -606,6 +1040,34 @@
|
||||
) hits
|
||||
</sql>
|
||||
|
||||
<sql id="externalSuspiciousTransactionSql">
|
||||
select
|
||||
bs.bank_statement_id as bankStatementId,
|
||||
bs.TRX_DATE as trxDate,
|
||||
source.person_name as relatedPersonName,
|
||||
null as relatedStaffName,
|
||||
null as relatedStaffCode,
|
||||
source.subject_type as relationType,
|
||||
bs.USER_MEMO as userMemo,
|
||||
bs.CASH_TYPE as cashType,
|
||||
case
|
||||
when ifnull(bs.AMOUNT_CR, 0) > 0 then bs.AMOUNT_CR
|
||||
when ifnull(bs.AMOUNT_DR, 0) > 0 then -bs.AMOUNT_DR
|
||||
else 0
|
||||
end as displayAmount,
|
||||
1 as hasModelRuleHit,
|
||||
0 as hasNameListHit,
|
||||
source.person_name as suspiciousPersonName,
|
||||
9 as matchPriority,
|
||||
'外部人员预警' as nameListHitType
|
||||
from (
|
||||
<bind name="externalProjectId" value="query.projectId"/>
|
||||
<include refid="externalPersonSourceSql"/>
|
||||
) source
|
||||
inner join ccdi_bank_statement bs
|
||||
on bs.bank_statement_id = source.bank_statement_id
|
||||
</sql>
|
||||
|
||||
<sql id="suspiciousTransactionMergedSql">
|
||||
select
|
||||
base.bankStatementId,
|
||||
@@ -652,6 +1114,27 @@
|
||||
inner join (
|
||||
<include refid="suspiciousTransactionNameHitSql"/>
|
||||
) name_hits on name_hits.bankStatementId = base.bankStatementId
|
||||
|
||||
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
|
||||
</sql>
|
||||
|
||||
<sql id="suspiciousTransactionAggregatedSql">
|
||||
@@ -706,6 +1189,9 @@
|
||||
<when test="query.suspiciousType == 'MODEL_RULE'">
|
||||
where final_result.hasModelRuleHit = 1
|
||||
</when>
|
||||
<when test="query.suspiciousType == 'EXTERNAL_PERSON'">
|
||||
where final_result.nameListHitType = '外部人员预警'
|
||||
</when>
|
||||
<otherwise>
|
||||
where final_result.hasModelRuleHit = 1 or final_result.hasNameListHit = 1
|
||||
</otherwise>
|
||||
@@ -1073,4 +1559,5 @@
|
||||
group by base.staff_id_card
|
||||
) agg
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -114,32 +114,18 @@
|
||||
|
||||
<sql id="projectEmployeeScopeSql">
|
||||
select distinct
|
||||
coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) as staff_id_card,
|
||||
cast(coalesce(direct_staff.staff_id, statement_staff.staff_id, family_staff.staff_id) as char) as staff_code,
|
||||
coalesce(direct_staff.name, statement_staff.name, family_staff.name) as staff_name,
|
||||
statement_staff.id_card as staff_id_card,
|
||||
cast(statement_staff.staff_id as char) as staff_code,
|
||||
statement_staff.name as staff_name,
|
||||
dept.dept_name
|
||||
from ccdi_bank_statement_tag_result tr
|
||||
left join ccdi_base_staff direct_staff
|
||||
on tr.object_type = 'STAFF_ID_CARD'
|
||||
and tr.object_key = direct_staff.id_card
|
||||
left join ccdi_bank_statement bs
|
||||
on tr.bank_statement_id = bs.bank_statement_id
|
||||
left join ccdi_base_staff statement_staff
|
||||
on (tr.object_key is null or tr.object_key = '')
|
||||
and bs.cret_no = statement_staff.id_card
|
||||
left join ccdi_staff_fmy_relation relation
|
||||
on relation.status = 1
|
||||
and (
|
||||
((tr.object_key is null or tr.object_key = '') and bs.cret_no = relation.relation_cert_no)
|
||||
or ((tr.object_key is not null and tr.object_key != '') and tr.object_type != 'STAFF_ID_CARD'
|
||||
and tr.object_key = relation.relation_cert_no)
|
||||
)
|
||||
left join ccdi_base_staff family_staff
|
||||
on relation.person_id = family_staff.id_card
|
||||
from ccdi_bank_statement bs
|
||||
inner join ccdi_base_staff statement_staff
|
||||
on statement_staff.id_card = trim(bs.cret_no)
|
||||
left join sys_dept dept
|
||||
on dept.dept_id = coalesce(direct_staff.dept_id, statement_staff.dept_id, family_staff.dept_id)
|
||||
where tr.project_id = #{projectId}
|
||||
and coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) is not null
|
||||
on dept.dept_id = statement_staff.dept_id
|
||||
where bs.project_id = #{projectId}
|
||||
and bs.cret_no is not null
|
||||
and trim(bs.cret_no) != ''
|
||||
</sql>
|
||||
|
||||
<sql id="spouseRelationSql">
|
||||
|
||||
@@ -281,7 +281,8 @@ class CcdiProjectOverviewControllerTest {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||
CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel();
|
||||
row.setSuspiciousPersonName("张三");
|
||||
row.setLeAccountName("张三");
|
||||
row.setCustomerAccountName("测试对手方");
|
||||
row.setDisplayAmount(new java.math.BigDecimal("10.00"));
|
||||
when(overviewService.exportSuspiciousTransactions(same(queryDTO))).thenReturn(List.of(row));
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiBankTagAnalysisMapperXmlTest {
|
||||
@@ -26,11 +27,18 @@ class CcdiBankTagAnalysisMapperXmlTest {
|
||||
"selectForexSellAmtStatements",
|
||||
"selectLargePurchaseTransactionStatements",
|
||||
"selectStockTfrLargeStatements",
|
||||
"selectLargeStockTradingStatements"
|
||||
"selectLargeStockTradingStatements",
|
||||
"selectExternalSingleLargeAmountStatements",
|
||||
"selectExternalNightTransactionStatements",
|
||||
"selectExternalGamblingMemoStatements",
|
||||
"selectExternalToStaffOrFamilyTransactionStatements"
|
||||
);
|
||||
private static final List<String> PHASE_TWO_OBJECT_SELECT_IDS = List.of(
|
||||
"selectLowIncomeRelativeLargeTransactionObjects",
|
||||
"selectMultiPartyGamblingTransferObjects",
|
||||
"selectExternalCumulativeTransactionAmountObjects",
|
||||
"selectExternalAnnualTurnoverObjects",
|
||||
"selectExternalMultiPartyGamblingTransferObjects",
|
||||
"selectMonthlyFixedIncomeObjects",
|
||||
"selectFixedCounterpartyTransferObjects",
|
||||
"selectSupplierConcentrationObjects",
|
||||
@@ -100,7 +108,10 @@ class CcdiBankTagAnalysisMapperXmlTest {
|
||||
void placeholderRules_shouldUseEmptyResultSqlTemplate() throws Exception {
|
||||
String xml = readXml(RESOURCE);
|
||||
assertTrue(xml.contains("占位SQL,待补充真实规则"));
|
||||
assertEquals(5, countMatches(xml, "where 1 = 0"));
|
||||
assertEquals(
|
||||
countMatches(xml, "占位SQL,待补充真实规则"),
|
||||
countMatches(xml, "where 1 = 0")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -156,7 +167,11 @@ class CcdiBankTagAnalysisMapperXmlTest {
|
||||
String xml = readXml(RESOURCE);
|
||||
for (String selectId : PHASE_TWO_OBJECT_SELECT_IDS) {
|
||||
String selectSql = extractSelectSql(xml, selectId);
|
||||
assertTrue(selectSql.contains("'STAFF_ID_CARD' AS objectType"), () -> selectId + " 缺少 objectType");
|
||||
assertTrue(
|
||||
selectSql.contains("'STAFF_ID_CARD' AS objectType")
|
||||
|| selectSql.contains("'EXTERNAL_CERT_NO' AS objectType"),
|
||||
() -> selectId + " 缺少 objectType"
|
||||
);
|
||||
assertTrue(selectSql.contains("AS objectKey"), () -> selectId + " 缺少 objectKey");
|
||||
assertTrue(selectSql.contains("reasonDetail"), () -> selectId + " 缺少 reasonDetail");
|
||||
assertTrue(!selectSql.contains("where 1 = 0"), () -> selectId + " 仍是占位 SQL");
|
||||
@@ -221,6 +236,31 @@ class CcdiBankTagAnalysisMapperXmlTest {
|
||||
factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void externalPersonRules_shouldUseExternalSubjectScopeAndCounterpartyEmployeeMatching() throws Exception {
|
||||
String xml = readXml(RESOURCE);
|
||||
String scopeSql = extractSqlFragment(xml, "externalPersonPredicateSql");
|
||||
String relationSql = extractSelectSql(xml, "selectExternalToStaffOrFamilyTransactionStatements");
|
||||
String annualSql = extractSelectSql(xml, "selectExternalAnnualTurnoverObjects");
|
||||
String gamblingSql = extractSelectSql(xml, "selectExternalMultiPartyGamblingTransferObjects");
|
||||
|
||||
assertTrue(scopeSql.contains("bs.cret_no is not null"));
|
||||
assertTrue(scopeSql.contains("staff.id_card = bs.cret_no"));
|
||||
assertTrue(scopeSql.contains("relation.relation_cert_no = bs.cret_no"));
|
||||
assertTrue(scopeSql.contains("trim(IFNULL(bs.LE_ACCOUNT_NAME, '')) <> trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''))"));
|
||||
assertFalse(scopeSql.contains("LE_ACCOUNT_NO"));
|
||||
assertTrue(annualSql.contains("'EXTERNAL_CERT_NO' AS objectType"));
|
||||
assertTrue(annualSql.contains("bs.cret_no AS certNo"));
|
||||
assertTrue(gamblingSql.contains("'EXTERNAL_CERT_NO' AS objectType"));
|
||||
assertTrue(gamblingSql.contains("having COUNT(1) > 2"));
|
||||
assertTrue(relationSql.contains("counter_account.owner_type in ('EMPLOYEE', 'RELATION', 'INTERMEDIARY', 'CREDIT_CUSTOMER')"));
|
||||
assertTrue(relationSql.contains("on counter_account.account_no is null"));
|
||||
assertTrue(relationSql.contains("counter_staff.name = trim(bs.CUSTOMER_ACCOUNT_NAME)"));
|
||||
assertTrue(relationSql.contains("counter_relation.relation_name = trim(bs.CUSTOMER_ACCOUNT_NAME)"));
|
||||
assertTrue(relationSql.contains("counter_account.owner_type in ('EMPLOYEE', 'RELATION')"));
|
||||
assertFalse(relationSql.contains("customer_cert_no"));
|
||||
}
|
||||
|
||||
private String readXml(String resource) throws Exception {
|
||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource)) {
|
||||
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
@@ -112,6 +112,33 @@ class CcdiProjectOverviewMapperSqlTest {
|
||||
assertTrue(reportSuspiciousSql.contains("与信贷客户之间非正常资金往来"), reportSuspiciousSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
void externalPersonSourceSql_shouldIncludeStatementAndObjectHits() throws Exception {
|
||||
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
||||
String externalSourceSql = extractSqlFragment(xml, "externalPersonSourceSql");
|
||||
String normalizedExternalSourceSql = externalSourceSql.replace("\r\n", "\n");
|
||||
|
||||
assertTrue(externalSourceSql.contains("tr.bank_statement_id = bs.bank_statement_id"), externalSourceSql);
|
||||
assertTrue(externalSourceSql.contains("union all"), externalSourceSql);
|
||||
assertTrue(externalSourceSql.contains("tr.object_type = 'EXTERNAL_CERT_NO'"), externalSourceSql);
|
||||
assertTrue(externalSourceSql.contains("tr.object_key = subject.cert_no"), externalSourceSql);
|
||||
assertTrue(externalSourceSql.contains("'资金' as related_object"), externalSourceSql);
|
||||
assertFalse(externalSourceSql.contains("counter_staff.id_card = bs.customer_cert_no"), externalSourceSql);
|
||||
assertFalse(externalSourceSql.contains("counter_relation.relation_cert_no = bs.customer_cert_no"), externalSourceSql);
|
||||
assertFalse(externalSourceSql.contains("counter_intermediary.person_id = bs.customer_cert_no"), externalSourceSql);
|
||||
assertInOrder(
|
||||
externalSourceSql,
|
||||
"when counter_account.owner_type = 'EMPLOYEE' then '员工'",
|
||||
"when counter_account.owner_type = 'RELATION' then '员工亲属'",
|
||||
"when counter_account.owner_type = 'CREDIT_CUSTOMER' then '信贷客户'",
|
||||
"when counter_account.owner_type = 'INTERMEDIARY' then '中介库人员'",
|
||||
"when counter_staff.id_card is not null then '员工'",
|
||||
"when counter_relation.relation_cert_no is not null then '员工亲属'"
|
||||
);
|
||||
assertTrue(normalizedExternalSourceSql.contains("left join ccdi_base_staff counter_staff\n on counter_account.account_no is null"), externalSourceSql);
|
||||
assertTrue(normalizedExternalSourceSql.contains("left join ccdi_staff_fmy_relation counter_relation\n on counter_account.account_no is null"), externalSourceSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
void suspiciousTransactionNameListSql_shouldKeepCreditCustomerAndIntermediaryRulesScopedByAmount() throws Exception {
|
||||
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
||||
@@ -205,4 +232,13 @@ class CcdiProjectOverviewMapperSqlTest {
|
||||
assertTrue(endIndex >= 0, "missing closing sql tag: " + sqlId);
|
||||
return xml.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
private void assertInOrder(String sql, String... fragments) {
|
||||
int previousIndex = -1;
|
||||
for (String fragment : fragments) {
|
||||
int currentIndex = sql.indexOf(fragment);
|
||||
assertTrue(currentIndex > previousIndex, () -> "fragment order mismatch: " + fragment + "\n" + sql);
|
||||
previousIndex = currentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ class CcdiProjectSpecialCheckMapperListSqlTest {
|
||||
String listSql = extractSelect(xml, "selectFamilyAssetLiabilityList");
|
||||
|
||||
assertTrue(listSql.contains("order by risk_level_sort desc, comparison_amount desc, staff_name asc"));
|
||||
assertTrue(xml.contains("from ccdi_bank_statement_tag_result"));
|
||||
assertTrue(xml.contains("from ccdi_bank_statement bs"));
|
||||
assertTrue(xml.contains("statement_staff.id_card = trim(bs.cret_no)"));
|
||||
assertTrue(xml.contains("bs.project_id = #{projectId}"));
|
||||
assertTrue(xml.contains("ccdi_base_staff"));
|
||||
assertTrue(xml.contains("ccdi_staff_fmy_relation"));
|
||||
assertTrue(xml.contains("relation_type = '配偶'"));
|
||||
|
||||
@@ -132,6 +132,49 @@ class BankTagRuleConfigResolverTest {
|
||||
assertEquals("8888", config.getThresholdValue("ANNUAL_TURNOVER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_shouldUseLargeTransactionParamsForExternalLargeTransactionRule() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(40L);
|
||||
project.setConfigType("default");
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
|
||||
buildParam("LARGE_TRANSACTION", "FREQUENT_TRANSFER", "100000")
|
||||
));
|
||||
|
||||
CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
|
||||
ruleMeta.setModelCode("EXTERNAL_LARGE_TRANSACTION");
|
||||
ruleMeta.setRuleCode("EXTERNAL_SINGLE_LARGE_AMOUNT");
|
||||
ruleMeta.setIndicatorCode("FREQUENT_TRANSFER");
|
||||
|
||||
BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);
|
||||
|
||||
assertEquals("100000", config.getThresholdValue("FREQUENT_TRANSFER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_shouldUseOriginalModelParamsForExternalObjectRules() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(40L);
|
||||
project.setConfigType("default");
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
|
||||
buildParam("LARGE_TRANSACTION", "CUMULATIVE_TRANSACTION_AMOUNT", "500000"),
|
||||
buildParam("LARGE_TRANSACTION", "ANNUAL_TURNOVER", "800000")
|
||||
));
|
||||
when(modelParamMapper.selectByProjectAndModel(0L, "SUSPICIOUS_GAMBLING")).thenReturn(List.of(
|
||||
buildParam("SUSPICIOUS_GAMBLING", "MULTI_PARTY_AMT_MIN", "500"),
|
||||
buildParam("SUSPICIOUS_GAMBLING", "MULTI_PARTY_AMT_MAX", "5000")
|
||||
));
|
||||
|
||||
assertRuleThresholds("EXTERNAL_LARGE_TRANSACTION", "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT",
|
||||
Map.of("CUMULATIVE_TRANSACTION_AMOUNT", "500000"));
|
||||
assertRuleThresholds("EXTERNAL_LARGE_TRANSACTION", "EXTERNAL_ANNUAL_TURNOVER",
|
||||
Map.of("ANNUAL_TURNOVER", "800000"));
|
||||
assertRuleThresholds("EXTERNAL_SUSPICIOUS_GAMBLING", "EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER",
|
||||
Map.of("MULTI_PARTY_AMT_MIN", "500", "MULTI_PARTY_AMT_MAX", "5000"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_shouldMapPhaseOneThresholdRulesToUppercaseParamCodes() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
|
||||
@@ -496,6 +496,95 @@ class CcdiBankTagServiceImplTest {
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rebuildProject_shouldDispatchExternalPersonStatementRules() {
|
||||
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
|
||||
|
||||
CcdiBankTagRule largeRule = buildRule("EXTERNAL_LARGE_TRANSACTION", "外部人员大额交易",
|
||||
"EXTERNAL_SINGLE_LARGE_AMOUNT", "外部人员单笔大额交易", "STATEMENT");
|
||||
CcdiBankTagRule nightRule = buildRule("EXTERNAL_ABNORMAL_TRANSACTION", "外部人员异常交易",
|
||||
"EXTERNAL_NIGHT_TRANSACTION", "外部人员夜间集中交易", "STATEMENT");
|
||||
CcdiBankTagRule gamblingRule = buildRule("EXTERNAL_SUSPICIOUS_GAMBLING", "外部人员可疑赌博",
|
||||
"EXTERNAL_GAMBLING_MEMO", "外部人员疑似赌博摘要", "STATEMENT");
|
||||
CcdiBankTagRule relationRule = buildRule("EXTERNAL_SUSPICIOUS_RELATION", "外部人员可疑关系",
|
||||
"EXTERNAL_TO_STAFF_FAMILY_TRANSACTION", "外部人员与员工或员工亲属交易", "STATEMENT");
|
||||
BankTagRuleExecutionConfig largeConfig = buildConfig(40L, largeRule);
|
||||
largeConfig.setThresholdValues(Map.of("FREQUENT_TRANSFER", "100000"));
|
||||
|
||||
BankTagStatementHitVO hit = new BankTagStatementHitVO();
|
||||
hit.setBankStatementId(100L);
|
||||
hit.setGroupId(40);
|
||||
hit.setLogId(40001);
|
||||
hit.setReasonDetail("外部人员命中");
|
||||
|
||||
when(ruleMapper.selectEnabledRules(null)).thenReturn(List.of(largeRule, nightRule, gamblingRule, relationRule));
|
||||
when(configResolver.resolve(40L, largeRule)).thenReturn(largeConfig);
|
||||
when(configResolver.resolve(40L, nightRule)).thenReturn(buildConfig(40L, nightRule));
|
||||
when(configResolver.resolve(40L, gamblingRule)).thenReturn(buildConfig(40L, gamblingRule));
|
||||
when(configResolver.resolve(40L, relationRule)).thenReturn(buildConfig(40L, relationRule));
|
||||
when(analysisMapper.selectExternalSingleLargeAmountStatements(40L, new BigDecimal("100000"))).thenReturn(List.of(hit));
|
||||
when(analysisMapper.selectExternalNightTransactionStatements(40L)).thenReturn(List.of());
|
||||
when(analysisMapper.selectExternalGamblingMemoStatements(40L)).thenReturn(List.of());
|
||||
when(analysisMapper.selectExternalToStaffOrFamilyTransactionStatements(40L)).thenReturn(List.of());
|
||||
|
||||
service.rebuildProject(40L, null, "admin", TriggerType.MANUAL);
|
||||
|
||||
verify(analysisMapper).selectExternalSingleLargeAmountStatements(40L, new BigDecimal("100000"));
|
||||
verify(analysisMapper).selectExternalNightTransactionStatements(40L);
|
||||
verify(analysisMapper).selectExternalGamblingMemoStatements(40L);
|
||||
verify(analysisMapper).selectExternalToStaffOrFamilyTransactionStatements(40L);
|
||||
verify(resultMapper).insertBatch(argThat(results -> results.stream().anyMatch(item ->
|
||||
"EXTERNAL_LARGE_TRANSACTION".equals(item.getModelCode())
|
||||
&& "EXTERNAL_SINGLE_LARGE_AMOUNT".equals(item.getRuleCode())
|
||||
&& "STATEMENT".equals(item.getResultType())
|
||||
&& Long.valueOf(100L).equals(item.getBankStatementId())
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rebuildProject_shouldDispatchExternalPersonObjectRules() {
|
||||
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
|
||||
|
||||
CcdiBankTagRule cumulativeRule = buildRule("EXTERNAL_LARGE_TRANSACTION", "外部人员大额交易",
|
||||
"EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT", "外部人员累计交易超限", "OBJECT");
|
||||
CcdiBankTagRule annualRule = buildRule("EXTERNAL_LARGE_TRANSACTION", "外部人员大额交易",
|
||||
"EXTERNAL_ANNUAL_TURNOVER", "外部人员年流水交易额超限", "OBJECT");
|
||||
CcdiBankTagRule gamblingRule = buildRule("EXTERNAL_SUSPICIOUS_GAMBLING", "外部人员可疑赌博",
|
||||
"EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER", "外部人员同日多对手方疑似赌博交易", "OBJECT");
|
||||
BankTagRuleExecutionConfig cumulativeConfig = buildConfig(40L, cumulativeRule);
|
||||
cumulativeConfig.setThresholdValues(Map.of("CUMULATIVE_TRANSACTION_AMOUNT", "500000"));
|
||||
BankTagRuleExecutionConfig annualConfig = buildConfig(40L, annualRule);
|
||||
annualConfig.setThresholdValues(Map.of("ANNUAL_TURNOVER", "800000"));
|
||||
BankTagRuleExecutionConfig gamblingConfig = buildConfig(40L, gamblingRule);
|
||||
gamblingConfig.setThresholdValues(Map.of("MULTI_PARTY_AMT_MIN", "500", "MULTI_PARTY_AMT_MAX", "5000"));
|
||||
|
||||
BankTagObjectHitVO hit = new BankTagObjectHitVO();
|
||||
hit.setObjectType("EXTERNAL_CERT_NO");
|
||||
hit.setObjectKey("330100198801010033");
|
||||
hit.setReasonDetail("外部人员累计交易超限");
|
||||
|
||||
when(ruleMapper.selectEnabledRules(null)).thenReturn(List.of(cumulativeRule, annualRule, gamblingRule));
|
||||
when(configResolver.resolve(40L, cumulativeRule)).thenReturn(cumulativeConfig);
|
||||
when(configResolver.resolve(40L, annualRule)).thenReturn(annualConfig);
|
||||
when(configResolver.resolve(40L, gamblingRule)).thenReturn(gamblingConfig);
|
||||
when(analysisMapper.selectExternalCumulativeTransactionAmountObjects(40L, new BigDecimal("500000"))).thenReturn(List.of(hit));
|
||||
when(analysisMapper.selectExternalAnnualTurnoverObjects(40L, new BigDecimal("800000"))).thenReturn(List.of());
|
||||
when(analysisMapper.selectExternalMultiPartyGamblingTransferObjects(40L, new BigDecimal("500"), new BigDecimal("5000"))).thenReturn(List.of());
|
||||
|
||||
service.rebuildProject(40L, null, "admin", TriggerType.MANUAL);
|
||||
|
||||
verify(analysisMapper).selectExternalCumulativeTransactionAmountObjects(40L, new BigDecimal("500000"));
|
||||
verify(analysisMapper).selectExternalAnnualTurnoverObjects(40L, new BigDecimal("800000"));
|
||||
verify(analysisMapper).selectExternalMultiPartyGamblingTransferObjects(40L, new BigDecimal("500"), new BigDecimal("5000"));
|
||||
verify(resultMapper).insertBatch(argThat(results -> results.stream().anyMatch(item ->
|
||||
"EXTERNAL_LARGE_TRANSACTION".equals(item.getModelCode())
|
||||
&& "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT".equals(item.getRuleCode())
|
||||
&& "OBJECT".equals(item.getResultType())
|
||||
&& "EXTERNAL_CERT_NO".equals(item.getObjectType())
|
||||
&& "330100198801010033".equals(item.getObjectKey())
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildSafeTaskErrorMessage_shouldKeepLongMessageForLongTextColumn() throws Exception {
|
||||
Method method = CcdiBankTagServiceImpl.class.getDeclaredMethod(
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.project.service.impl;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
|
||||
@@ -10,8 +11,10 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportUploadSubjectVO
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewStatVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
@@ -19,6 +22,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiProjectOverviewReportPdfExporterTest {
|
||||
@@ -36,6 +40,54 @@ class CcdiProjectOverviewReportPdfExporterTest {
|
||||
assertTrue(response.getContentAsByteArray().length > 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseOverallRiskSummaryInReportMetrics() throws Exception {
|
||||
CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter();
|
||||
CcdiProjectOverviewReportVO report = buildReport();
|
||||
report.setExternalRiskSummary(buildExternalSummary());
|
||||
|
||||
List<CcdiProjectOverviewStatVO> stats = invokeOverallRiskMetrics(exporter, report);
|
||||
|
||||
assertEquals(12, stats.get(0).getValue());
|
||||
assertEquals(3, stats.get(1).getValue());
|
||||
assertEquals(4, stats.get(2).getValue());
|
||||
assertEquals(1, stats.get(3).getValue());
|
||||
assertEquals("无风险", stats.get(4).getLabel());
|
||||
assertEquals(4, stats.get(4).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHideExternalSectionWhenReportHasOnlyEmployees() throws Exception {
|
||||
CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
CcdiProjectOverviewReportVO report = buildReport();
|
||||
|
||||
exporter.export(response, report);
|
||||
|
||||
assertEquals("application/pdf", response.getContentType());
|
||||
assertFalse(invokeHasExternalRisk(exporter, report));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldShowExternalSectionWhenExternalRiskExists() throws Exception {
|
||||
CcdiProjectOverviewReportPdfExporter exporter = new CcdiProjectOverviewReportPdfExporter();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
CcdiProjectOverviewReportVO report = buildReport();
|
||||
report.setExternalRiskSummary(buildExternalSummary());
|
||||
report.setExternalModelSummaries(List.of(buildExternalModelSummary()));
|
||||
report.setExternalPersonWarnings(List.of(buildExternalPersonWarning()));
|
||||
|
||||
exporter.export(response, report);
|
||||
List<CcdiProjectOverviewStatVO> externalMetrics = invokeExternalMetrics(exporter, report);
|
||||
|
||||
assertEquals("application/pdf", response.getContentType());
|
||||
assertTrue(invokeHasExternalRisk(exporter, report));
|
||||
assertEquals("外部人员", externalMetrics.get(0).getLabel());
|
||||
assertEquals(2, externalMetrics.get(0).getValue());
|
||||
assertEquals("高风险", externalMetrics.get(1).getLabel());
|
||||
assertEquals(1, externalMetrics.get(1).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tableGap_shouldLeaveEnoughSpaceForNextSectionTitle() throws Exception {
|
||||
Class<?> writerClass = Class.forName(
|
||||
@@ -47,6 +99,44 @@ class CcdiProjectOverviewReportPdfExporterTest {
|
||||
assertTrue(tableAfterGap > sectionFontSize);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<CcdiProjectOverviewStatVO> invokeOverallRiskMetrics(
|
||||
CcdiProjectOverviewReportPdfExporter exporter,
|
||||
CcdiProjectOverviewReportVO report
|
||||
) throws Exception {
|
||||
Method method = CcdiProjectOverviewReportPdfExporter.class.getDeclaredMethod(
|
||||
"buildOverallRiskMetrics",
|
||||
CcdiProjectOverviewReportVO.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
return (List<CcdiProjectOverviewStatVO>) method.invoke(exporter, report);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<CcdiProjectOverviewStatVO> invokeExternalMetrics(
|
||||
CcdiProjectOverviewReportPdfExporter exporter,
|
||||
CcdiProjectOverviewReportVO report
|
||||
) throws Exception {
|
||||
Method method = CcdiProjectOverviewReportPdfExporter.class.getDeclaredMethod(
|
||||
"buildExternalMetrics",
|
||||
CcdiProjectOverviewReportVO.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
return (List<CcdiProjectOverviewStatVO>) method.invoke(exporter, report);
|
||||
}
|
||||
|
||||
private boolean invokeHasExternalRisk(
|
||||
CcdiProjectOverviewReportPdfExporter exporter,
|
||||
CcdiProjectOverviewReportVO report
|
||||
) throws Exception {
|
||||
Method method = CcdiProjectOverviewReportPdfExporter.class.getDeclaredMethod(
|
||||
"hasExternalRisk",
|
||||
CcdiProjectOverviewReportVO.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
return (Boolean) method.invoke(exporter, report);
|
||||
}
|
||||
|
||||
private CcdiProjectOverviewReportVO buildReport() {
|
||||
CcdiProjectOverviewReportVO report = new CcdiProjectOverviewReportVO();
|
||||
CcdiProject project = new CcdiProject();
|
||||
@@ -73,22 +163,33 @@ class CcdiProjectOverviewReportPdfExporterTest {
|
||||
private CcdiProjectOverviewDashboardVO buildDashboard() {
|
||||
CcdiProjectOverviewDashboardVO dashboard = new CcdiProjectOverviewDashboardVO();
|
||||
dashboard.setStats(List.of(
|
||||
buildStat("总人数", 10),
|
||||
buildStat("高风险", 2),
|
||||
buildStat("中风险", 3),
|
||||
buildStat("低风险", 1),
|
||||
buildStat("无风险", 4)
|
||||
buildStat("people", "总人数", 10),
|
||||
buildStat("riskPeople", "高风险", 2),
|
||||
buildStat("medium", "中风险", 3),
|
||||
buildStat("low", "低风险", 1),
|
||||
buildStat("count", "无风险人员", 4)
|
||||
));
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
private CcdiProjectOverviewStatVO buildStat(String label, Integer value) {
|
||||
private CcdiProjectOverviewStatVO buildStat(String key, String label, Integer value) {
|
||||
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
|
||||
stat.setKey(key);
|
||||
stat.setLabel(label);
|
||||
stat.setValue(value);
|
||||
return stat;
|
||||
}
|
||||
|
||||
private CcdiProjectExternalRiskSummaryVO buildExternalSummary() {
|
||||
CcdiProjectExternalRiskSummaryVO summary = new CcdiProjectExternalRiskSummaryVO();
|
||||
summary.setTotal(2);
|
||||
summary.setHigh(1);
|
||||
summary.setMedium(1);
|
||||
summary.setLow(0);
|
||||
summary.setNoRisk(0);
|
||||
return summary;
|
||||
}
|
||||
|
||||
private CcdiProjectOverviewReportUploadSubjectVO buildUploadSubject() {
|
||||
CcdiProjectOverviewReportUploadSubjectVO row = new CcdiProjectOverviewReportUploadSubjectVO();
|
||||
row.setSubjectName("测试主体");
|
||||
@@ -117,6 +218,15 @@ class CcdiProjectOverviewReportPdfExporterTest {
|
||||
return row;
|
||||
}
|
||||
|
||||
private CcdiProjectOverviewReportModelSummaryVO buildExternalModelSummary() {
|
||||
CcdiProjectOverviewReportModelSummaryVO row = new CcdiProjectOverviewReportModelSummaryVO();
|
||||
row.setModelName("外部人员异常交易");
|
||||
row.setWarningCount(2);
|
||||
row.setPeopleCount(2);
|
||||
row.setPeopleNames("-");
|
||||
return row;
|
||||
}
|
||||
|
||||
private CcdiProjectRiskModelPeopleItemVO buildRiskPeople() {
|
||||
CcdiProjectRiskModelPeopleItemVO row = new CcdiProjectRiskModelPeopleItemVO();
|
||||
row.setStaffName("张三");
|
||||
@@ -130,6 +240,19 @@ class CcdiProjectOverviewReportPdfExporterTest {
|
||||
return row;
|
||||
}
|
||||
|
||||
private CcdiProjectExternalPersonWarningExcel buildExternalPersonWarning() {
|
||||
CcdiProjectExternalPersonWarningExcel row = new CcdiProjectExternalPersonWarningExcel();
|
||||
row.setName("外部人员甲");
|
||||
row.setIdNo("330000000000000003");
|
||||
row.setSubjectType("外部人员");
|
||||
row.setRiskLevel("高风险");
|
||||
row.setModelCount(1);
|
||||
row.setRiskPoint("疑似异常往来");
|
||||
row.setRelatedObject("张三");
|
||||
row.setLatestTradeTime("2026-06-25 10:00:00");
|
||||
return row;
|
||||
}
|
||||
|
||||
private CcdiProjectOverviewReportSuspiciousTransactionVO buildSuspiciousTransaction() {
|
||||
CcdiProjectOverviewReportSuspiciousTransactionVO row = new CcdiProjectOverviewReportSuspiciousTransactionVO();
|
||||
row.setTrxDate("2026-03-20 10:00:00");
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
|
||||
@@ -250,17 +251,20 @@ class CcdiProjectOverviewServiceImplTest {
|
||||
project.setProjectId(40L);
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
|
||||
CcdiProjectSuspiciousTransactionItemVO suspiciousItem = new CcdiProjectSuspiciousTransactionItemVO();
|
||||
CcdiProjectOverviewReportSuspiciousTransactionVO suspiciousItem =
|
||||
new CcdiProjectOverviewReportSuspiciousTransactionVO();
|
||||
suspiciousItem.setTrxDate("2026-03-20 10:00:00");
|
||||
suspiciousItem.setSuspiciousPersonName("张三");
|
||||
suspiciousItem.setRelatedPersonName("张三");
|
||||
suspiciousItem.setLeAccountNo("6222000000000000");
|
||||
suspiciousItem.setLeAccountName("张三");
|
||||
suspiciousItem.setCustomerAccountName("测试对手方");
|
||||
suspiciousItem.setCustomerAccountNo("6222000000000001");
|
||||
suspiciousItem.setRelatedStaffName("张三");
|
||||
suspiciousItem.setRelatedStaffCode("1001");
|
||||
suspiciousItem.setRelationType("本人");
|
||||
suspiciousItem.setUserMemo("转账");
|
||||
suspiciousItem.setCashType("转账");
|
||||
suspiciousItem.setHitTags("异常标签");
|
||||
suspiciousItem.setDisplayAmount(new BigDecimal("100.00"));
|
||||
when(overviewMapper.selectSuspiciousTransactionList(any())).thenReturn(List.of(suspiciousItem));
|
||||
when(overviewMapper.selectReportSuspiciousTransactionList(any())).thenReturn(List.of(suspiciousItem));
|
||||
|
||||
CcdiProjectEmployeeCreditNegativeItemVO creditItem = new CcdiProjectEmployeeCreditNegativeItemVO();
|
||||
creditItem.setPersonName("李四");
|
||||
@@ -282,14 +286,17 @@ class CcdiProjectOverviewServiceImplTest {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
service.exportRiskDetails(response, 40L);
|
||||
|
||||
verify(overviewMapper).selectSuspiciousTransactionList(argThat(query ->
|
||||
verify(overviewMapper).selectReportSuspiciousTransactionList(argThat(query ->
|
||||
query.getProjectId().equals(40L) && "ALL".equals(query.getSuspiciousType())
|
||||
));
|
||||
verify(workbookExporter).export(
|
||||
eq(response),
|
||||
eq(40L),
|
||||
argThat((List<CcdiProjectSuspiciousTransactionExcel> rows) ->
|
||||
rows.size() == 1 && "张三".equals(rows.getFirst().getSuspiciousPersonName())
|
||||
rows.size() == 1
|
||||
&& "张三".equals(rows.getFirst().getLeAccountName())
|
||||
&& "测试对手方".equals(rows.getFirst().getCustomerAccountName())
|
||||
&& "异常标签".equals(rows.getFirst().getHitTags())
|
||||
),
|
||||
argThat((List<CcdiProjectEmployeeCreditNegativeExcel> rows) ->
|
||||
rows.size() == 1 && "李四".equals(rows.getFirst().getPersonName())
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
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.CcdiProjectSuspiciousTransactionItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||
@@ -98,17 +99,20 @@ class CcdiProjectOverviewServiceSuspiciousTransactionTest {
|
||||
project.setProjectId(40L);
|
||||
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||
|
||||
CcdiProjectSuspiciousTransactionItemVO item = new CcdiProjectSuspiciousTransactionItemVO();
|
||||
CcdiProjectOverviewReportSuspiciousTransactionVO item =
|
||||
new CcdiProjectOverviewReportSuspiciousTransactionVO();
|
||||
item.setTrxDate("2024-01-15 10:00:00");
|
||||
item.setSuspiciousPersonName("孙七");
|
||||
item.setRelatedPersonName("孙七");
|
||||
item.setLeAccountNo("6222000000000000");
|
||||
item.setLeAccountName("孙七");
|
||||
item.setCustomerAccountName("测试对手方");
|
||||
item.setCustomerAccountNo("6222000000000001");
|
||||
item.setRelatedStaffName("孙七");
|
||||
item.setRelatedStaffCode("809901");
|
||||
item.setRelationType("本人");
|
||||
item.setUserMemo("");
|
||||
item.setCashType("转账");
|
||||
item.setHitTags("大额交易");
|
||||
item.setDisplayAmount(new BigDecimal("500000.00"));
|
||||
when(overviewMapper.selectSuspiciousTransactionList(any(CcdiProjectSuspiciousTransactionQueryDTO.class)))
|
||||
when(overviewMapper.selectReportSuspiciousTransactionList(any(CcdiProjectSuspiciousTransactionQueryDTO.class)))
|
||||
.thenReturn(List.of(item));
|
||||
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||
@@ -117,8 +121,14 @@ class CcdiProjectOverviewServiceSuspiciousTransactionTest {
|
||||
List<CcdiProjectSuspiciousTransactionExcel> rows = service.exportSuspiciousTransactions(queryDTO);
|
||||
|
||||
assertEquals(1, rows.size());
|
||||
assertEquals("6222000000000000", rows.getFirst().getLeAccountNo());
|
||||
assertEquals("孙七", rows.getFirst().getLeAccountName());
|
||||
assertEquals("测试对手方", rows.getFirst().getCustomerAccountName());
|
||||
assertEquals("6222000000000001", rows.getFirst().getCustomerAccountNo());
|
||||
assertEquals("孙七(809901)", rows.getFirst().getRelatedStaffDisplay());
|
||||
assertEquals("/转账", rows.getFirst().getSummaryAndCashType());
|
||||
assertEquals("", rows.getFirst().getUserMemo());
|
||||
assertEquals("转账", rows.getFirst().getCashType());
|
||||
assertEquals("大额交易", rows.getFirst().getHitTags());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -23,11 +23,14 @@ class CcdiProjectRiskDetailWorkbookExporterTest {
|
||||
|
||||
CcdiProjectSuspiciousTransactionExcel suspiciousRow = new CcdiProjectSuspiciousTransactionExcel();
|
||||
suspiciousRow.setTrxDate("2026-03-20 10:00:00");
|
||||
suspiciousRow.setSuspiciousPersonName("张三");
|
||||
suspiciousRow.setRelatedPersonName("张三");
|
||||
suspiciousRow.setLeAccountNo("6222000000000000");
|
||||
suspiciousRow.setLeAccountName("张三");
|
||||
suspiciousRow.setCustomerAccountName("测试对手方");
|
||||
suspiciousRow.setCustomerAccountNo("6222000000000001");
|
||||
suspiciousRow.setRelatedStaffDisplay("张三(1001)");
|
||||
suspiciousRow.setRelationType("本人");
|
||||
suspiciousRow.setSummaryAndCashType("转账/转账");
|
||||
suspiciousRow.setUserMemo("转账");
|
||||
suspiciousRow.setCashType("转账");
|
||||
suspiciousRow.setHitTags("异常标签");
|
||||
suspiciousRow.setDisplayAmount(new BigDecimal("100.00"));
|
||||
|
||||
CcdiProjectEmployeeCreditNegativeExcel creditRow = new CcdiProjectEmployeeCreditNegativeExcel();
|
||||
@@ -56,6 +59,19 @@ class CcdiProjectRiskDetailWorkbookExporterTest {
|
||||
assertEquals("涉疑交易明细", workbook.getSheetAt(0).getSheetName());
|
||||
assertEquals("员工负面征信信息", workbook.getSheetAt(1).getSheetName());
|
||||
assertEquals("异常账户人员信息", workbook.getSheetAt(2).getSheetName());
|
||||
assertEquals("交易时间", workbook.getSheetAt(0).getRow(0).getCell(0).getStringCellValue());
|
||||
assertEquals("本方账户", workbook.getSheetAt(0).getRow(0).getCell(1).getStringCellValue());
|
||||
assertEquals("本方主体", workbook.getSheetAt(0).getRow(0).getCell(2).getStringCellValue());
|
||||
assertEquals("对方名称", workbook.getSheetAt(0).getRow(0).getCell(3).getStringCellValue());
|
||||
assertEquals("对方账户", workbook.getSheetAt(0).getRow(0).getCell(4).getStringCellValue());
|
||||
assertEquals("关联员工", workbook.getSheetAt(0).getRow(0).getCell(5).getStringCellValue());
|
||||
assertEquals("摘要", workbook.getSheetAt(0).getRow(0).getCell(6).getStringCellValue());
|
||||
assertEquals("交易类型", workbook.getSheetAt(0).getRow(0).getCell(7).getStringCellValue());
|
||||
assertEquals("异常标签", workbook.getSheetAt(0).getRow(0).getCell(8).getStringCellValue());
|
||||
assertEquals("交易金额", workbook.getSheetAt(0).getRow(0).getCell(9).getStringCellValue());
|
||||
assertEquals("测试对手方", workbook.getSheetAt(0).getRow(1).getCell(3).getStringCellValue());
|
||||
assertEquals("6222000000000001", workbook.getSheetAt(0).getRow(1).getCell(4).getStringCellValue());
|
||||
assertEquals("异常标签", workbook.getSheetAt(0).getRow(1).getCell(8).getStringCellValue());
|
||||
assertEquals("账号", workbook.getSheetAt(2).getRow(0).getCell(0).getStringCellValue());
|
||||
assertEquals("开户人", workbook.getSheetAt(2).getRow(0).getCell(1).getStringCellValue());
|
||||
assertEquals("银行", workbook.getSheetAt(2).getRow(0).getCell(2).getStringCellValue());
|
||||
|
||||
Reference in New Issue
Block a user