完成风险明细统一导出功能
This commit is contained in:
@@ -144,4 +144,14 @@ public class CcdiProjectOverviewController extends BaseController {
|
|||||||
new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class);
|
new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class);
|
||||||
util.exportExcel(response, rows, "涉疑交易明细");
|
util.exportExcel(response, rows, "涉疑交易明细");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出风险明细
|
||||||
|
*/
|
||||||
|
@PostMapping("/risk-details/export")
|
||||||
|
@Operation(summary = "导出风险明细")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||||
|
overviewService.exportRiskDetails(response, projectId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.excel;
|
||||||
|
|
||||||
|
import com.ruoyi.common.annotation.Excel;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目员工负面征信导出对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiProjectEmployeeCreditNegativeExcel {
|
||||||
|
|
||||||
|
@Excel(name = "员工姓名")
|
||||||
|
private String personName;
|
||||||
|
|
||||||
|
@Excel(name = "身份证号")
|
||||||
|
private String personId;
|
||||||
|
|
||||||
|
@Excel(name = "最近征信查询日期")
|
||||||
|
private String queryDate;
|
||||||
|
|
||||||
|
@Excel(name = "民事案件笔数")
|
||||||
|
private Integer civilCnt;
|
||||||
|
|
||||||
|
@Excel(name = "民事案件金额")
|
||||||
|
private BigDecimal civilLmt;
|
||||||
|
|
||||||
|
@Excel(name = "强制执行笔数")
|
||||||
|
private Integer enforceCnt;
|
||||||
|
|
||||||
|
@Excel(name = "强制执行金额")
|
||||||
|
private BigDecimal enforceLmt;
|
||||||
|
|
||||||
|
@Excel(name = "行政处罚笔数")
|
||||||
|
private Integer admCnt;
|
||||||
|
|
||||||
|
@Excel(name = "行政处罚金额")
|
||||||
|
private BigDecimal admLmt;
|
||||||
|
}
|
||||||
@@ -98,6 +98,14 @@ public interface CcdiProjectOverviewMapper {
|
|||||||
@Param("query") CcdiProjectEmployeeCreditNegativeQueryDTO query
|
@Param("query") CcdiProjectEmployeeCreditNegativeQueryDTO query
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目员工负面征信导出列表
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 导出列表
|
||||||
|
*/
|
||||||
|
List<CcdiProjectEmployeeCreditNegativeItemVO> selectEmployeeCreditNegativeList(@Param("projectId") Long projectId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询涉疑交易导出列表
|
* 查询涉疑交易导出列表
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO
|
|||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
|
||||||
@@ -14,6 +15,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
|||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -100,6 +102,25 @@ public interface ICcdiProjectOverviewService {
|
|||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一导出风险明细
|
||||||
|
*
|
||||||
|
* @param response 响应流
|
||||||
|
* @param projectId 项目ID
|
||||||
|
*/
|
||||||
|
default void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出项目员工负面征信
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 导出列表
|
||||||
|
*/
|
||||||
|
default List<CcdiProjectEmployeeCreditNegativeExcel> exportEmployeeCreditNegative(Long projectId) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询项目员工负面征信
|
* 查询项目员工负面征信
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO
|
|||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||||
@@ -37,7 +38,9 @@ import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
|
|||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewMapper;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -70,6 +73,9 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
@Resource
|
@Resource
|
||||||
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiProjectRiskDetailWorkbookExporter workbookExporter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CcdiProjectOverviewDashboardVO getDashboard(Long projectId) {
|
public CcdiProjectOverviewDashboardVO getDashboard(Long projectId) {
|
||||||
CcdiProject project = overviewMapper.selectDashboardBaseByProjectId(projectId);
|
CcdiProject project = overviewMapper.selectDashboardBaseByProjectId(projectId);
|
||||||
@@ -241,6 +247,30 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||||
|
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
|
||||||
|
queryDTO.setProjectId(projectId);
|
||||||
|
queryDTO.setSuspiciousType("ALL");
|
||||||
|
|
||||||
|
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows = exportSuspiciousTransactions(queryDTO);
|
||||||
|
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows = exportEmployeeCreditNegative(projectId);
|
||||||
|
try {
|
||||||
|
workbookExporter.export(response, projectId, suspiciousRows, creditRows);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ServiceException("导出风险明细失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CcdiProjectEmployeeCreditNegativeExcel> exportEmployeeCreditNegative(Long projectId) {
|
||||||
|
ensureProjectExists(projectId);
|
||||||
|
|
||||||
|
return defaultList(overviewMapper.selectEmployeeCreditNegativeList(projectId)).stream()
|
||||||
|
.map(this::buildEmployeeCreditNegativeExcelRow)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void refreshOverviewEmployeeResults(Long projectId, String operator) {
|
public void refreshOverviewEmployeeResults(Long projectId, String operator) {
|
||||||
@@ -393,6 +423,22 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CcdiProjectEmployeeCreditNegativeExcel buildEmployeeCreditNegativeExcelRow(
|
||||||
|
CcdiProjectEmployeeCreditNegativeItemVO item
|
||||||
|
) {
|
||||||
|
CcdiProjectEmployeeCreditNegativeExcel row = new CcdiProjectEmployeeCreditNegativeExcel();
|
||||||
|
row.setPersonName(item.getPersonName());
|
||||||
|
row.setPersonId(item.getPersonId());
|
||||||
|
row.setQueryDate(item.getQueryDate());
|
||||||
|
row.setCivilCnt(item.getCivilCnt());
|
||||||
|
row.setCivilLmt(item.getCivilLmt());
|
||||||
|
row.setEnforceCnt(item.getEnforceCnt());
|
||||||
|
row.setEnforceLmt(item.getEnforceLmt());
|
||||||
|
row.setAdmCnt(item.getAdmCnt());
|
||||||
|
row.setAdmLmt(item.getAdmLmt());
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
private String formatRelatedStaff(String relatedStaffName, String relatedStaffCode) {
|
private String formatRelatedStaff(String relatedStaffName, String relatedStaffCode) {
|
||||||
if (relatedStaffName == null || relatedStaffName.isBlank()) {
|
if (relatedStaffName == null || relatedStaffName.isBlank()) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
|
import com.ruoyi.common.utils.file.FileUtils;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险明细工作簿导出器
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class CcdiProjectRiskDetailWorkbookExporter {
|
||||||
|
|
||||||
|
private static final String CONTENT_TYPE =
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||||
|
|
||||||
|
public void export(
|
||||||
|
HttpServletResponse response,
|
||||||
|
Long projectId,
|
||||||
|
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows,
|
||||||
|
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows
|
||||||
|
) throws IOException {
|
||||||
|
response.setContentType(CONTENT_TYPE);
|
||||||
|
FileUtils.setAttachmentResponseHeader(response, "风险明细_" + projectId + ".xlsx");
|
||||||
|
|
||||||
|
try (Workbook workbook = new XSSFWorkbook()) {
|
||||||
|
writeSuspiciousSheet(workbook.createSheet("涉疑交易明细"), suspiciousRows);
|
||||||
|
writeCreditSheet(workbook.createSheet("员工负面征信信息"), creditRows);
|
||||||
|
writeAbnormalAccountSheet(workbook.createSheet("异常账户人员信息"));
|
||||||
|
workbook.write(response.getOutputStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSuspiciousSheet(Sheet sheet, List<CcdiProjectSuspiciousTransactionExcel> rows) {
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeCreditSheet(Sheet sheet, List<CcdiProjectEmployeeCreditNegativeExcel> rows) {
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
String[] headers = {
|
||||||
|
"员工姓名",
|
||||||
|
"身份证号",
|
||||||
|
"最近征信查询日期",
|
||||||
|
"民事案件笔数",
|
||||||
|
"民事案件金额",
|
||||||
|
"强制执行笔数",
|
||||||
|
"强制执行金额",
|
||||||
|
"行政处罚笔数",
|
||||||
|
"行政处罚金额"
|
||||||
|
};
|
||||||
|
writeHeader(header, headers);
|
||||||
|
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
CcdiProjectEmployeeCreditNegativeExcel item = rows.get(i);
|
||||||
|
Row row = sheet.createRow(i + 1);
|
||||||
|
row.createCell(0).setCellValue(safeText(item.getPersonName()));
|
||||||
|
row.createCell(1).setCellValue(safeText(item.getPersonId()));
|
||||||
|
row.createCell(2).setCellValue(safeText(item.getQueryDate()));
|
||||||
|
row.createCell(3).setCellValue(item.getCivilCnt() == null ? 0 : item.getCivilCnt());
|
||||||
|
row.createCell(4).setCellValue(safeNumber(item.getCivilLmt()));
|
||||||
|
row.createCell(5).setCellValue(item.getEnforceCnt() == null ? 0 : item.getEnforceCnt());
|
||||||
|
row.createCell(6).setCellValue(safeNumber(item.getEnforceLmt()));
|
||||||
|
row.createCell(7).setCellValue(item.getAdmCnt() == null ? 0 : item.getAdmCnt());
|
||||||
|
row.createCell(8).setCellValue(safeNumber(item.getAdmLmt()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeAbnormalAccountSheet(Sheet sheet) {
|
||||||
|
Row header = sheet.createRow(0);
|
||||||
|
String[] headers = { "账号", "开户人", "银行", "异常类型", "异常发生时间", "状态" };
|
||||||
|
writeHeader(header, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeHeader(Row row, String[] headers) {
|
||||||
|
for (int i = 0; i < headers.length; i++) {
|
||||||
|
row.createCell(i).setCellValue(headers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String safeText(String value) {
|
||||||
|
return value == null ? "" : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double safeNumber(BigDecimal value) {
|
||||||
|
return value == null ? 0D : value.doubleValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -609,6 +609,25 @@
|
|||||||
order by neg.query_date desc, neg.person_id asc
|
order by neg.query_date desc, neg.person_id asc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectEmployeeCreditNegativeList"
|
||||||
|
resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO">
|
||||||
|
select
|
||||||
|
coalesce(neg.person_name, result.staff_name) as personName,
|
||||||
|
neg.person_id as personId,
|
||||||
|
date_format(neg.query_date, '%Y-%m-%d') as queryDate,
|
||||||
|
ifnull(neg.civil_cnt, 0) as civilCnt,
|
||||||
|
ifnull(neg.civil_lmt, 0) as civilLmt,
|
||||||
|
ifnull(neg.enforce_cnt, 0) as enforceCnt,
|
||||||
|
ifnull(neg.enforce_lmt, 0) as enforceLmt,
|
||||||
|
ifnull(neg.adm_cnt, 0) as admCnt,
|
||||||
|
ifnull(neg.adm_lmt, 0) as admLmt
|
||||||
|
from ccdi_project_overview_employee_result result
|
||||||
|
inner join ccdi_credit_negative_info neg
|
||||||
|
on neg.person_id = result.staff_id_card
|
||||||
|
where result.project_id = #{projectId}
|
||||||
|
order by neg.query_date desc, neg.person_id asc
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectRiskModelNamesByScope" resultType="java.lang.String">
|
<select id="selectRiskModelNamesByScope" resultType="java.lang.String">
|
||||||
select
|
select
|
||||||
json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName'))) as model_name
|
json_unquote(json_extract(result.model_hit_summary_json, concat('$[', idx.idx, '].modelName'))) as model_name
|
||||||
|
|||||||
@@ -144,6 +144,22 @@ class CcdiProjectOverviewControllerContractTest {
|
|||||||
assertNotNull(operation);
|
assertNotNull(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeRiskDetailsExportEndpointContract() throws Exception {
|
||||||
|
Class<?> controllerClass = Class.forName("com.ruoyi.ccdi.project.controller.CcdiProjectOverviewController");
|
||||||
|
Method method = controllerClass.getMethod(
|
||||||
|
"exportRiskDetails",
|
||||||
|
HttpServletResponse.class,
|
||||||
|
Long.class
|
||||||
|
);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/risk-details/export", postMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldExposeSuspiciousTransactionsQueryDtoFields() throws Exception {
|
void shouldExposeSuspiciousTransactionsQueryDtoFields() throws Exception {
|
||||||
Class<?> dtoClass = Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO");
|
Class<?> dtoClass = Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO");
|
||||||
|
|||||||
@@ -268,4 +268,25 @@ class CcdiProjectOverviewControllerTest {
|
|||||||
assertEquals("/suspicious-transactions/export", postMapping.value()[0]);
|
assertEquals("/suspicious-transactions/export", postMapping.value()[0]);
|
||||||
assertNotNull(operation);
|
assertNotNull(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExposeRiskDetailsExportEndpoint() throws Exception {
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
controller.exportRiskDetails(response, 40L);
|
||||||
|
|
||||||
|
verify(overviewService).exportRiskDetails(same(response), same(40L));
|
||||||
|
|
||||||
|
Method method = CcdiProjectOverviewController.class.getMethod(
|
||||||
|
"exportRiskDetails",
|
||||||
|
jakarta.servlet.http.HttpServletResponse.class,
|
||||||
|
Long.class
|
||||||
|
);
|
||||||
|
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||||
|
Operation operation = method.getAnnotation(Operation.class);
|
||||||
|
|
||||||
|
assertNotNull(postMapping);
|
||||||
|
assertEquals("/risk-details/export", postMapping.value()[0]);
|
||||||
|
assertNotNull(operation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,12 +93,22 @@ class CcdiProjectOverviewMapperSqlTest {
|
|||||||
void shouldExposeEmployeeCreditNegativeQuery() throws Exception {
|
void shouldExposeEmployeeCreditNegativeQuery() throws Exception {
|
||||||
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
|
||||||
String employeeCreditSql = extractSelect(xml, "selectEmployeeCreditNegativePage");
|
String employeeCreditSql = extractSelect(xml, "selectEmployeeCreditNegativePage");
|
||||||
|
String employeeCreditExportSql = extractSelect(xml, "selectEmployeeCreditNegativeList");
|
||||||
|
|
||||||
assertTrue(employeeCreditSql.contains("from ccdi_project_overview_employee_result"), employeeCreditSql);
|
assertTrue(employeeCreditSql.contains("from ccdi_project_overview_employee_result"), employeeCreditSql);
|
||||||
assertTrue(employeeCreditSql.contains("inner join ccdi_credit_negative_info"), employeeCreditSql);
|
assertTrue(employeeCreditSql.contains("inner join ccdi_credit_negative_info"), employeeCreditSql);
|
||||||
assertTrue(employeeCreditSql.contains("result.project_id = #{query.projectId}"), employeeCreditSql);
|
assertTrue(employeeCreditSql.contains("result.project_id = #{query.projectId}"), employeeCreditSql);
|
||||||
assertTrue(employeeCreditSql.contains("order by neg.query_date desc, neg.person_id asc"), employeeCreditSql);
|
assertTrue(employeeCreditSql.contains("order by neg.query_date desc, neg.person_id asc"), employeeCreditSql);
|
||||||
assertFalse(employeeCreditSql.contains("ccdi_debts_info"), employeeCreditSql);
|
assertFalse(employeeCreditSql.contains("ccdi_debts_info"), employeeCreditSql);
|
||||||
|
|
||||||
|
assertTrue(employeeCreditExportSql.contains("from ccdi_project_overview_employee_result"), employeeCreditExportSql);
|
||||||
|
assertTrue(employeeCreditExportSql.contains("inner join ccdi_credit_negative_info"), employeeCreditExportSql);
|
||||||
|
assertTrue(employeeCreditExportSql.contains("result.project_id = #{projectId}"), employeeCreditExportSql);
|
||||||
|
assertTrue(
|
||||||
|
employeeCreditExportSql.contains("order by neg.query_date desc, neg.person_id asc"),
|
||||||
|
employeeCreditExportSql
|
||||||
|
);
|
||||||
|
assertFalse(employeeCreditExportSql.contains("ccdi_debts_info"), employeeCreditExportSql);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String extractSelect(String xml, String selectId) {
|
private String extractSelect(String xml, String selectId) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.project.service.impl;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||||
@@ -22,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -90,4 +92,33 @@ class CcdiProjectOverviewServiceEmployeeCreditNegativeTest {
|
|||||||
|
|
||||||
assertThrows(ServiceException.class, () -> service.getEmployeeCreditNegative(queryDTO));
|
assertThrows(ServiceException.class, () -> service.getEmployeeCreditNegative(queryDTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportEmployeeCreditNegativeRows() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeItemVO item = new CcdiProjectEmployeeCreditNegativeItemVO();
|
||||||
|
item.setPersonName("李四");
|
||||||
|
item.setPersonId("330000000000000001");
|
||||||
|
item.setQueryDate("2026-03-20");
|
||||||
|
item.setCivilCnt(1);
|
||||||
|
item.setCivilLmt(new BigDecimal("10000.00"));
|
||||||
|
when(overviewMapper.selectEmployeeCreditNegativeList(40L)).thenReturn(List.of(item));
|
||||||
|
|
||||||
|
List<CcdiProjectEmployeeCreditNegativeExcel> rows = service.exportEmployeeCreditNegative(40L);
|
||||||
|
|
||||||
|
assertEquals(1, rows.size());
|
||||||
|
assertEquals("李四", rows.getFirst().getPersonName());
|
||||||
|
assertEquals("330000000000000001", rows.getFirst().getPersonId());
|
||||||
|
verify(overviewMapper).selectEmployeeCreditNegativeList(eq(40L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldThrowWhenExportEmployeeCreditNegativeProjectDoesNotExist() {
|
||||||
|
when(projectMapper.selectById(100L)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(ServiceException.class, () -> service.exportEmployeeCreditNegative(100L));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import com.ruoyi.ccdi.project.domain.CcdiProject;
|
|||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
|
||||||
@@ -19,6 +22,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
|
|||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||||
@@ -28,6 +32,7 @@ import com.ruoyi.common.exception.ServiceException;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
@@ -66,6 +71,9 @@ class CcdiProjectOverviewServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiProjectRiskDetailWorkbookExporter workbookExporter;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldBuildDashboardWithNoRiskCount() {
|
void shouldBuildDashboardWithNoRiskCount() {
|
||||||
CcdiProject project = new CcdiProject();
|
CcdiProject project = new CcdiProject();
|
||||||
@@ -198,6 +206,50 @@ class CcdiProjectOverviewServiceImplTest {
|
|||||||
assertThrows(ServiceException.class, () -> service.getPersonAnalysisDetail(buildPersonAnalysisDetailQuery(99L)));
|
assertThrows(ServiceException.class, () -> service.getPersonAnalysisDetail(buildPersonAnalysisDetailQuery(99L)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportRiskDetailsWorkbook() throws Exception {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(40L);
|
||||||
|
when(projectMapper.selectById(40L)).thenReturn(project);
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionItemVO suspiciousItem = new CcdiProjectSuspiciousTransactionItemVO();
|
||||||
|
suspiciousItem.setTrxDate("2026-03-20 10:00:00");
|
||||||
|
suspiciousItem.setSuspiciousPersonName("张三");
|
||||||
|
suspiciousItem.setRelatedPersonName("张三");
|
||||||
|
suspiciousItem.setRelatedStaffName("张三");
|
||||||
|
suspiciousItem.setRelatedStaffCode("1001");
|
||||||
|
suspiciousItem.setRelationType("本人");
|
||||||
|
suspiciousItem.setUserMemo("转账");
|
||||||
|
suspiciousItem.setCashType("转账");
|
||||||
|
suspiciousItem.setDisplayAmount(new BigDecimal("100.00"));
|
||||||
|
when(overviewMapper.selectSuspiciousTransactionList(any())).thenReturn(List.of(suspiciousItem));
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeItemVO creditItem = new CcdiProjectEmployeeCreditNegativeItemVO();
|
||||||
|
creditItem.setPersonName("李四");
|
||||||
|
creditItem.setPersonId("330000000000000001");
|
||||||
|
creditItem.setQueryDate("2026-03-20");
|
||||||
|
creditItem.setCivilCnt(1);
|
||||||
|
creditItem.setCivilLmt(new BigDecimal("20000.00"));
|
||||||
|
when(overviewMapper.selectEmployeeCreditNegativeList(40L)).thenReturn(List.of(creditItem));
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
service.exportRiskDetails(response, 40L);
|
||||||
|
|
||||||
|
verify(overviewMapper).selectSuspiciousTransactionList(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())
|
||||||
|
),
|
||||||
|
argThat((List<CcdiProjectEmployeeCreditNegativeExcel> rows) ->
|
||||||
|
rows.size() == 1 && "李四".equals(rows.getFirst().getPersonName())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnPersonAnalysisDetailWithBasicInfoAndGroupedAbnormalDetail() {
|
void shouldReturnPersonAnalysisDetailWithBasicInfoAndGroupedAbnormalDetail() {
|
||||||
CcdiProject project = new CcdiProject();
|
CcdiProject project = new CcdiProject();
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
|
||||||
|
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CcdiProjectRiskDetailWorkbookExporterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportWorkbookWithThreeOrderedSheets() throws Exception {
|
||||||
|
CcdiProjectRiskDetailWorkbookExporter exporter = new CcdiProjectRiskDetailWorkbookExporter();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
CcdiProjectSuspiciousTransactionExcel suspiciousRow = new CcdiProjectSuspiciousTransactionExcel();
|
||||||
|
suspiciousRow.setTrxDate("2026-03-20 10:00:00");
|
||||||
|
suspiciousRow.setSuspiciousPersonName("张三");
|
||||||
|
suspiciousRow.setRelatedPersonName("张三");
|
||||||
|
suspiciousRow.setRelatedStaffDisplay("张三(1001)");
|
||||||
|
suspiciousRow.setRelationType("本人");
|
||||||
|
suspiciousRow.setSummaryAndCashType("转账/转账");
|
||||||
|
suspiciousRow.setDisplayAmount(new BigDecimal("100.00"));
|
||||||
|
|
||||||
|
CcdiProjectEmployeeCreditNegativeExcel creditRow = new CcdiProjectEmployeeCreditNegativeExcel();
|
||||||
|
creditRow.setPersonName("李四");
|
||||||
|
creditRow.setPersonId("330000000000000001");
|
||||||
|
creditRow.setQueryDate("2026-03-20");
|
||||||
|
creditRow.setCivilCnt(1);
|
||||||
|
creditRow.setCivilLmt(new BigDecimal("20000.00"));
|
||||||
|
|
||||||
|
exporter.export(response, 40L, List.of(suspiciousRow), List.of(creditRow));
|
||||||
|
|
||||||
|
assertTrue(response.getContentType().startsWith(
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
));
|
||||||
|
|
||||||
|
try (var workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||||
|
assertEquals(3, workbook.getNumberOfSheets());
|
||||||
|
assertEquals("涉疑交易明细", workbook.getSheetAt(0).getSheetName());
|
||||||
|
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(5).getStringCellValue());
|
||||||
|
assertEquals(1, workbook.getSheetAt(2).getPhysicalNumberOfRows());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# 项目详情风险明细统一导出后端实施记录
|
||||||
|
|
||||||
|
**日期**: 2026-03-30
|
||||||
|
**范围**: 项目详情 - 结果总览 - 风险明细统一导出后端
|
||||||
|
|
||||||
|
## 1. 本次实施内容
|
||||||
|
|
||||||
|
本次后端完成以下改动:
|
||||||
|
|
||||||
|
- 在 `CcdiProjectOverviewController` 新增统一导出接口:
|
||||||
|
- `POST /ccdi/project/overview/risk-details/export`
|
||||||
|
- 在 `ICcdiProjectOverviewService`、`CcdiProjectOverviewServiceImpl` 中新增统一导出主流程
|
||||||
|
- 新增 `CcdiProjectEmployeeCreditNegativeExcel`,用于 `员工负面征信信息` sheet 行导出
|
||||||
|
- 在 `CcdiProjectOverviewMapper` 与 `CcdiProjectOverviewMapper.xml` 中新增 `selectEmployeeCreditNegativeList`
|
||||||
|
- 新增 `CcdiProjectRiskDetailWorkbookExporter`,统一生成 3 个 sheet:
|
||||||
|
- `涉疑交易明细`
|
||||||
|
- `员工负面征信信息`
|
||||||
|
- `异常账户人员信息`
|
||||||
|
|
||||||
|
## 2. 导出实现口径
|
||||||
|
|
||||||
|
### 2.1 涉疑交易明细
|
||||||
|
|
||||||
|
- 复用现有 `exportSuspiciousTransactions` 查询链路
|
||||||
|
- 在统一导出主流程中固定传入 `suspiciousType=ALL`
|
||||||
|
- 导出当前项目全部命中记录
|
||||||
|
|
||||||
|
### 2.2 员工负面征信信息
|
||||||
|
|
||||||
|
- 复用现有项目员工范围口径
|
||||||
|
- 新增非分页导出 SQL:`selectEmployeeCreditNegativeList`
|
||||||
|
- 只导出存在负面征信记录的员工
|
||||||
|
|
||||||
|
### 2.3 异常账户人员信息
|
||||||
|
|
||||||
|
- 本轮不开发真实查询
|
||||||
|
- 统一导出文件中保留 `异常账户人员信息` sheet
|
||||||
|
- sheet 仅输出表头:
|
||||||
|
- `账号`
|
||||||
|
- `开户人`
|
||||||
|
- `银行`
|
||||||
|
- `异常类型`
|
||||||
|
- `异常发生时间`
|
||||||
|
- `状态`
|
||||||
|
|
||||||
|
## 3. 测试补充
|
||||||
|
|
||||||
|
新增或扩展了以下测试:
|
||||||
|
|
||||||
|
- `CcdiProjectOverviewControllerContractTest`
|
||||||
|
- 校验统一导出接口路径和注解
|
||||||
|
- `CcdiProjectOverviewControllerTest`
|
||||||
|
- 校验控制器对统一导出服务的委托
|
||||||
|
- `CcdiProjectOverviewMapperSqlTest`
|
||||||
|
- 校验员工负面征信导出 SQL 的表、关联和排序
|
||||||
|
- `CcdiProjectOverviewServiceEmployeeCreditNegativeTest`
|
||||||
|
- 校验员工负面征信导出列表映射与项目校验
|
||||||
|
- `CcdiProjectOverviewServiceImplTest`
|
||||||
|
- 校验统一导出主流程会查询两类真实数据并调用工作簿导出器
|
||||||
|
- `CcdiProjectRiskDetailWorkbookExporterTest`
|
||||||
|
- 校验 3 个 sheet 的顺序和空白异常账户 sheet 表头
|
||||||
|
|
||||||
|
## 4. 验证命令
|
||||||
|
|
||||||
|
执行命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn -pl ccdi-project -am -Dsurefire.failIfNoSpecifiedTests=false \
|
||||||
|
-Dtest=CcdiProjectOverviewControllerTest,CcdiProjectOverviewControllerContractTest,\
|
||||||
|
CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceEmployeeCreditNegativeTest,\
|
||||||
|
CcdiProjectOverviewServiceImplTest,CcdiProjectRiskDetailWorkbookExporterTest test
|
||||||
|
```
|
||||||
|
|
||||||
|
验证结果:
|
||||||
|
|
||||||
|
- 42 个定向测试全部通过
|
||||||
|
- 统一导出接口、数据查询、工作簿生成链路全部通过定向验证
|
||||||
|
|
||||||
|
## 5. 结果说明
|
||||||
|
|
||||||
|
本次后端已满足设计目标:
|
||||||
|
|
||||||
|
- 提供统一导出接口
|
||||||
|
- 导出文件固定包含 3 个 sheet
|
||||||
|
- 涉疑交易与员工负面征信导出真实数据
|
||||||
|
- 异常账户 sheet 保留空白模板,不伪造数据
|
||||||
|
|
||||||
|
未完成项:
|
||||||
|
|
||||||
|
- 未开发异常账户真实查询链路
|
||||||
|
- 未进行真实浏览器下载联调,等待与前端统一按钮联调时一起验证
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# 项目详情风险明细统一导出前端实施记录
|
||||||
|
|
||||||
|
**日期**: 2026-03-30
|
||||||
|
**范围**: 项目详情 - 结果总览 - 风险明细统一导出前端
|
||||||
|
|
||||||
|
## 1. 本次实施内容
|
||||||
|
|
||||||
|
本次前端仅修改 `ruoyi-ui/src/views/ccdiProject/components/detail/RiskDetailSection.vue`,完成以下调整:
|
||||||
|
|
||||||
|
- 在 `风险明细` 总卡片右上角新增统一 `导出` 按钮
|
||||||
|
- 移除 `涉疑交易明细` 区块右上角原有导出按钮
|
||||||
|
- 移除 `异常账户人员信息` 区块右上角占位导出按钮
|
||||||
|
- 将下载路径从:
|
||||||
|
- `ccdi/project/overview/suspicious-transactions/export`
|
||||||
|
- 调整为 `ccdi/project/overview/risk-details/export`
|
||||||
|
- 将导出文件名调整为:
|
||||||
|
- `风险明细_<projectId>_<timestamp>.xlsx`
|
||||||
|
- 调整总卡片头部布局,使标题区与导出按钮同一行展示
|
||||||
|
|
||||||
|
## 2. 交互口径
|
||||||
|
|
||||||
|
- 统一导出按钮只依赖 `projectId` 是否存在
|
||||||
|
- 不再绑定 `suspiciousTotal`
|
||||||
|
- 不再携带 `suspiciousType`
|
||||||
|
- 不改变现有涉疑交易筛选、分页和详情弹窗逻辑
|
||||||
|
- 不改变员工负面征信分页逻辑
|
||||||
|
|
||||||
|
## 3. 验证结果
|
||||||
|
|
||||||
|
执行命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ruoyi-ui
|
||||||
|
npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
执行结果:
|
||||||
|
|
||||||
|
- 构建成功
|
||||||
|
- 仅存在仓库原有的体积告警,没有新增编译错误
|
||||||
|
|
||||||
|
## 4. 本轮未完成项
|
||||||
|
|
||||||
|
- 未做浏览器层手工点击联调
|
||||||
|
- 未启动前端开发服务做页面人工回归
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 本轮已完成静态构建验证
|
||||||
|
- 真实下载链路需结合后端统一导出接口在联调时一起确认
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
<div class="section-title">风险明细</div>
|
<div class="section-title">风险明细</div>
|
||||||
<div class="section-subtitle">展示涉疑交易与异常账户关联人员信息</div>
|
<div class="section-subtitle">展示涉疑交易与异常账户关联人员信息</div>
|
||||||
</div>
|
</div>
|
||||||
|
<el-button size="mini" type="text" :disabled="!projectId" @click="handleRiskDetailExport">
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
@@ -30,14 +33,6 @@
|
|||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
<el-button
|
|
||||||
size="mini"
|
|
||||||
type="text"
|
|
||||||
:disabled="!projectId || suspiciousTotal === 0"
|
|
||||||
@click="handleExport"
|
|
||||||
>
|
|
||||||
导出
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -191,7 +186,6 @@
|
|||||||
<div class="block-title">异常账户人员信息</div>
|
<div class="block-title">异常账户人员信息</div>
|
||||||
<div class="block-subtitle">展示异常账户关联人员与处理状态</div>
|
<div class="block-subtitle">展示异常账户关联人员与处理状态</div>
|
||||||
</div>
|
</div>
|
||||||
<el-button size="mini" type="text">导出</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="sectionData.abnormalAccountList || []" class="detail-table">
|
<el-table :data="sectionData.abnormalAccountList || []" class="detail-table">
|
||||||
@@ -592,17 +586,16 @@ export default {
|
|||||||
this.detailLoading = false;
|
this.detailLoading = false;
|
||||||
this.detailData = createEmptyDetailData();
|
this.detailData = createEmptyDetailData();
|
||||||
},
|
},
|
||||||
handleExport() {
|
handleRiskDetailExport() {
|
||||||
if (!this.projectId || this.suspiciousTotal === 0) {
|
if (!this.projectId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.download(
|
this.download(
|
||||||
"ccdi/project/overview/suspicious-transactions/export",
|
"ccdi/project/overview/risk-details/export",
|
||||||
{
|
{
|
||||||
projectId: this.projectId,
|
projectId: this.projectId,
|
||||||
suspiciousType: this.currentSuspiciousType,
|
|
||||||
},
|
},
|
||||||
`涉疑交易明细_${new Date().getTime()}.xlsx`
|
`风险明细_${this.projectId}_${new Date().getTime()}.xlsx`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
formatRelatedStaff(row) {
|
formatRelatedStaff(row) {
|
||||||
@@ -723,6 +716,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
border-left: 4px solid #2563eb;
|
border-left: 4px solid #2563eb;
|
||||||
|
|||||||
Reference in New Issue
Block a user