diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java index 406413a4..dcb4c3f6 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java @@ -9,10 +9,12 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskExclusionSaveDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; +import com.ruoyi.ccdi.project.domain.dto.CcdiProjectUnsubmittedAccountQueryDTO; 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.excel.CcdiProjectUnsubmittedAccountFlowExcel; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO; @@ -24,6 +26,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountVO; import com.ruoyi.ccdi.project.service.CcdiProjectAccessService; import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService; import com.ruoyi.common.utils.poi.ExcelUtil; @@ -226,6 +229,18 @@ public class CcdiProjectOverviewController extends BaseController { return AjaxResult.success(pageVO); } + /** + * 查询未提交账户 + */ + @GetMapping("/unsubmitted-accounts") + @Operation(summary = "查询未提交账户") + @PreAuthorize("@ss.hasPermi('ccdi:project:query')") + public AjaxResult getUnsubmittedAccounts(CcdiProjectUnsubmittedAccountQueryDTO queryDTO) { + projectAccessService.assertCanRead(queryDTO.getProjectId()); + CcdiProjectUnsubmittedAccountVO result = overviewService.getUnsubmittedAccounts(queryDTO); + return AjaxResult.success(result); + } + /** * 导出涉疑交易明细 */ @@ -302,6 +317,20 @@ public class CcdiProjectOverviewController extends BaseController { util.exportExcel(response, rows, "外部人员风险模型命中人员"); } + /** + * 导出未提交账户流水 + */ + @PostMapping("/unsubmitted-accounts/export") + @Operation(summary = "导出未提交账户流水") + @PreAuthorize("@ss.hasPermi('ccdi:project:query')") + public void exportUnsubmittedAccountFlows(HttpServletResponse response, Long projectId) { + projectAccessService.assertCanRead(projectId); + List rows = overviewService.exportUnsubmittedAccountFlows(projectId); + ExcelUtil util = + new ExcelUtil<>(CcdiProjectUnsubmittedAccountFlowExcel.class); + util.exportExcel(response, rows, "未提交账户流水"); + } + /** * 导出风险明细 */ diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectUnsubmittedAccountQueryDTO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectUnsubmittedAccountQueryDTO.java new file mode 100644 index 00000000..e2e9ad65 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectUnsubmittedAccountQueryDTO.java @@ -0,0 +1,15 @@ +package com.ruoyi.ccdi.project.domain.dto; + +import lombok.Data; + +/** + * 未提交账户查询参数 + */ +@Data +public class CcdiProjectUnsubmittedAccountQueryDTO { + + /** + * 项目ID + */ + private Long projectId; +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectUnsubmittedAccountFlowExcel.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectUnsubmittedAccountFlowExcel.java new file mode 100644 index 00000000..9e1b83a8 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectUnsubmittedAccountFlowExcel.java @@ -0,0 +1,45 @@ +package com.ruoyi.ccdi.project.domain.excel; + +import com.ruoyi.common.annotation.Excel; +import java.math.BigDecimal; +import lombok.Data; + +/** + * 未提交账户流水导出对象 + */ +@Data +public class CcdiProjectUnsubmittedAccountFlowExcel { + + @Excel(name = "主体名称") + private String subjectName; + + @Excel(name = "未提交账号") + private String accountNo; + + @Excel(name = "交易时间") + private String trxDate; + + @Excel(name = "本方账号") + private String leAccountNo; + + @Excel(name = "本方主体") + private String leAccountName; + + @Excel(name = "对方名称") + private String customerAccountName; + + @Excel(name = "对方账号") + private String customerAccountNo; + + @Excel(name = "摘要") + private String userMemo; + + @Excel(name = "交易类型") + private String cashType; + + @Excel(name = "流入金额") + private BigDecimal amountCr; + + @Excel(name = "流出金额") + private BigDecimal amountDr; +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectOverviewReportVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectOverviewReportVO.java index 7602655f..205d967d 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectOverviewReportVO.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectOverviewReportVO.java @@ -4,6 +4,7 @@ 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.excel.CcdiProjectUnsubmittedAccountFlowExcel; import java.util.ArrayList; import java.util.List; import lombok.Data; @@ -37,4 +38,6 @@ public class CcdiProjectOverviewReportVO { private List illegalPeople = new ArrayList<>(); private List abnormalAccounts = new ArrayList<>(); + + private List unsubmittedAccountFlows = new ArrayList<>(); } diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountFlowVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountFlowVO.java new file mode 100644 index 00000000..75cb59f5 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountFlowVO.java @@ -0,0 +1,33 @@ +package com.ruoyi.ccdi.project.domain.vo; + +import java.math.BigDecimal; +import lombok.Data; + +/** + * 未提交账户流水行 + */ +@Data +public class CcdiProjectUnsubmittedAccountFlowVO { + + private String subjectName; + + private String accountNo; + + private String trxDate; + + private String leAccountNo; + + private String leAccountName; + + private String customerAccountName; + + private String customerAccountNo; + + private String userMemo; + + private String cashType; + + private BigDecimal amountCr; + + private BigDecimal amountDr; +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountItemVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountItemVO.java new file mode 100644 index 00000000..b9482c6f --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountItemVO.java @@ -0,0 +1,33 @@ +package com.ruoyi.ccdi.project.domain.vo; + +import java.math.BigDecimal; +import lombok.Data; + +/** + * 未提交账户账号项 + */ +@Data +public class CcdiProjectUnsubmittedAccountItemVO { + + private String subjectName; + + private String accountNo; + + private BigDecimal totalAmountCr; + + private BigDecimal totalAmountDr; + + private BigDecimal netAmount; + + private BigDecimal inflowRatio; + + private BigDecimal outflowRatio; + + private Boolean highRatio; + + private Integer transactionCount; + + private String startDate; + + private String endDate; +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountSubjectVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountSubjectVO.java new file mode 100644 index 00000000..c80723c0 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountSubjectVO.java @@ -0,0 +1,25 @@ +package com.ruoyi.ccdi.project.domain.vo; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +/** + * 未提交账户主体分组 + */ +@Data +public class CcdiProjectUnsubmittedAccountSubjectVO { + + private String subjectName; + + private Integer accountCount = 0; + + private BigDecimal totalAmountCr = BigDecimal.ZERO; + + private BigDecimal totalAmountDr = BigDecimal.ZERO; + + private BigDecimal netAmount = BigDecimal.ZERO; + + private List accounts = new ArrayList<>(); +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountVO.java new file mode 100644 index 00000000..a0f529d0 --- /dev/null +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectUnsubmittedAccountVO.java @@ -0,0 +1,23 @@ +package com.ruoyi.ccdi.project.domain.vo; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +/** + * 未提交账户查询结果 + */ +@Data +public class CcdiProjectUnsubmittedAccountVO { + + private Integer accountCount = 0; + + private BigDecimal totalAmountCr = BigDecimal.ZERO; + + private BigDecimal totalAmountDr = BigDecimal.ZERO; + + private BigDecimal netAmount = BigDecimal.ZERO; + + private List subjects = new ArrayList<>(); +} diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java index 844f0554..d5f6f72e 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java @@ -9,6 +9,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQuery 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.dto.CcdiProjectUnsubmittedAccountQueryDTO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO; @@ -24,6 +25,8 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountFlowVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountItemVO; import java.util.List; import java.util.Map; import org.apache.ibatis.annotations.Mapper; @@ -259,6 +262,24 @@ public interface CcdiProjectOverviewMapper { */ List selectAbnormalAccountList(@Param("projectId") Long projectId); + /** + * 查询未提交账户列表 + * + * @param query 查询条件 + * @return 未提交账户列表 + */ + List selectUnsubmittedAccountList( + @Param("query") CcdiProjectUnsubmittedAccountQueryDTO query + ); + + /** + * 查询未提交账户流水 + * + * @param projectId 项目ID + * @return 未提交账户流水 + */ + List selectUnsubmittedAccountFlowList(@Param("projectId") Long projectId); + /** * 查询项目员工负面征信导出列表 * diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java index 8adfb15a..3ec913ce 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java @@ -9,12 +9,14 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskExclusionSaveDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; +import com.ruoyi.ccdi.project.domain.dto.CcdiProjectUnsubmittedAccountQueryDTO; 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.excel.CcdiProjectUnsubmittedAccountFlowExcel; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO; @@ -26,6 +28,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountVO; import jakarta.servlet.http.HttpServletResponse; import java.util.List; @@ -278,6 +281,28 @@ public interface ICcdiProjectOverviewService { return List.of(); } + /** + * 查询未提交账户 + * + * @param queryDTO 查询条件 + * @return 未提交账户结果 + */ + default CcdiProjectUnsubmittedAccountVO getUnsubmittedAccounts( + CcdiProjectUnsubmittedAccountQueryDTO queryDTO + ) { + return new CcdiProjectUnsubmittedAccountVO(); + } + + /** + * 导出未提交账户流水 + * + * @param projectId 项目ID + * @return 导出列表 + */ + default List exportUnsubmittedAccountFlows(Long projectId) { + return List.of(); + } + /** * 重算结果总览员工结果并同步项目风险人数 * diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewReportPdfExporter.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewReportPdfExporter.java index b599c815..60f15c5e 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewReportPdfExporter.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewReportPdfExporter.java @@ -246,6 +246,25 @@ public class CcdiProjectOverviewReportPdfExporter { new float[] { 0.12F, 0.1F, 0.14F, 0.14F, 0.11F, 0.08F, 0.22F, 0.09F }, "暂无异常账户信息" ); + + writer.subsection("4. 未提交账户流水表(共" + report.getUnsubmittedAccountFlows().size() + "条)"); + writer.table( + List.of("主体", "未提交账号", "交易时间", "本方账号", "对方名称", "摘要/交易类型", "流入", "流出"), + report.getUnsubmittedAccountFlows().stream() + .map(item -> List.of( + safeText(item.getSubjectName()), + maskAccount(item.getAccountNo()), + safeText(item.getTrxDate()), + maskAccount(item.getLeAccountNo()), + safeText(item.getCustomerAccountName()), + formatSummaryAndCashType(item.getUserMemo(), item.getCashType()), + formatMoney(item.getAmountCr()), + formatMoney(item.getAmountDr()) + )) + .collect(Collectors.toList()), + new float[] { 0.14F, 0.13F, 0.12F, 0.13F, 0.14F, 0.17F, 0.08F, 0.09F }, + "暂无未提交账户流水" + ); } private LoadedChineseFont loadChineseFont(PDDocument document) throws IOException { diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java index 2c2f9b34..08c8fe8c 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java @@ -12,12 +12,14 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskExclusionSaveDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; +import com.ruoyi.ccdi.project.domain.dto.CcdiProjectUnsubmittedAccountQueryDTO; 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.excel.CcdiProjectUnsubmittedAccountFlowExcel; import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult; import com.ruoyi.ccdi.project.domain.entity.CcdiProjectRiskExclusion; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO; @@ -53,6 +55,10 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountFlowVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountItemVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountSubjectVO; +import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountVO; import com.ruoyi.ccdi.project.mapper.CcdiModelParamMapper; import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; @@ -67,6 +73,7 @@ import com.ruoyi.common.utils.SecurityUtils; import jakarta.servlet.http.HttpServletResponse; import jakarta.annotation.Resource; import java.io.IOException; +import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; @@ -474,6 +481,39 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi .toList(); } + @Override + public CcdiProjectUnsubmittedAccountVO getUnsubmittedAccounts(CcdiProjectUnsubmittedAccountQueryDTO queryDTO) { + ensureProjectExists(queryDTO.getProjectId()); + + List rows = + defaultList(overviewMapper.selectUnsubmittedAccountList(queryDTO)); + CcdiProjectUnsubmittedAccountVO result = new CcdiProjectUnsubmittedAccountVO(); + result.setAccountCount(rows.size()); + result.setTotalAmountCr(sumUnsubmittedAmount(rows, true)); + result.setTotalAmountDr(sumUnsubmittedAmount(rows, false)); + result.setNetAmount(result.getTotalAmountCr().subtract(result.getTotalAmountDr())); + + Map> groupedRows = rows.stream() + .collect(Collectors.groupingBy( + item -> item.getSubjectName() == null ? "" : item.getSubjectName(), + LinkedHashMap::new, + Collectors.toList() + )); + result.setSubjects(groupedRows.entrySet().stream() + .map(entry -> buildUnsubmittedAccountSubject(entry.getKey(), entry.getValue())) + .toList()); + return result; + } + + @Override + public List exportUnsubmittedAccountFlows(Long projectId) { + ensureProjectExists(projectId); + + return defaultList(overviewMapper.selectUnsubmittedAccountFlowList(projectId)).stream() + .map(this::buildUnsubmittedAccountFlowExcelRow) + .toList(); + } + @Override public void exportRiskDetails(HttpServletResponse response, Long projectId) { CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO(); @@ -491,8 +531,9 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi Long projectId = queryDTO.getProjectId(); List creditRows = exportEmployeeCreditNegative(projectId); List abnormalRows = exportAbnormalAccountPeople(projectId); + List unsubmittedRows = exportUnsubmittedAccountFlows(projectId); try { - workbookExporter.export(response, projectId, suspiciousRows, creditRows, abnormalRows); + workbookExporter.export(response, projectId, suspiciousRows, creditRows, abnormalRows, unsubmittedRows); } catch (IOException e) { throw new ServiceException("导出风险明细失败"); } @@ -525,6 +566,7 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi )); report.setIllegalPeople(exportEmployeeCreditNegative(projectId)); report.setAbnormalAccounts(exportAbnormalAccountPeople(projectId)); + report.setUnsubmittedAccountFlows(exportUnsubmittedAccountFlows(projectId)); try { reportPdfExporter.export(response, report); @@ -910,6 +952,45 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi return row; } + private CcdiProjectUnsubmittedAccountSubjectVO buildUnsubmittedAccountSubject( + String subjectName, + List rows + ) { + CcdiProjectUnsubmittedAccountSubjectVO subject = new CcdiProjectUnsubmittedAccountSubjectVO(); + subject.setSubjectName(subjectName); + subject.setAccountCount(rows.size()); + subject.setTotalAmountCr(sumUnsubmittedAmount(rows, true)); + subject.setTotalAmountDr(sumUnsubmittedAmount(rows, false)); + subject.setNetAmount(subject.getTotalAmountCr().subtract(subject.getTotalAmountDr())); + subject.setAccounts(rows); + return subject; + } + + private BigDecimal sumUnsubmittedAmount(List rows, boolean inflow) { + return rows.stream() + .map(item -> inflow ? item.getTotalAmountCr() : item.getTotalAmountDr()) + .filter(value -> value != null) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private CcdiProjectUnsubmittedAccountFlowExcel buildUnsubmittedAccountFlowExcelRow( + CcdiProjectUnsubmittedAccountFlowVO item + ) { + CcdiProjectUnsubmittedAccountFlowExcel row = new CcdiProjectUnsubmittedAccountFlowExcel(); + row.setSubjectName(item.getSubjectName()); + row.setAccountNo(item.getAccountNo()); + row.setTrxDate(item.getTrxDate()); + row.setLeAccountNo(item.getLeAccountNo()); + row.setLeAccountName(item.getLeAccountName()); + row.setCustomerAccountName(item.getCustomerAccountName()); + row.setCustomerAccountNo(item.getCustomerAccountNo()); + row.setUserMemo(item.getUserMemo()); + row.setCashType(item.getCashType()); + row.setAmountCr(item.getAmountCr()); + row.setAmountDr(item.getAmountDr()); + return row; + } + private List buildReportParams(CcdiProject project) { Long effectiveProjectId = "default".equals(project.getConfigType()) ? 0L : project.getProjectId(); return defaultList(modelParamMapper.selectByProjectId(effectiveProjectId)).stream() diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectRiskDetailWorkbookExporter.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectRiskDetailWorkbookExporter.java index f2a310b3..1fad7491 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectRiskDetailWorkbookExporter.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectRiskDetailWorkbookExporter.java @@ -3,6 +3,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.CcdiProjectSuspiciousTransactionExcel; +import com.ruoyi.ccdi.project.domain.excel.CcdiProjectUnsubmittedAccountFlowExcel; import com.ruoyi.common.utils.file.FileUtils; import jakarta.servlet.http.HttpServletResponse; import org.apache.poi.ss.usermodel.Row; @@ -29,7 +30,8 @@ public class CcdiProjectRiskDetailWorkbookExporter { Long projectId, List suspiciousRows, List creditRows, - List abnormalRows + List abnormalRows, + List unsubmittedRows ) throws IOException { response.setContentType(CONTENT_TYPE); FileUtils.setAttachmentResponseHeader(response, "风险明细_" + projectId + ".xlsx"); @@ -38,6 +40,7 @@ public class CcdiProjectRiskDetailWorkbookExporter { writeSuspiciousSheet(workbook.createSheet("涉疑交易明细"), suspiciousRows); writeCreditSheet(workbook.createSheet("员工负面征信信息"), creditRows); writeAbnormalAccountSheet(workbook.createSheet("异常账户人员信息"), abnormalRows); + writeUnsubmittedAccountSheet(workbook.createSheet("未提交账户流水"), unsubmittedRows); workbook.write(response.getOutputStream()); } } @@ -123,6 +126,40 @@ public class CcdiProjectRiskDetailWorkbookExporter { } } + private void writeUnsubmittedAccountSheet(Sheet sheet, List rows) { + Row header = sheet.createRow(0); + String[] headers = { + "主体名称", + "未提交账号", + "交易时间", + "本方账号", + "本方主体", + "对方名称", + "对方账号", + "摘要", + "交易类型", + "流入金额", + "流出金额" + }; + writeHeader(header, headers); + + for (int i = 0; i < rows.size(); i++) { + CcdiProjectUnsubmittedAccountFlowExcel item = rows.get(i); + Row row = sheet.createRow(i + 1); + row.createCell(0).setCellValue(safeText(item.getSubjectName())); + row.createCell(1).setCellValue(safeText(item.getAccountNo())); + row.createCell(2).setCellValue(safeText(item.getTrxDate())); + row.createCell(3).setCellValue(safeText(item.getLeAccountNo())); + row.createCell(4).setCellValue(safeText(item.getLeAccountName())); + row.createCell(5).setCellValue(safeText(item.getCustomerAccountName())); + row.createCell(6).setCellValue(safeText(item.getCustomerAccountNo())); + row.createCell(7).setCellValue(safeText(item.getUserMemo())); + row.createCell(8).setCellValue(safeText(item.getCashType())); + row.createCell(9).setCellValue(safeNumber(item.getAmountCr())); + row.createCell(10).setCellValue(safeNumber(item.getAmountDr())); + } + } + private void writeHeader(Row row, String[] headers) { for (int i = 0; i < headers.length; i++) { row.createCell(i).setCellValue(headers[i]); diff --git a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml index f5a7e167..42d33791 100644 --- a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml +++ b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml @@ -96,6 +96,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1545,6 +1573,185 @@ order by abnormal.abnormal_time desc, abnormal.account_no asc, abnormal.rule_code asc + + ( + bs.USER_MEMO like '%还贷%' + or bs.USER_MEMO like '%还贷款%' + or bs.USER_MEMO like '%贷款还款%' + or bs.USER_MEMO like '%归还贷款%' + or bs.USER_MEMO like '%贷款归还%' + or bs.USER_MEMO like '%还本%' + or bs.USER_MEMO like '%还息%' + or bs.USER_MEMO like '%付息%' + or bs.USER_MEMO like '%贷息%' + or bs.USER_MEMO like '%贷款利息%' + or bs.USER_MEMO like '%贷款发放%' + or bs.USER_MEMO like '%普贷放款%' + or bs.USER_MEMO like '%放款%' + or bs.USER_MEMO like '%放贷%' + or bs.USER_MEMO like '%银团贷%' + or bs.USER_MEMO like '%流贷%' + or bs.USER_MEMO like '%续贷%资金%' + or bs.CASH_TYPE like '%贷款本息%' + or bs.CASH_TYPE like '%贷款扣款%' + or bs.CASH_TYPE like '%贷款回收%' + or bs.CASH_TYPE like '%还贷款%' + or bs.CASH_TYPE like '%归还本金%' + or bs.CASH_TYPE like '%归还本息%' + or bs.CASH_TYPE like '%归还利息%' + or bs.CASH_TYPE like '%归还逾期利息%' + or bs.CASH_TYPE like '%贷息%' + or bs.CASH_TYPE like '%贷款放%' + or bs.CASH_TYPE like '%放贷%' + or bs.CASH_TYPE like '%信贷系统业务对公资产%' + or bs.CUSTOMER_ACCOUNT_NAME like '%个贷系统平账专户%' + or bs.CUSTOMER_ACCOUNT_NAME like '%企业贷%' + or bs.CUSTOMER_ACCOUNT_NAME like '%微粒贷%' + or bs.CUSTOMER_ACCOUNT_NAME like '%贷款还款户%' + or bs.CUSTOMER_ACCOUNT_NAME like '%贷款归还户%' + or bs.CUSTOMER_ACCOUNT_NAME like '%融资利息收入%' + or bs.CUSTOMER_ACCOUNT_NAME like '%短期贷款%' + or bs.CUSTOMER_ACCOUNT_NAME like '%科创贷%' + or bs.CUSTOMER_ACCOUNT_NAME like '%企金贷%' + or bs.CUSTOMER_ACCOUNT_NAME like '%网贷通%' + or bs.CUSTOMER_ACCOUNT_NAME like '%联合贷%' + or bs.CUSTOMER_ACCOUNT_NAME like '%数字信用凭据融资%' + or bs.CUSTOMER_ACCOUNT_NAME like '%消费金融%' + ) + + + + select + bs.bank_statement_id as bankStatementId, + trim(bs.LE_ACCOUNT_NAME) as subjectName, + replace(replace(replace(trim(bs.CUSTOMER_ACCOUNT_NO), '-', ''), char(9), ''), ' ', '') as accountNo, + bs.TRX_DATE as trxDate, + bs.LE_ACCOUNT_NO as leAccountNo, + bs.LE_ACCOUNT_NAME as leAccountName, + bs.CUSTOMER_ACCOUNT_NAME as customerAccountName, + bs.CUSTOMER_ACCOUNT_NO as customerAccountNo, + bs.USER_MEMO as userMemo, + bs.CASH_TYPE as cashType, + ifnull(bs.AMOUNT_CR, 0) as amountCr, + ifnull(bs.AMOUNT_DR, 0) as amountDr, + case + when then 1 + else 0 + end as loanFlag + from ccdi_bank_statement bs + where bs.project_id = #{unsubmittedProjectId} + and bs.LE_ACCOUNT_NAME is not null + and trim(bs.LE_ACCOUNT_NAME) != '' + and bs.CUSTOMER_ACCOUNT_NAME is not null + and trim(bs.CUSTOMER_ACCOUNT_NAME) != '' + and replace(replace(trim(bs.CUSTOMER_ACCOUNT_NAME), '(', '('), ')', ')') + = replace(replace(trim(bs.LE_ACCOUNT_NAME), '(', '('), ')', ')') + and bs.CUSTOMER_ACCOUNT_NO is not null + and replace(replace(replace(trim(bs.CUSTOMER_ACCOUNT_NO), '-', ''), char(9), ''), ' ', '') != '' + and replace(replace(replace(trim(bs.CUSTOMER_ACCOUNT_NO), '-', ''), char(9), ''), ' ', '') + != replace(replace(replace(trim(ifnull(bs.LE_ACCOUNT_NO, '')), '-', ''), char(9), ''), ' ', '') + and not exists ( + select 1 + from ccdi_bank_statement submitted + where submitted.project_id = bs.project_id + and submitted.LE_ACCOUNT_NO is not null + and replace(replace(replace(trim(submitted.LE_ACCOUNT_NO), '-', ''), char(9), ''), ' ', '') + = replace(replace(replace(trim(bs.CUSTOMER_ACCOUNT_NO), '-', ''), char(9), ''), ' ', '') + ) + + + + select + candidate.subjectName, + candidate.accountNo, + sum(candidate.amountCr) as totalAmountCr, + sum(candidate.amountDr) as totalAmountDr, + sum(candidate.amountCr) - sum(candidate.amountDr) as netAmount, + count(*) as transactionCount, + min(candidate.trxDate) as startDate, + max(candidate.trxDate) as endDate + from ( + + ) candidate + group by candidate.subjectName, candidate.accountNo + having sum(candidate.loanFlag) < count(*) + + + + select + trim(bs.LE_ACCOUNT_NAME) as subjectName, + sum(ifnull(bs.AMOUNT_CR, 0)) as subjectAmountCr, + sum(ifnull(bs.AMOUNT_DR, 0)) as subjectAmountDr + from ccdi_bank_statement bs + where bs.project_id = #{unsubmittedProjectId} + and bs.LE_ACCOUNT_NAME is not null + and trim(bs.LE_ACCOUNT_NAME) != '' + group by trim(bs.LE_ACCOUNT_NAME) + + + + + + rows) -> + rows != null && rows.isEmpty() ) ); } diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectRiskDetailWorkbookExporterTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectRiskDetailWorkbookExporterTest.java index 97723043..ef5fa9dd 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectRiskDetailWorkbookExporterTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectRiskDetailWorkbookExporterTest.java @@ -3,6 +3,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.CcdiProjectSuspiciousTransactionExcel; +import com.ruoyi.ccdi.project.domain.excel.CcdiProjectUnsubmittedAccountFlowExcel; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletResponse; @@ -17,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class CcdiProjectRiskDetailWorkbookExporterTest { @Test - void shouldExportWorkbookWithThreeOrderedSheets() throws Exception { + void shouldExportWorkbookWithFourOrderedSheets() throws Exception { CcdiProjectRiskDetailWorkbookExporter exporter = new CcdiProjectRiskDetailWorkbookExporter(); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -50,17 +51,38 @@ class CcdiProjectRiskDetailWorkbookExporterTest { abnormalRow.setReasonDetail("账户6222000000000001销户前存在异常交易"); abnormalRow.setInvolvedAmount(new BigDecimal("120000.00")); - exporter.export(response, 40L, List.of(suspiciousRow), List.of(creditRow), List.of(abnormalRow)); + CcdiProjectUnsubmittedAccountFlowExcel unsubmittedRow = new CcdiProjectUnsubmittedAccountFlowExcel(); + unsubmittedRow.setSubjectName("测试主体"); + unsubmittedRow.setAccountNo("330001000000000001"); + unsubmittedRow.setTrxDate("2026-03-21"); + unsubmittedRow.setLeAccountNo("330001000000000002"); + unsubmittedRow.setLeAccountName("测试主体"); + unsubmittedRow.setCustomerAccountName("测试主体"); + unsubmittedRow.setCustomerAccountNo("330001000000000001"); + unsubmittedRow.setUserMemo("往来款"); + unsubmittedRow.setCashType("转账"); + unsubmittedRow.setAmountCr(new BigDecimal("30000.00")); + unsubmittedRow.setAmountDr(BigDecimal.ZERO); + + exporter.export( + response, + 40L, + List.of(suspiciousRow), + List.of(creditRow), + List.of(abnormalRow), + List.of(unsubmittedRow) + ); assertTrue(response.getContentType().startsWith( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" )); try (var workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) { - assertEquals(3, workbook.getNumberOfSheets()); + assertEquals(4, workbook.getNumberOfSheets()); assertEquals("涉疑交易明细", workbook.getSheetAt(0).getSheetName()); assertEquals("员工负面征信信息", workbook.getSheetAt(1).getSheetName()); assertEquals("异常账户人员信息", workbook.getSheetAt(2).getSheetName()); + assertEquals("未提交账户流水", workbook.getSheetAt(3).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()); @@ -91,6 +113,13 @@ class CcdiProjectRiskDetailWorkbookExporterTest { assertEquals("账户6222000000000001销户前存在异常交易", workbook.getSheetAt(2).getRow(1).getCell(6).getStringCellValue()); assertEquals(120000D, workbook.getSheetAt(2).getRow(1).getCell(7).getNumericCellValue()); assertEquals(2, workbook.getSheetAt(2).getPhysicalNumberOfRows()); + assertEquals("主体名称", workbook.getSheetAt(3).getRow(0).getCell(0).getStringCellValue()); + assertEquals("未提交账号", workbook.getSheetAt(3).getRow(0).getCell(1).getStringCellValue()); + assertEquals("交易时间", workbook.getSheetAt(3).getRow(0).getCell(2).getStringCellValue()); + assertEquals("流入金额", workbook.getSheetAt(3).getRow(0).getCell(9).getStringCellValue()); + assertEquals("测试主体", workbook.getSheetAt(3).getRow(1).getCell(0).getStringCellValue()); + assertEquals("330001000000000001", workbook.getSheetAt(3).getRow(1).getCell(1).getStringCellValue()); + assertEquals(30000D, workbook.getSheetAt(3).getRow(1).getCell(9).getNumericCellValue()); } } } diff --git a/docs/plans/backend/2026-07-20-unsubmitted-account-backend-implementation.md b/docs/plans/backend/2026-07-20-unsubmitted-account-backend-implementation.md new file mode 100644 index 00000000..79617446 --- /dev/null +++ b/docs/plans/backend/2026-07-20-unsubmitted-account-backend-implementation.md @@ -0,0 +1,27 @@ +# 未提交账户后端实施计划 + +## 目标 + +在结果总览风险明细中新增“未提交账户”能力,参考 lsfx 未提交账户口径识别本项目已上传流水中疑似未提交分析的同名账户,并按已确认范围排除银行贷款/信贷类账号。 + +## 实施范围 + +- 新增未提交账户查询接口,返回汇总、主体分组和账号卡片数据。 +- 新增未提交账户导出接口,导出未提交账户流水。 +- 风险明细统一导出增加“未提交账户流水” Sheet。 +- 结果总览报告增加未提交账户明细。 +- 不新增数据库表,不新增菜单与权限。 + +## 关键口径 + +1. 候选账号来自 `ccdi_bank_statement` 当前项目流水。 +2. 对手方户名等于本方主体名,对手方账号非空,且清洗后不等于本方账号。 +3. 对手方账号不在本项目已提交账号集合中,已提交账号集合来自上传记录保存的 `account_nos`。 +4. 贷款排除只使用银行贷款/信贷高确定性关键词,不使用 `TRX_TYPE`。 +5. 只有候选账号关联流水全部命中贷款/信贷排除条件时,才排除该账号。 + +## 验证 + +- Mapper XML 断言覆盖未提交账户 SQL、贷款排除关键词与“不使用 TRX_TYPE”。 +- 风险明细工作簿测试覆盖新增 Sheet。 +- 后端编译或相关测试通过后记录实施结果。 diff --git a/docs/plans/frontend/2026-07-20-unsubmitted-account-frontend-implementation.md b/docs/plans/frontend/2026-07-20-unsubmitted-account-frontend-implementation.md new file mode 100644 index 00000000..db82621f --- /dev/null +++ b/docs/plans/frontend/2026-07-20-unsubmitted-account-frontend-implementation.md @@ -0,0 +1,26 @@ +# 未提交账户前端实施计划 + +## 目标 + +在结果总览风险明细页面新增“未提交账户”展示区,形态参考 lsfx 截图:顶部汇总、主体分组、账号卡片和导出流水入口。 + +## 实施范围 + +- `projectOverview.js` 增加未提交账户查询 API。 +- `RiskDetailSection.vue` 增加未提交账户区块。 +- 展示未提交账户数、流入总额、流出总额、流入/流出差额。 +- 按主体展示账号卡片,卡片显示账号、流入金额、流出金额和交易占比较高标记。 +- 导出按钮调用后端未提交账户流水导出接口。 + +## 交互规则 + +- 当前项目为空时显示空态。 +- 查询失败时展示错误提示并清空未提交账户数据。 +- 账号显示使用中间省略,金额按万元展示。 +- 交易占比较高按后端返回的标记展示。 + +## 验证 + +- 前端单元静态断言覆盖标题、汇总字段、导出入口和卡片字段。 +- 按项目要求使用可用 Node/npm 版本完成构建或测试。 +- 页面开发完成后使用浏览器实际验证。 diff --git a/docs/reports/implementation/2026-07-20-unsubmitted-account.md b/docs/reports/implementation/2026-07-20-unsubmitted-account.md new file mode 100644 index 00000000..ceaeadc8 --- /dev/null +++ b/docs/reports/implementation/2026-07-20-unsubmitted-account.md @@ -0,0 +1,20 @@ +# 未提交账户实施记录 + +## 修改内容 + +- 新增结果总览“未提交账户”后端查询与导出能力。 +- 新增前端风险明细未提交账户卡片展示。 +- 风险明细统一导出与结果总览报告接入未提交账户流水。 +- 贷款排除仅使用银行贷款/信贷高确定性关键词,未启用内部杂项账号排除。 + +## 影响范围 + +- 后端:`ccdi-project` 结果总览 Controller、Service、Mapper、VO、Excel 与导出器。 +- 前端:`ruoyi-ui` 结果总览风险明细组件与 API。 +- 文档:后端计划、前端计划、实施记录。 + +## 验证情况 + +- 后端执行 `mvn -pl ccdi-project -am test "-Dtest=CcdiProjectOverviewMapperSqlTest,CcdiProjectRiskDetailWorkbookExporterTest,CcdiProjectOverviewReportPdfExporterTest,CcdiProjectOverviewServiceImplTest" "-Dsurefire.failIfNoSpecifiedTests=false"`,32 个测试通过。 +- 前端使用 Node 22.22.3 / npm 10.9.8 执行 `node tests\unit\risk-detail-abnormal-account-layout.test.js`,静态断言通过。 +- 前端使用 Node 22.22.3 / npm 10.9.8 执行 `npm run build:prod`,构建通过;仍存在项目既有资源体积告警。 diff --git a/ruoyi-ui/src/api/ccdi/projectOverview.js b/ruoyi-ui/src/api/ccdi/projectOverview.js index 53a83367..ad5ffa59 100644 --- a/ruoyi-ui/src/api/ccdi/projectOverview.js +++ b/ruoyi-ui/src/api/ccdi/projectOverview.js @@ -144,6 +144,14 @@ export function getOverviewAbnormalAccountPeople(params) { }) } +export function getOverviewUnsubmittedAccounts(projectId) { + return request({ + url: '/ccdi/project/overview/unsubmitted-accounts', + method: 'get', + params: { projectId } + }) +} + export function exportOverviewReport(projectId) { return request({ url: '/ccdi/project/overview/report/export', diff --git a/ruoyi-ui/src/views/ccdiProject/components/detail/RiskDetailSection.vue b/ruoyi-ui/src/views/ccdiProject/components/detail/RiskDetailSection.vue index 7ad4cfa0..559a0960 100644 --- a/ruoyi-ui/src/views/ccdiProject/components/detail/RiskDetailSection.vue +++ b/ruoyi-ui/src/views/ccdiProject/components/detail/RiskDetailSection.vue @@ -11,6 +11,93 @@ +
+
+
+
未提交账户
+
被调查主体未提交分析的同名账户,已排除银行贷款类账号
+
+ + 导出流水 + +
+ +
+ + +
+
+
@@ -369,6 +456,7 @@ import { getOverviewAbnormalAccountPeople, getOverviewEmployeeCreditNegative, getOverviewSuspiciousTransactions, + getOverviewUnsubmittedAccounts, } from "@/api/ccdi/projectOverview"; import { getBankStatementDetail } from "@/api/ccdiProjectBankStatement"; import { buildFlowEvidenceFingerprint, buildFlowEvidenceSnapshot } from "@/utils/ccdiEvidence"; @@ -559,6 +647,14 @@ export default { abnormalAccountPageSize: DEFAULT_RISK_DETAIL_PAGE_SIZE, abnormalAccountTotal: 0, abnormalAccountList: [], + unsubmittedAccountLoading: false, + unsubmittedAccountSummary: { + totalAmountCr: 0, + totalAmountDr: 0, + netAmount: 0, + }, + unsubmittedAccountTotal: 0, + unsubmittedAccountSubjects: [], projectId: null, statementDetailCache: {}, suspiciousRequestSeq: 0, @@ -603,6 +699,8 @@ export default { this.abnormalAccountPageSize = DEFAULT_RISK_DETAIL_PAGE_SIZE; this.abnormalAccountTotal = 0; this.abnormalAccountList = []; + this.unsubmittedAccountTotal = 0; + this.unsubmittedAccountSubjects = []; this.employeeCreditNegativeList = Array.isArray(value && value.employeeCreditNegativeList) ? value.employeeCreditNegativeList : []; @@ -622,6 +720,7 @@ export default { this.closeDetailDialog(); } this.loadAbnormalAccountPeople(); + this.loadUnsubmittedAccounts(); }, }, }, @@ -791,6 +890,44 @@ export default { this.abnormalAccountLoading = false; } }, + async loadUnsubmittedAccounts() { + if (!this.projectId) { + this.unsubmittedAccountSummary = { + totalAmountCr: 0, + totalAmountDr: 0, + netAmount: 0, + }; + this.unsubmittedAccountTotal = 0; + this.unsubmittedAccountSubjects = []; + this.unsubmittedAccountLoading = false; + return; + } + + this.unsubmittedAccountLoading = true; + try { + const response = await getOverviewUnsubmittedAccounts(this.projectId); + const data = (response && response.data) || {}; + this.unsubmittedAccountSummary = { + totalAmountCr: Number(data.totalAmountCr) || 0, + totalAmountDr: Number(data.totalAmountDr) || 0, + netAmount: Number(data.netAmount) || 0, + }; + this.unsubmittedAccountTotal = Number(data.accountCount) || 0; + this.unsubmittedAccountSubjects = Array.isArray(data.subjects) ? data.subjects : []; + } catch (error) { + this.unsubmittedAccountSummary = { + totalAmountCr: 0, + totalAmountDr: 0, + netAmount: 0, + }; + this.unsubmittedAccountTotal = 0; + this.unsubmittedAccountSubjects = []; + this.$message.error("加载未提交账户失败"); + console.error("加载未提交账户失败", error); + } finally { + this.unsubmittedAccountLoading = false; + } + }, async fetchStatementDetail(bankStatementId, silent, scope = {}) { if (!bankStatementId) { return createEmptyDetailData(); @@ -899,6 +1036,16 @@ export default { `风险明细_${this.projectId}_${new Date().getTime()}.xlsx` ); }, + handleUnsubmittedAccountExport() { + if (!this.projectId || this.unsubmittedAccountTotal <= 0) { + return; + } + this.download( + "ccdi/project/overview/unsubmitted-accounts/export", + { projectId: this.projectId }, + `未提交账户流水_${this.projectId}_${new Date().getTime()}.xlsx` + ); + }, formatRelatedStaff(row) { if (!row || !row.relatedStaffName) { return "-"; @@ -943,6 +1090,29 @@ export default { maximumFractionDigits: 2, }); }, + formatWanAmount(value) { + if (value === null || value === undefined || value === "") { + return "0.00 万元"; + } + const amount = Number(value); + if (Number.isNaN(amount)) { + return "0.00 万元"; + } + return `${(amount / 10000).toLocaleString("zh-CN", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} 万元`; + }, + maskAccountNo(value) { + if (value === null || value === undefined || value === "") { + return "-"; + } + const text = String(value); + if (text.length <= 10) { + return text; + } + return `${text.slice(0, 8)} ... ${text.slice(-6)}`; + }, formatSignedAmount(value) { if (value === null || value === undefined || value === "") { return "-"; @@ -1121,6 +1291,112 @@ export default { width: 240px; } +.unsubmitted-panel { + min-height: 120px; +} + +.unsubmitted-metrics { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 18px; + margin-bottom: 24px; +} + +.unsubmitted-metric { + min-width: 0; +} + +.metric-label { + color: #64748b; + font-size: 13px; + line-height: 20px; +} + +.metric-value { + margin-top: 6px; + color: #0f172a; + font-size: 24px; + line-height: 32px; + font-weight: 700; + overflow-wrap: anywhere; +} + +.unsubmitted-subject + .unsubmitted-subject { + margin-top: 22px; +} + +.unsubmitted-subject-name { + margin-bottom: 12px; + color: #111827; + font-size: 15px; + line-height: 22px; + font-weight: 700; +} + +.unsubmitted-account-grid { + display: grid; + grid-template-columns: repeat(4, minmax(220px, 1fr)); + gap: 16px; +} + +.unsubmitted-account-card { + min-width: 0; + min-height: 132px; + padding: 18px 20px; + border: 1px solid #e5e7eb; + background: #fff; +} + +.unsubmitted-account-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + min-height: 28px; +} + +.unsubmitted-account-no { + display: inline-flex; + align-items: center; + gap: 8px; + min-width: 0; + color: #111827; + font-size: 14px; + line-height: 22px; + font-weight: 600; +} + +.unsubmitted-account-no i { + flex: 0 0 auto; + color: #9ca3af; +} + +.unsubmitted-account-no span { + min-width: 0; + overflow-wrap: anywhere; +} + +.unsubmitted-card-metrics { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; + margin-top: 24px; +} + +.card-metric-label { + color: #64748b; + font-size: 13px; + line-height: 20px; +} + +.card-metric-value { + margin-top: 4px; + font-size: 16px; + line-height: 24px; + font-weight: 700; + overflow-wrap: anywhere; +} + .result-table { width: 100%; } @@ -1355,6 +1631,14 @@ export default { } @media (max-width: 992px) { + .unsubmitted-metrics { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .unsubmitted-account-grid { + grid-template-columns: repeat(2, minmax(220px, 1fr)); + } + .detail-overview-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 24px; @@ -1362,6 +1646,12 @@ export default { } @media (max-width: 768px) { + .unsubmitted-metrics, + .unsubmitted-account-grid, + .unsubmitted-card-metrics { + grid-template-columns: 1fr; + } + .suspicious-filter-form { align-items: stretch; } diff --git a/ruoyi-ui/tests/unit/risk-detail-abnormal-account-layout.test.js b/ruoyi-ui/tests/unit/risk-detail-abnormal-account-layout.test.js index 3e764832..3d9813ca 100644 --- a/ruoyi-ui/tests/unit/risk-detail-abnormal-account-layout.test.js +++ b/ruoyi-ui/tests/unit/risk-detail-abnormal-account-layout.test.js @@ -11,6 +11,14 @@ const source = fs.readFileSync( ); [ + "未提交账户", + "未提交账户数", + "流入总额", + "流出总额", + "流入/流出差额", + "交易占比较高", + "导出流水", + "当前项目暂无未提交账户", "异常账户人员信息", "账号", "开户人",