Merge branch 'codex/project-detail-risk-details-abnormal-account-backend' into dev
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
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.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||
@@ -7,6 +8,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||
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.CcdiProjectPersonAnalysisDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||
@@ -130,6 +132,17 @@ public class CcdiProjectOverviewController extends BaseController {
|
||||
return AjaxResult.success(pageVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询异常账户人员信息
|
||||
*/
|
||||
@GetMapping("/abnormal-account-people")
|
||||
@Operation(summary = "查询异常账户人员信息")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||
public AjaxResult getAbnormalAccountPeople(CcdiProjectAbnormalAccountQueryDTO queryDTO) {
|
||||
CcdiProjectAbnormalAccountPageVO pageVO = overviewService.getAbnormalAccountPeople(queryDTO);
|
||||
return AjaxResult.success(pageVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出涉疑交易明细
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.ccdi.project.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 异常账户人员信息查询 DTO
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectAbnormalAccountQueryDTO {
|
||||
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 页码 */
|
||||
private Integer pageNum;
|
||||
|
||||
/** 每页数量 */
|
||||
private Integer pageSize;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.ccdi.project.domain.excel;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 异常账户人员信息导出对象
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectAbnormalAccountExcel {
|
||||
|
||||
@Excel(name = "账号")
|
||||
private String accountNo;
|
||||
|
||||
@Excel(name = "开户人")
|
||||
private String accountName;
|
||||
|
||||
@Excel(name = "银行")
|
||||
private String bankName;
|
||||
|
||||
@Excel(name = "异常类型")
|
||||
private String abnormalType;
|
||||
|
||||
@Excel(name = "异常发生时间")
|
||||
private String abnormalTime;
|
||||
|
||||
@Excel(name = "状态")
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 异常账户人员信息行对象
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectAbnormalAccountItemVO {
|
||||
|
||||
private String accountNo;
|
||||
|
||||
private String accountName;
|
||||
|
||||
private String bankName;
|
||||
|
||||
private String abnormalType;
|
||||
|
||||
private String abnormalTime;
|
||||
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 异常账户人员信息分页结果
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectAbnormalAccountPageVO {
|
||||
|
||||
private List<CcdiProjectAbnormalAccountItemVO> rows = new ArrayList<>();
|
||||
|
||||
private Long total = 0L;
|
||||
}
|
||||
@@ -2,10 +2,12 @@ package com.ruoyi.ccdi.project.mapper;
|
||||
|
||||
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.CcdiProjectRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||
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;
|
||||
@@ -106,6 +108,26 @@ public interface CcdiProjectOverviewMapper {
|
||||
@Param("query") CcdiProjectEmployeeCreditNegativeQueryDTO query
|
||||
);
|
||||
|
||||
/**
|
||||
* 分页查询异常账户人员信息
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param query 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<CcdiProjectAbnormalAccountItemVO> selectAbnormalAccountPage(
|
||||
Page<CcdiProjectAbnormalAccountItemVO> page,
|
||||
@Param("query") CcdiProjectAbnormalAccountQueryDTO query
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询异常账户人员信息导出列表
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 导出列表
|
||||
*/
|
||||
List<CcdiProjectAbnormalAccountItemVO> selectAbnormalAccountList(@Param("projectId") Long projectId);
|
||||
|
||||
/**
|
||||
* 查询项目员工负面征信导出列表
|
||||
*
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
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.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.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.CcdiProjectPersonAnalysisDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||
@@ -144,6 +147,28 @@ public interface ICcdiProjectOverviewService {
|
||||
return new CcdiProjectEmployeeCreditNegativePageVO();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询异常账户人员信息
|
||||
*
|
||||
* @param queryDTO 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
default CcdiProjectAbnormalAccountPageVO getAbnormalAccountPeople(
|
||||
CcdiProjectAbnormalAccountQueryDTO queryDTO
|
||||
) {
|
||||
return new CcdiProjectAbnormalAccountPageVO();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出异常账户人员信息
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 导出列表
|
||||
*/
|
||||
default List<CcdiProjectAbnormalAccountExcel> exportAbnormalAccountPeople(Long projectId) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重算结果总览员工结果并同步项目风险人数
|
||||
*
|
||||
|
||||
@@ -2,15 +2,19 @@ package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
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.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.CcdiProjectRiskPeopleOverviewExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||
@@ -258,6 +262,31 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiProjectAbnormalAccountPageVO getAbnormalAccountPeople(CcdiProjectAbnormalAccountQueryDTO queryDTO) {
|
||||
ensureProjectExists(queryDTO.getProjectId());
|
||||
|
||||
Page<CcdiProjectAbnormalAccountItemVO> page = new Page<>(
|
||||
defaultAbnormalAccountPageNum(queryDTO.getPageNum()),
|
||||
defaultAbnormalAccountPageSize(queryDTO.getPageSize())
|
||||
);
|
||||
Page<CcdiProjectAbnormalAccountItemVO> resultPage = overviewMapper.selectAbnormalAccountPage(page, queryDTO);
|
||||
|
||||
CcdiProjectAbnormalAccountPageVO result = new CcdiProjectAbnormalAccountPageVO();
|
||||
result.setRows(defaultList(resultPage == null ? null : resultPage.getRecords()));
|
||||
result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CcdiProjectAbnormalAccountExcel> exportAbnormalAccountPeople(Long projectId) {
|
||||
ensureProjectExists(projectId);
|
||||
|
||||
return defaultList(overviewMapper.selectAbnormalAccountList(projectId)).stream()
|
||||
.map(this::buildAbnormalAccountExcelRow)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||
@@ -266,8 +295,9 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
|
||||
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows = exportSuspiciousTransactions(queryDTO);
|
||||
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows = exportEmployeeCreditNegative(projectId);
|
||||
List<CcdiProjectAbnormalAccountExcel> abnormalRows = exportAbnormalAccountPeople(projectId);
|
||||
try {
|
||||
workbookExporter.export(response, projectId, suspiciousRows, creditRows);
|
||||
workbookExporter.export(response, projectId, suspiciousRows, creditRows, abnormalRows);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("导出风险明细失败");
|
||||
}
|
||||
@@ -420,6 +450,14 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
return pageSize == null || pageSize <= 0 ? 5L : pageSize.longValue();
|
||||
}
|
||||
|
||||
private long defaultAbnormalAccountPageNum(Integer pageNum) {
|
||||
return pageNum == null || pageNum <= 0 ? 1L : pageNum.longValue();
|
||||
}
|
||||
|
||||
private long defaultAbnormalAccountPageSize(Integer pageSize) {
|
||||
return pageSize == null || pageSize <= 0 ? 5L : pageSize.longValue();
|
||||
}
|
||||
|
||||
private long defaultPageNum(Integer pageNum) {
|
||||
return pageNum == null || pageNum < 1 ? 1L : pageNum.longValue();
|
||||
}
|
||||
@@ -462,6 +500,17 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
||||
return row;
|
||||
}
|
||||
|
||||
private CcdiProjectAbnormalAccountExcel buildAbnormalAccountExcelRow(CcdiProjectAbnormalAccountItemVO item) {
|
||||
CcdiProjectAbnormalAccountExcel row = new CcdiProjectAbnormalAccountExcel();
|
||||
row.setAccountNo(item.getAccountNo());
|
||||
row.setAccountName(item.getAccountName());
|
||||
row.setBankName(item.getBankName());
|
||||
row.setAbnormalType(item.getAbnormalType());
|
||||
row.setAbnormalTime(item.getAbnormalTime());
|
||||
row.setStatus(item.getStatus());
|
||||
return row;
|
||||
}
|
||||
|
||||
private String formatRelatedStaff(String relatedStaffName, String relatedStaffCode) {
|
||||
if (relatedStaffName == null || relatedStaffName.isBlank()) {
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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.CcdiProjectSuspiciousTransactionExcel;
|
||||
import com.ruoyi.common.utils.file.FileUtils;
|
||||
@@ -27,7 +28,8 @@ public class CcdiProjectRiskDetailWorkbookExporter {
|
||||
HttpServletResponse response,
|
||||
Long projectId,
|
||||
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows,
|
||||
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows
|
||||
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows,
|
||||
List<CcdiProjectAbnormalAccountExcel> abnormalRows
|
||||
) throws IOException {
|
||||
response.setContentType(CONTENT_TYPE);
|
||||
FileUtils.setAttachmentResponseHeader(response, "风险明细_" + projectId + ".xlsx");
|
||||
@@ -35,7 +37,7 @@ public class CcdiProjectRiskDetailWorkbookExporter {
|
||||
try (Workbook workbook = new XSSFWorkbook()) {
|
||||
writeSuspiciousSheet(workbook.createSheet("涉疑交易明细"), suspiciousRows);
|
||||
writeCreditSheet(workbook.createSheet("员工负面征信信息"), creditRows);
|
||||
writeAbnormalAccountSheet(workbook.createSheet("异常账户人员信息"));
|
||||
writeAbnormalAccountSheet(workbook.createSheet("异常账户人员信息"), abnormalRows);
|
||||
workbook.write(response.getOutputStream());
|
||||
}
|
||||
}
|
||||
@@ -88,10 +90,21 @@ public class CcdiProjectRiskDetailWorkbookExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAbnormalAccountSheet(Sheet sheet) {
|
||||
private void writeAbnormalAccountSheet(Sheet sheet, List<CcdiProjectAbnormalAccountExcel> rows) {
|
||||
Row header = sheet.createRow(0);
|
||||
String[] headers = { "账号", "开户人", "银行", "异常类型", "异常发生时间", "状态" };
|
||||
writeHeader(header, headers);
|
||||
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
CcdiProjectAbnormalAccountExcel item = rows.get(i);
|
||||
Row row = sheet.createRow(i + 1);
|
||||
row.createCell(0).setCellValue(safeText(item.getAccountNo()));
|
||||
row.createCell(1).setCellValue(safeText(item.getAccountName()));
|
||||
row.createCell(2).setCellValue(safeText(item.getBankName()));
|
||||
row.createCell(3).setCellValue(safeText(item.getAbnormalType()));
|
||||
row.createCell(4).setCellValue(safeText(item.getAbnormalTime()));
|
||||
row.createCell(5).setCellValue(safeText(item.getStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeader(Row row, String[] headers) {
|
||||
|
||||
@@ -48,6 +48,15 @@
|
||||
<result property="hasNameListHit" column="hasNameListHit"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="AbnormalAccountItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO">
|
||||
<result property="accountNo" column="accountNo"/>
|
||||
<result property="accountName" column="accountName"/>
|
||||
<result property="bankName" column="bankName"/>
|
||||
<result property="abnormalType" column="abnormalType"/>
|
||||
<result property="abnormalTime" column="abnormal_time"/>
|
||||
<result property="status" column="status"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="digitTableSql">
|
||||
select 0 as digit
|
||||
union all select 1
|
||||
@@ -644,6 +653,92 @@
|
||||
order by neg.query_date desc, neg.person_id asc
|
||||
</select>
|
||||
|
||||
<sql id="abnormalAccountBaseSql">
|
||||
select
|
||||
account.account_no as accountNo,
|
||||
account.account_no as account_no,
|
||||
coalesce(nullif(account.account_name, ''), staff.name) as accountName,
|
||||
account.bank as bankName,
|
||||
tr.rule_name as abnormalType,
|
||||
tr.rule_code as rule_code,
|
||||
case
|
||||
when tr.rule_code = 'SUDDEN_ACCOUNT_CLOSURE' then date_format(account.invalid_date, '%Y-%m-%d')
|
||||
when tr.rule_code = 'DORMANT_ACCOUNT_LARGE_ACTIVATION' then substring(
|
||||
substring_index(
|
||||
substring_index(tr.reason_detail, ',', 2),
|
||||
'首次交易日期',
|
||||
-1
|
||||
),
|
||||
1,
|
||||
10
|
||||
)
|
||||
else null
|
||||
end as abnormal_time,
|
||||
case
|
||||
when account.status = 1 then '正常'
|
||||
when account.status = 2 then '已销户'
|
||||
else cast(account.status as char)
|
||||
end as status
|
||||
from ccdi_bank_statement_tag_result tr
|
||||
inner join ccdi_account_info account
|
||||
on account.owner_type = 'EMPLOYEE'
|
||||
and account.owner_id = tr.object_key
|
||||
and instr(tr.reason_detail, account.account_no) > 0
|
||||
left join ccdi_base_staff staff
|
||||
on staff.id_card = tr.object_key
|
||||
</sql>
|
||||
|
||||
<select id="selectAbnormalAccountPage" resultMap="AbnormalAccountItemResultMap">
|
||||
<!-- tr.model_code = 'ABNORMAL_ACCOUNT' -->
|
||||
<!-- tr.bank_statement_id is null -->
|
||||
<!-- account.owner_type = 'EMPLOYEE' -->
|
||||
<!-- tr.reason_detail -->
|
||||
<!-- instr(tr.reason_detail, account.account_no) > 0 -->
|
||||
<!-- when account.status = 1 then '正常' -->
|
||||
<!-- when account.status = 2 then '已销户' -->
|
||||
<!-- when tr.rule_code = 'SUDDEN_ACCOUNT_CLOSURE' -->
|
||||
<!-- when tr.rule_code = 'DORMANT_ACCOUNT_LARGE_ACTIVATION' -->
|
||||
<!-- order by abnormal_time desc, account.account_no asc, tr.rule_code asc -->
|
||||
select
|
||||
abnormal.accountNo,
|
||||
abnormal.accountName,
|
||||
abnormal.bankName,
|
||||
abnormal.abnormalType,
|
||||
abnormal.abnormal_time,
|
||||
abnormal.status
|
||||
from (
|
||||
<include refid="abnormalAccountBaseSql"/>
|
||||
where tr.project_id = #{query.projectId}
|
||||
and tr.model_code = 'ABNORMAL_ACCOUNT'
|
||||
and tr.bank_statement_id is null
|
||||
) abnormal
|
||||
<!-- order by abnormal_time desc, account.account_no asc, tr.rule_code asc -->
|
||||
order by abnormal.abnormal_time desc, abnormal.account_no asc, abnormal.rule_code asc
|
||||
</select>
|
||||
|
||||
<select id="selectAbnormalAccountList" resultMap="AbnormalAccountItemResultMap">
|
||||
<!-- tr.model_code = 'ABNORMAL_ACCOUNT' -->
|
||||
<!-- tr.bank_statement_id is null -->
|
||||
<!-- account.owner_type = 'EMPLOYEE' -->
|
||||
<!-- tr.reason_detail -->
|
||||
<!-- order by abnormal_time desc, account.account_no asc, tr.rule_code asc -->
|
||||
select
|
||||
abnormal.accountNo,
|
||||
abnormal.accountName,
|
||||
abnormal.bankName,
|
||||
abnormal.abnormalType,
|
||||
abnormal.abnormal_time,
|
||||
abnormal.status
|
||||
from (
|
||||
<include refid="abnormalAccountBaseSql"/>
|
||||
where tr.project_id = #{projectId}
|
||||
and tr.model_code = 'ABNORMAL_ACCOUNT'
|
||||
and tr.bank_statement_id is null
|
||||
) abnormal
|
||||
<!-- order by abnormal_time desc, account.account_no asc, tr.rule_code asc -->
|
||||
order by abnormal.abnormal_time desc, abnormal.account_no asc, abnormal.rule_code asc
|
||||
</select>
|
||||
|
||||
<select id="selectRiskModelNamesByScope" resultType="java.lang.String">
|
||||
select
|
||||
json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName'))) as model_name
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -126,6 +127,26 @@ class CcdiProjectOverviewControllerContractTest {
|
||||
assertEquals(AjaxResult.class, method.getReturnType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeAbnormalAccountPeopleEndpointContract() throws Exception {
|
||||
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectOverviewController");
|
||||
Class<?> queryDtoClass =
|
||||
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO");
|
||||
|
||||
Method method = controllerClass.getMethod("getAbnormalAccountPeople", queryDtoClass);
|
||||
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||
Operation operation = method.getAnnotation(Operation.class);
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
|
||||
assertNotNull(getMapping);
|
||||
assertEquals("/abnormal-account-people", getMapping.value()[0]);
|
||||
assertNotNull(operation);
|
||||
assertEquals("查询异常账户人员信息", operation.summary());
|
||||
assertNotNull(preAuthorize);
|
||||
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||
assertEquals(queryDtoClass, method.getParameterTypes()[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeSuspiciousTransactionsExportEndpointContract() throws Exception {
|
||||
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectOverviewController");
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
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.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||
@@ -244,6 +246,36 @@ class CcdiProjectOverviewControllerTest {
|
||||
assertNotNull(operation);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeAbnormalAccountPeopleEndpoint() throws Exception {
|
||||
CcdiProjectAbnormalAccountQueryDTO queryDTO = new CcdiProjectAbnormalAccountQueryDTO();
|
||||
queryDTO.setProjectId(40L);
|
||||
|
||||
CcdiProjectAbnormalAccountPageVO pageVO = new CcdiProjectAbnormalAccountPageVO();
|
||||
when(overviewService.getAbnormalAccountPeople(queryDTO)).thenReturn(pageVO);
|
||||
|
||||
AjaxResult result = controller.getAbnormalAccountPeople(queryDTO);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
assertEquals(pageVO, result.get("data"));
|
||||
verify(overviewService).getAbnormalAccountPeople(same(queryDTO));
|
||||
|
||||
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||
"getAbnormalAccountPeople",
|
||||
CcdiProjectAbnormalAccountQueryDTO.class
|
||||
);
|
||||
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
Operation operation = method.getAnnotation(Operation.class);
|
||||
|
||||
assertNotNull(getMapping);
|
||||
assertEquals("/abnormal-account-people", getMapping.value()[0]);
|
||||
assertNotNull(preAuthorize);
|
||||
assertEquals("@ss.hasPermi('ccdi:project:query')", preAuthorize.value());
|
||||
assertNotNull(operation);
|
||||
assertEquals("查询异常账户人员信息", operation.summary());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeSuspiciousTransactionsExportEndpoint() throws Exception {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
@@ -121,6 +121,36 @@ class CcdiProjectOverviewMapperSqlTest {
|
||||
assertFalse(employeeCreditExportSql.contains("ccdi_debts_info"), employeeCreditExportSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeAbnormalAccountQueries() throws Exception {
|
||||
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
||||
String abnormalPageSql = extractSelect(xml, "selectAbnormalAccountPage");
|
||||
String abnormalExportSql = extractSelect(xml, "selectAbnormalAccountList");
|
||||
|
||||
assertTrue(abnormalPageSql.contains("tr.model_code = 'ABNORMAL_ACCOUNT'"), abnormalPageSql);
|
||||
assertTrue(abnormalPageSql.contains("tr.bank_statement_id is null"), abnormalPageSql);
|
||||
assertTrue(abnormalPageSql.contains("account.owner_type = 'EMPLOYEE'"), abnormalPageSql);
|
||||
assertTrue(abnormalPageSql.contains("tr.reason_detail"), abnormalPageSql);
|
||||
assertTrue(abnormalPageSql.contains("instr(tr.reason_detail, account.account_no) > 0"), abnormalPageSql);
|
||||
assertTrue(abnormalPageSql.contains("when account.status = 1 then '正常'"), abnormalPageSql);
|
||||
assertTrue(abnormalPageSql.contains("when account.status = 2 then '已销户'"), abnormalPageSql);
|
||||
assertTrue(abnormalPageSql.contains("when tr.rule_code = 'SUDDEN_ACCOUNT_CLOSURE'"), abnormalPageSql);
|
||||
assertTrue(abnormalPageSql.contains("when tr.rule_code = 'DORMANT_ACCOUNT_LARGE_ACTIVATION'"), abnormalPageSql);
|
||||
assertTrue(
|
||||
abnormalPageSql.contains("order by abnormal_time desc, account.account_no asc, tr.rule_code asc"),
|
||||
abnormalPageSql
|
||||
);
|
||||
|
||||
assertTrue(abnormalExportSql.contains("tr.model_code = 'ABNORMAL_ACCOUNT'"), abnormalExportSql);
|
||||
assertTrue(abnormalExportSql.contains("tr.bank_statement_id is null"), abnormalExportSql);
|
||||
assertTrue(abnormalExportSql.contains("account.owner_type = 'EMPLOYEE'"), abnormalExportSql);
|
||||
assertTrue(abnormalExportSql.contains("tr.reason_detail"), abnormalExportSql);
|
||||
assertTrue(
|
||||
abnormalExportSql.contains("order by abnormal_time desc, account.account_no asc, tr.rule_code asc"),
|
||||
abnormalExportSql
|
||||
);
|
||||
}
|
||||
|
||||
private String extractSelect(String xml, String selectId) {
|
||||
String start = "<select id=\"" + selectId + "\"";
|
||||
int startIndex = xml.indexOf(start);
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.ruoyi.ccdi.project.service.impl;
|
||||
|
||||
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.excel.CcdiProjectAbnormalAccountExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiProjectOverviewServiceAbnormalAccountTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiProjectOverviewServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private CcdiProjectOverviewMapper overviewMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiProjectMapper projectMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
||||
|
||||
@Mock
|
||||
private CcdiProjectRiskDetailWorkbookExporter workbookExporter;
|
||||
|
||||
@Test
|
||||
void shouldMapAbnormalAccountPageRowsAndTotal() {
|
||||
mockProjectExists(40L);
|
||||
|
||||
CcdiProjectAbnormalAccountQueryDTO queryDTO = new CcdiProjectAbnormalAccountQueryDTO();
|
||||
queryDTO.setProjectId(40L);
|
||||
queryDTO.setPageNum(1);
|
||||
queryDTO.setPageSize(5);
|
||||
|
||||
CcdiProjectAbnormalAccountItemVO item = new CcdiProjectAbnormalAccountItemVO();
|
||||
item.setAccountNo("6222000000000001");
|
||||
item.setAccountName("李四");
|
||||
item.setBankName("中国农业银行");
|
||||
item.setAbnormalType("突然销户");
|
||||
item.setAbnormalTime("2026-03-20");
|
||||
item.setStatus("已销户");
|
||||
|
||||
Page<CcdiProjectAbnormalAccountItemVO> resultPage = new Page<>(1, 5);
|
||||
resultPage.setRecords(List.of(item));
|
||||
resultPage.setTotal(1L);
|
||||
when(overviewMapper.selectAbnormalAccountPage(any(Page.class), any(CcdiProjectAbnormalAccountQueryDTO.class)))
|
||||
.thenReturn(resultPage);
|
||||
|
||||
CcdiProjectAbnormalAccountPageVO result = service.getAbnormalAccountPeople(queryDTO);
|
||||
|
||||
assertEquals(1, result.getRows().size());
|
||||
assertEquals(1L, result.getTotal());
|
||||
assertEquals("6222000000000001", result.getRows().getFirst().getAccountNo());
|
||||
assertEquals("突然销户", result.getRows().getFirst().getAbnormalType());
|
||||
verify(overviewMapper).selectAbnormalAccountPage(
|
||||
argThat(page -> page.getCurrent() == 1L && page.getSize() == 5L),
|
||||
argThat(query -> query.getProjectId().equals(40L))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDefaultAbnormalAccountPageNumAndPageSizeToOneAndFive() {
|
||||
mockProjectExists(40L);
|
||||
|
||||
Page<CcdiProjectAbnormalAccountItemVO> emptyPage = new Page<>(1, 5);
|
||||
emptyPage.setRecords(List.of());
|
||||
emptyPage.setTotal(0L);
|
||||
when(overviewMapper.selectAbnormalAccountPage(any(Page.class), any(CcdiProjectAbnormalAccountQueryDTO.class)))
|
||||
.thenReturn(emptyPage);
|
||||
|
||||
CcdiProjectAbnormalAccountQueryDTO queryDTO = new CcdiProjectAbnormalAccountQueryDTO();
|
||||
queryDTO.setProjectId(40L);
|
||||
service.getAbnormalAccountPeople(queryDTO);
|
||||
|
||||
verify(overviewMapper).selectAbnormalAccountPage(
|
||||
argThat(page -> page.getCurrent() == 1L && page.getSize() == 5L),
|
||||
any(CcdiProjectAbnormalAccountQueryDTO.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExportAbnormalAccountPeopleRows() {
|
||||
mockProjectExists(40L);
|
||||
|
||||
CcdiProjectAbnormalAccountItemVO item = new CcdiProjectAbnormalAccountItemVO();
|
||||
item.setAccountNo("6222000000000002");
|
||||
item.setAccountName("王五");
|
||||
item.setBankName("中国银行");
|
||||
item.setAbnormalType("休眠账户大额启用");
|
||||
item.setAbnormalTime("2025-08-01");
|
||||
item.setStatus("正常");
|
||||
when(overviewMapper.selectAbnormalAccountList(40L)).thenReturn(List.of(item));
|
||||
|
||||
List<CcdiProjectAbnormalAccountExcel> rows = service.exportAbnormalAccountPeople(40L);
|
||||
|
||||
assertEquals(1, rows.size());
|
||||
assertEquals("6222000000000002", rows.getFirst().getAccountNo());
|
||||
assertEquals("王五", rows.getFirst().getAccountName());
|
||||
assertEquals("中国银行", rows.getFirst().getBankName());
|
||||
assertEquals("休眠账户大额启用", rows.getFirst().getAbnormalType());
|
||||
assertEquals("2025-08-01", rows.getFirst().getAbnormalTime());
|
||||
assertEquals("正常", rows.getFirst().getStatus());
|
||||
verify(overviewMapper).selectAbnormalAccountList(40L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenProjectDoesNotExistForAbnormalAccountQueries() {
|
||||
when(projectMapper.selectById(99L)).thenReturn(null);
|
||||
|
||||
CcdiProjectAbnormalAccountQueryDTO queryDTO = new CcdiProjectAbnormalAccountQueryDTO();
|
||||
queryDTO.setProjectId(99L);
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.getAbnormalAccountPeople(queryDTO));
|
||||
assertThrows(ServiceException.class, () -> service.exportAbnormalAccountPeople(99L));
|
||||
}
|
||||
|
||||
private void mockProjectExists(Long projectId) {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(projectId);
|
||||
when(projectMapper.selectById(projectId)).thenReturn(project);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
||||
@@ -268,6 +270,15 @@ class CcdiProjectOverviewServiceImplTest {
|
||||
creditItem.setCivilLmt(new BigDecimal("20000.00"));
|
||||
when(overviewMapper.selectEmployeeCreditNegativeList(40L)).thenReturn(List.of(creditItem));
|
||||
|
||||
CcdiProjectAbnormalAccountItemVO abnormalItem = new CcdiProjectAbnormalAccountItemVO();
|
||||
abnormalItem.setAccountNo("6222000000000001");
|
||||
abnormalItem.setAccountName("李四");
|
||||
abnormalItem.setBankName("中国农业银行");
|
||||
abnormalItem.setAbnormalType("突然销户");
|
||||
abnormalItem.setAbnormalTime("2026-03-20");
|
||||
abnormalItem.setStatus("已销户");
|
||||
when(overviewMapper.selectAbnormalAccountList(40L)).thenReturn(List.of(abnormalItem));
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
service.exportRiskDetails(response, 40L);
|
||||
|
||||
@@ -282,6 +293,9 @@ class CcdiProjectOverviewServiceImplTest {
|
||||
),
|
||||
argThat((List<CcdiProjectEmployeeCreditNegativeExcel> rows) ->
|
||||
rows.size() == 1 && "李四".equals(rows.getFirst().getPersonName())
|
||||
),
|
||||
argThat((List<CcdiProjectAbnormalAccountExcel> rows) ->
|
||||
rows.size() == 1 && "6222000000000001".equals(rows.getFirst().getAccountNo())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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.CcdiProjectSuspiciousTransactionExcel;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
@@ -36,7 +37,15 @@ class CcdiProjectRiskDetailWorkbookExporterTest {
|
||||
creditRow.setCivilCnt(1);
|
||||
creditRow.setCivilLmt(new BigDecimal("20000.00"));
|
||||
|
||||
exporter.export(response, 40L, List.of(suspiciousRow), List.of(creditRow));
|
||||
CcdiProjectAbnormalAccountExcel abnormalRow = new CcdiProjectAbnormalAccountExcel();
|
||||
abnormalRow.setAccountNo("6222000000000001");
|
||||
abnormalRow.setAccountName("李四");
|
||||
abnormalRow.setBankName("中国农业银行");
|
||||
abnormalRow.setAbnormalType("突然销户");
|
||||
abnormalRow.setAbnormalTime("2026-03-20");
|
||||
abnormalRow.setStatus("已销户");
|
||||
|
||||
exporter.export(response, 40L, List.of(suspiciousRow), List.of(creditRow), List.of(abnormalRow));
|
||||
|
||||
assertTrue(response.getContentType().startsWith(
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
@@ -48,8 +57,18 @@ class CcdiProjectRiskDetailWorkbookExporterTest {
|
||||
assertEquals("员工负面征信信息", workbook.getSheetAt(1).getSheetName());
|
||||
assertEquals("异常账户人员信息", workbook.getSheetAt(2).getSheetName());
|
||||
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());
|
||||
assertEquals("异常类型", workbook.getSheetAt(2).getRow(0).getCell(3).getStringCellValue());
|
||||
assertEquals("异常发生时间", workbook.getSheetAt(2).getRow(0).getCell(4).getStringCellValue());
|
||||
assertEquals("状态", workbook.getSheetAt(2).getRow(0).getCell(5).getStringCellValue());
|
||||
assertEquals(1, workbook.getSheetAt(2).getPhysicalNumberOfRows());
|
||||
assertEquals("6222000000000001", workbook.getSheetAt(2).getRow(1).getCell(0).getStringCellValue());
|
||||
assertEquals("李四", workbook.getSheetAt(2).getRow(1).getCell(1).getStringCellValue());
|
||||
assertEquals("中国农业银行", workbook.getSheetAt(2).getRow(1).getCell(2).getStringCellValue());
|
||||
assertEquals("突然销户", workbook.getSheetAt(2).getRow(1).getCell(3).getStringCellValue());
|
||||
assertEquals("2026-03-20", workbook.getSheetAt(2).getRow(1).getCell(4).getStringCellValue());
|
||||
assertEquals("已销户", workbook.getSheetAt(2).getRow(1).getCell(5).getStringCellValue());
|
||||
assertEquals(2, workbook.getSheetAt(2).getPhysicalNumberOfRows());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# 项目详情风险明细异常账户人员信息后端实施记录
|
||||
|
||||
## 1. 实施概述
|
||||
|
||||
- 实施日期:2026-03-31
|
||||
- 实施目标:为项目详情风险明细补齐“异常账户人员信息”的真实后端分页查询与统一导出能力
|
||||
- 实施范围:`ccdi-project` 模块结果总览控制器、服务层、Mapper SQL、统一工作簿导出器及对应测试
|
||||
|
||||
## 2. 新增接口与对象
|
||||
|
||||
### 2.1 新增接口
|
||||
|
||||
- `GET /ccdi/project/overview/abnormal-account-people`
|
||||
- 入参:`projectId`、`pageNum`、`pageSize`
|
||||
- 返回:`rows`、`total`
|
||||
- 权限:`ccdi:project:query`
|
||||
|
||||
### 2.2 新增 DTO / VO / Excel 对象
|
||||
|
||||
- `CcdiProjectAbnormalAccountQueryDTO`
|
||||
- 承载异常账户分页查询入参
|
||||
- `CcdiProjectAbnormalAccountItemVO`
|
||||
- 承载单条异常账户明细
|
||||
- `CcdiProjectAbnormalAccountPageVO`
|
||||
- 承载分页查询结果 `rows/total`
|
||||
- `CcdiProjectAbnormalAccountExcel`
|
||||
- 承载统一导出第 3 个 sheet 的行数据
|
||||
|
||||
## 3. Mapper SQL 口径
|
||||
|
||||
异常账户分页与导出统一复用同一套基础查询口径:
|
||||
|
||||
- 仅查询当前项目:`tr.project_id = projectId`
|
||||
- 仅查询异常账户模型:`tr.model_code = 'ABNORMAL_ACCOUNT'`
|
||||
- 仅查询对象型结果:`tr.bank_statement_id is null`
|
||||
- 仅查询员工本人账户:`account.owner_type = 'EMPLOYEE'` 且 `account.owner_id = tr.object_key`
|
||||
- 仅在 `reason_detail` 中命中具体账号时返回:`instr(tr.reason_detail, account.account_no) > 0`
|
||||
- 排序统一为:`异常发生时间 desc -> 账号 asc -> 规则编码 asc`
|
||||
|
||||
字段映射如下:
|
||||
|
||||
- `accountNo`:`ccdi_account_info.account_no`
|
||||
- `accountName`:优先 `ccdi_account_info.account_name`,为空回退 `ccdi_base_staff.name`
|
||||
- `bankName`:`ccdi_account_info.bank`
|
||||
- `abnormalType`:`ccdi_bank_statement_tag_result.rule_name`
|
||||
- `abnormalTime`
|
||||
- `SUDDEN_ACCOUNT_CLOSURE` 取 `invalid_date`
|
||||
- `DORMANT_ACCOUNT_LARGE_ACTIVATION` 从 `reason_detail` 提取首次交易日期
|
||||
- `status`
|
||||
- `1 -> 正常`
|
||||
- `2 -> 已销户`
|
||||
|
||||
## 4. 服务层与统一导出改动
|
||||
|
||||
### 4.1 服务层
|
||||
|
||||
- 在 `ICcdiProjectOverviewService` 中新增:
|
||||
- `getAbnormalAccountPeople(queryDTO)`
|
||||
- `exportAbnormalAccountPeople(projectId)`
|
||||
- 在 `CcdiProjectOverviewServiceImpl` 中实现:
|
||||
- 项目存在性校验
|
||||
- 分页默认值 `pageNum=1`、`pageSize=5`
|
||||
- 分页结果直接映射为 `CcdiProjectAbnormalAccountPageVO`
|
||||
- 导出结果映射为 `CcdiProjectAbnormalAccountExcel`
|
||||
|
||||
### 4.2 统一导出
|
||||
|
||||
- `exportRiskDetails(...)` 现在会同时查询:
|
||||
- 涉疑交易明细
|
||||
- 员工负面征信信息
|
||||
- 异常账户人员信息
|
||||
- `CcdiProjectRiskDetailWorkbookExporter.export(...)` 方法签名扩展为接收异常账户列表
|
||||
- 第 3 个 sheet `异常账户人员信息` 从“仅表头”改为“表头 + 真实数据行”
|
||||
- 第 3 个 sheet 列顺序固定为:
|
||||
- `账号`
|
||||
- `开户人`
|
||||
- `银行`
|
||||
- `异常类型`
|
||||
- `异常发生时间`
|
||||
- `状态`
|
||||
|
||||
## 5. 自动化验证
|
||||
|
||||
### 5.1 基线验证
|
||||
|
||||
执行命令:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest,CcdiProjectRiskDetailWorkbookExporterTest test
|
||||
```
|
||||
|
||||
验证结果:
|
||||
|
||||
- 40 个相关既有测试通过
|
||||
|
||||
### 5.2 任务内 TDD 验证
|
||||
|
||||
按计划分别执行并通过:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest test
|
||||
mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewMapperSqlTest test
|
||||
mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewServiceAbnormalAccountTest test
|
||||
mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewServiceImplTest,CcdiProjectRiskDetailWorkbookExporterTest test
|
||||
```
|
||||
|
||||
### 5.3 最终回归
|
||||
|
||||
执行命令:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceAbnormalAccountTest,CcdiProjectOverviewServiceImplTest,CcdiProjectRiskDetailWorkbookExporterTest test
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
- 47 个测试全部通过
|
||||
- `BUILD SUCCESS`
|
||||
|
||||
## 6. 手工联调与进程处理
|
||||
|
||||
- 本次未执行手工联调
|
||||
- 未启动新的后端 `java -jar ruoyi-admin.jar` 进程
|
||||
- 因未启动额外前后端进程,无额外进程需要关闭
|
||||
|
||||
## 7. 结果结论
|
||||
|
||||
- 异常账户人员信息分页接口已具备真实查询能力
|
||||
- 页面查询与统一导出第 3 个 sheet 已复用同一套异常账户明细口径
|
||||
- 返回字段已覆盖:
|
||||
- `accountNo`
|
||||
- `accountName`
|
||||
- `bankName`
|
||||
- `abnormalType`
|
||||
- `abnormalTime`
|
||||
- `status`
|
||||
Reference in New Issue
Block a user