94 Commits

Author SHA1 Message Date
wjj
0a0313af7c Merge remote-tracking branch 'origin/dev-ui' into dev-ui 2026-07-20 14:37:45 +08:00
wjj
175a50bf96 新增流水明细对手方汇总查询 2026-07-20 14:29:23 +08:00
wjj
7fec8e21be 新增未提交账户风险明细 2026-07-20 14:26:29 +08:00
wjj
75d094dad4 支持拉取行内和金综流水 2026-07-20 13:08:33 +08:00
wjj
17628486ae 支持拉取行内和金综流水 2026-07-20 13:07:49 +08:00
wjj
dd424cca50 新增结果总览未提交账户展示 2026-07-20 11:50:45 +08:00
wjj
1561df924f 修复流水明细导出权限校验 2026-07-20 11:26:58 +08:00
wjj
657977dfb7 完善异常账户预警口径和账户查询 2026-07-20 11:13:54 +08:00
wjj
4a75d68bf6 修复流水上传身份证号回填 2026-07-20 11:03:05 +08:00
wjj
b70f967153 调整涉疑交易默认每页十条 2026-07-16 19:05:34 +08:00
wjj
6492fca2dd 设计涉疑交易默认分页调整 2026-07-16 18:52:47 +08:00
wjj
e0188b5e5b 修复涉疑交易筛选与标签错乱 2026-07-16 18:46:51 +08:00
wjj
c3b56bdf2a 设计涉疑交易筛选一致性优化 2026-07-16 18:01:14 +08:00
wjj
6d90bb4e58 优化无外部人员涉疑交易查询 2026-07-16 15:26:14 +08:00
wjj
4a7cfde4af 调整专项排查资产收入匹配口径 2026-07-16 14:47:04 +08:00
wjj
15aa9117c3 调整员工信息维护列表和个人资料校验 2026-07-16 11:28:55 +08:00
wjj
ded07a2c33 优化前端页面布局细节 2026-07-13 16:46:37 +08:00
wjj
7a317204f7 调整流水打标规则口径 2026-07-13 13:05:35 +08:00
wjj
2c6c982ed3 账户库中介风险等级固定为高 2026-07-10 17:46:51 +08:00
wjj
486a499af7 优化流水账号入库和明细筛选 2026-07-10 17:33:16 +08:00
wjj
c999497693 优化外部人员风险明细模型筛选 2026-07-09 16:19:08 +08:00
wjj
3f3e9268f2 实现结果总览排除可疑预警 2026-07-09 14:49:02 +08:00
wjj
9c02812675 合并dev最新代码到dev-ui 2026-07-06 09:05:10 +08:00
wkc
b7e9a8da03 校验上传流水文件名身份证信息 2026-07-03 17:38:46 +08:00
wjj
bb41fd7e89 新增新增贷款查询和导出 2026-07-03 10:01:17 +08:00
wjj
8b9226643c 新增涉疑交易明细筛选 2026-07-03 09:15:14 +08:00
wjj
6d2f40843a 调整项目列表入口和操作按钮 2026-07-02 17:48:08 +08:00
wkc
21c4e91f41 修复专项核查图谱项目ID传递 2026-07-02 17:04:22 +08:00
wkc
87fb6443e6 test 2026-07-02 16:48:17 +08:00
wkc
979ed9669f 调整测试目录忽略规则 2026-07-02 16:46:57 +08:00
wkc
57a33098c9 实现流程项目逻辑删除与恢复 2026-07-02 10:54:36 +08:00
wkc
2f53fc4d1e 用户角色选择改为下拉单选 2026-07-02 10:53:01 +08:00
wkc
64ddc362e3 新增项目管理员角色权限控制 2026-07-01 17:02:17 +08:00
wkc
6c70149e0c 项目上传删除暂不调用流水分析平台 2026-07-01 17:02:17 +08:00
wkc
2298f9e589 移除风险模型导出按钮 2026-07-01 17:02:17 +08:00
wkc
e08f437acc 为风险明细列表添加分页选择器 2026-07-01 17:00:11 +08:00
wjj
6134ec06af 放开涉疑交易流水级模型命中 2026-07-01 16:58:16 +08:00
wjj
dfa35ebd85 优化涉疑交易模型口径和报告展示 2026-07-01 16:58:16 +08:00
wkc
1f82c7e16e 优化资金图谱主题节点检索 2026-07-01 16:57:31 +08:00
wkc
6c206deb5f 实现结果总览详情资产和征信页签 2026-07-01 16:57:31 +08:00
wkc
2999d7cf7d 调整项目人数按流水证件号统计 2026-07-01 16:55:09 +08:00
wjj
f42981f153 放开涉疑交易流水级模型命中 2026-07-01 16:53:44 +08:00
wkc
67f206be1b Refactor project pages and update related docs 2026-07-01 16:47:26 +08:00
wjj
66d34c7ceb 优化涉疑交易模型口径和报告展示 2026-07-01 16:46:23 +08:00
wkc
2e30b2a877 修正双员工夫妻家庭专项核查口径 2026-07-01 16:37:00 +08:00
wkc
d2cdb0f3c9 修复风险总览无风险人员负数问题 2026-07-01 16:37:00 +08:00
wkc
aaec746181 优化资产估值万元展示 2026-07-01 16:37:00 +08:00
wkc
dcfd869d84 Implement credit parse result polling and sentinel handling 2026-07-01 16:37:00 +08:00
wkc
d284923c59 调整征信解析返回解析和日志 2026-07-01 16:37:00 +08:00
wkc
84b04e31ed Refactor credit parse to use remote HTML paths 2026-07-01 16:37:00 +08:00
wkc
507bc1c2d7 Remove obsolete code and documentation 2026-07-01 16:37:00 +08:00
wkc
64891a621d 移除前端默认登录凭据 2026-07-01 16:37:00 +08:00
wkc
606b4f836b 生产配置 2026-07-01 16:37:00 +08:00
wkc
33c1f9df90 新增生产统一部署脚本 2026-07-01 16:37:00 +08:00
wkc
bee1e800ce Fix PDF font loading for project overview reports 2026-07-01 16:37:00 +08:00
wkc
a191cc7ca3 新增专项排查图谱展示 2026-07-01 16:33:59 +08:00
wkc
7826645a90 新增专项排查图谱前端实施计划 2026-07-01 16:31:35 +08:00
wkc
9b11426617 新增专项排查图谱嵌入设计 2026-07-01 16:31:35 +08:00
wkc
1494491d89 展示员工亲属实体关联统信码 2026-07-01 16:31:35 +08:00
wkc
5509ece708 调整招聘信息毕业年月选择控件 2026-07-01 16:31:35 +08:00
wkc
02e45dadcc 完善招聘信息主键关联与工作经历维护 2026-07-01 16:31:35 +08:00
wkc
caeac0f115 招聘导入模板增加招聘类型下拉框 2026-07-01 16:31:35 +08:00
wkc
a4005d3e9f 修复导入模板格式和必填标记 2026-07-01 16:31:35 +08:00
wkc
f9e0a079f5 Update import templates and relation query fields 2026-07-01 16:31:35 +08:00
wkc
e00d60f5c1 回测四类导入自动补入实体库 2026-07-01 16:31:35 +08:00
wkc
87da17bccb 修复双Sheet资产单独导入任务ID 2026-07-01 16:31:35 +08:00
wkc
4a9c6ba64b 实现关联业务自动补入实体库 2026-07-01 16:31:35 +08:00
wkc
17eb11b3b8 uat配置更新 2026-07-01 16:31:35 +08:00
wkc
b740d4c100 移除.DS_Store跟踪 2026-07-01 16:31:35 +08:00
wkc
f5a98bbd7a 修订: 中介实体补入无需机构名称 2026-07-01 16:31:35 +08:00
wkc
987afc3863 调整信息维护页面并修复项目概览统计 2026-07-01 16:31:35 +08:00
wkc
051650370e 计划: 员工资产导入与实体库自动补入修复 2026-07-01 16:28:16 +08:00
wkc
c211413bd2 修订: 明确资产Sheet单独导入规则 2026-07-01 16:28:16 +08:00
wkc
bb9994a339 修订: 完善员工资产导入与实体库补入设计 2026-07-01 16:28:16 +08:00
wkc
daf463bbb4 设计: 员工资产导入与实体库自动补入修复 2026-07-01 16:28:16 +08:00
wkc
6f6f2d264c 统一项目分析弹窗圆角样式 2026-07-01 16:28:16 +08:00
wkc
266108ac06 修复流水上传原始文件名保持 2026-07-01 16:16:48 +08:00
wkc
5ff67442bb 修复流水分析上传文件名传递 2026-07-01 16:16:48 +08:00
wkc
b7bb96b43b 设计: 保持上传流水原始文件名 2026-07-01 16:16:48 +08:00
wkc
9902430350 Add import dropdown validation 2026-07-01 16:16:48 +08:00
wkc
89773caa7b 新增导入下拉框校验实施计划 2026-07-01 16:16:48 +08:00
wkc
def656d57b 完善导入下拉框校验设计 2026-07-01 16:16:48 +08:00
wkc
6b6fae4b92 新增导入下拉框校验设计文档 2026-07-01 16:16:48 +08:00
wkc
0dc3b6ee2f uat配置文件 2026-07-01 16:16:47 +08:00
wjj
04ede64767 优化项目分析详情与异常明细展示 2026-07-01 11:26:01 +08:00
wjj
066d850389 清洗流水本方证件号格式 2026-06-30 13:40:24 +08:00
wjj
5e4bfca05b 完善外部人员预警与项目分析上线内容 2026-06-30 13:26:29 +08:00
wjj
4e90e22ee2 优化项目详情资金流向图谱展示 2026-06-04 18:01:55 +08:00
wjj
a5eba12ed5 调整流水模型理财剔除与提现规则 2026-06-04 15:55:25 +08:00
wjj
aef6c43181 放开涉疑交易流水级模型命中 2026-06-01 16:44:51 +08:00
wjj
4c2ea9d8e6 修正短时间多次存现本人口径 2026-06-01 15:50:25 +08:00
wjj
26cd049991 新增图谱功能及验收清单 2026-05-29 18:33:26 +08:00
wjj
180a892275 优化涉疑交易模型口径和报告展示 2026-05-26 16:43:39 +08:00
wjj
ef8147892e 补充项目分析个人详情页正式化样式 2026-05-06 16:18:03 +08:00
311 changed files with 18239 additions and 1244 deletions

4
.gitignore vendored
View File

@@ -86,12 +86,8 @@ ruoyi-ui/vue.config.js
ruoyi-ui/dist.zip ruoyi-ui/dist.zip
*/src/test/
.pytest_cache/ .pytest_cache/
tests/
tongweb_62318.properties tongweb_62318.properties
.superpowers/ .superpowers/

View File

@@ -39,6 +39,10 @@ public class CcdiAccountInfoQueryDTO implements Serializable {
@Schema(description = "账户姓名") @Schema(description = "账户姓名")
private String accountName; private String accountName;
/** 账户号码 */
@Schema(description = "账户号码")
private String accountNo;
/** 账户类型 */ /** 账户类型 */
@Schema(description = "账户类型") @Schema(description = "账户类型")
private String accountType; private String accountType;

View File

@@ -28,6 +28,14 @@ public interface CcdiBaseStaffMapper extends BaseMapper<CcdiBaseStaff> {
Page<CcdiBaseStaffVO> selectBaseStaffPageWithDept(@Param("page") Page<CcdiBaseStaffVO> page, Page<CcdiBaseStaffVO> selectBaseStaffPageWithDept(@Param("page") Page<CcdiBaseStaffVO> page,
@Param("query") CcdiBaseStaffQueryDTO queryDTO); @Param("query") CcdiBaseStaffQueryDTO queryDTO);
/**
* 查询员工详情(包含部门名称)
*
* @param staffId 员工ID
* @return 员工详情
*/
CcdiBaseStaffVO selectBaseStaffByIdWithDept(@Param("staffId") Long staffId);
int insertOrUpdateBatch(@Param("list") List<CcdiBaseStaff> list); int insertOrUpdateBatch(@Param("list") List<CcdiBaseStaff> list);
/** /**

View File

@@ -49,6 +49,10 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
private static final Set<String> ACCOUNT_TYPES = Set.of("BANK", "SECURITIES", "PAYMENT", "OTHER"); private static final Set<String> ACCOUNT_TYPES = Set.of("BANK", "SECURITIES", "PAYMENT", "OTHER");
private static final Set<String> BANK_SCOPES = Set.of("INTERNAL", "EXTERNAL"); private static final Set<String> BANK_SCOPES = Set.of("INTERNAL", "EXTERNAL");
private static final Set<String> LEVELS = Set.of("LOW", "MEDIUM", "HIGH"); private static final Set<String> LEVELS = Set.of("LOW", "MEDIUM", "HIGH");
private static final String OWNER_TYPE_INTERMEDIARY = "INTERMEDIARY";
private static final String BANK_SCOPE_EXTERNAL = "EXTERNAL";
private static final String RISK_LEVEL_LOW = "LOW";
private static final String RISK_LEVEL_HIGH = "HIGH";
private final List<AccountInfoImportFailureVO> latestImportFailures = new CopyOnWriteArrayList<>(); private final List<AccountInfoImportFailureVO> latestImportFailures = new CopyOnWriteArrayList<>();
@Resource @Resource
@@ -195,12 +199,12 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
private void validateOwner(String ownerType, String ownerId) { private void validateOwner(String ownerType, String ownerId) {
if (StringUtils.isEmpty(ownerId)) { if (StringUtils.isEmpty(ownerId)) {
if ("EXTERNAL".equals(ownerType) || "INTERMEDIARY".equals(ownerType)) { if ("EXTERNAL".equals(ownerType) || OWNER_TYPE_INTERMEDIARY.equals(ownerType)) {
throw new RuntimeException("证件号不能为空"); throw new RuntimeException("证件号不能为空");
} }
throw new RuntimeException("所属人不能为空"); throw new RuntimeException("所属人不能为空");
} }
if ("EXTERNAL".equals(ownerType) || "INTERMEDIARY".equals(ownerType)) { if ("EXTERNAL".equals(ownerType) || OWNER_TYPE_INTERMEDIARY.equals(ownerType)) {
return; return;
} }
if ("EMPLOYEE".equals(ownerType)) { if ("EMPLOYEE".equals(ownerType)) {
@@ -232,8 +236,9 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
} }
private void prepareAnalysisFields(CcdiAccountInfo accountInfo) { private void prepareAnalysisFields(CcdiAccountInfo accountInfo) {
if (!"EXTERNAL".equals(accountInfo.getBankScope())) { if (!BANK_SCOPE_EXTERNAL.equals(accountInfo.getBankScope())) {
clearAnalysisFields(accountInfo); clearAnalysisFields(accountInfo);
applyIntermediaryRiskLevel(accountInfo);
return; return;
} }
if (accountInfo.getIsActualControl() == null) { if (accountInfo.getIsActualControl() == null) {
@@ -249,8 +254,9 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
accountInfo.setTxnFrequencyLevel("MEDIUM"); accountInfo.setTxnFrequencyLevel("MEDIUM");
} }
if (StringUtils.isEmpty(accountInfo.getTxnRiskLevel())) { if (StringUtils.isEmpty(accountInfo.getTxnRiskLevel())) {
accountInfo.setTxnRiskLevel("LOW"); accountInfo.setTxnRiskLevel(RISK_LEVEL_LOW);
} }
applyIntermediaryRiskLevel(accountInfo);
} }
private void clearAnalysisFields(CcdiAccountInfo accountInfo) { private void clearAnalysisFields(CcdiAccountInfo accountInfo) {
@@ -265,6 +271,12 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
accountInfo.setTxnRiskLevel(null); accountInfo.setTxnRiskLevel(null);
} }
private void applyIntermediaryRiskLevel(CcdiAccountInfo accountInfo) {
if (OWNER_TYPE_INTERMEDIARY.equals(accountInfo.getOwnerType())) {
accountInfo.setTxnRiskLevel(RISK_LEVEL_HIGH);
}
}
private void validateAmount(BigDecimal amount, String fieldLabel) { private void validateAmount(BigDecimal amount, String fieldLabel) {
if (amount == null) { if (amount == null) {
return; return;
@@ -285,6 +297,7 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
addDTO.setTxnFrequencyLevel(toUpper(addDTO.getTxnFrequencyLevel())); addDTO.setTxnFrequencyLevel(toUpper(addDTO.getTxnFrequencyLevel()));
addDTO.setTxnRiskLevel(toUpper(addDTO.getTxnRiskLevel())); addDTO.setTxnRiskLevel(toUpper(addDTO.getTxnRiskLevel()));
addDTO.setOwnerId(normalizeOwnerId(addDTO.getOwnerId())); addDTO.setOwnerId(normalizeOwnerId(addDTO.getOwnerId()));
applyIntermediaryRiskLevel(addDTO);
} }
private void normalizeEditDto(CcdiAccountInfoEditDTO editDTO) { private void normalizeEditDto(CcdiAccountInfoEditDTO editDTO) {
@@ -295,6 +308,19 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
editDTO.setTxnFrequencyLevel(toUpper(editDTO.getTxnFrequencyLevel())); editDTO.setTxnFrequencyLevel(toUpper(editDTO.getTxnFrequencyLevel()));
editDTO.setTxnRiskLevel(toUpper(editDTO.getTxnRiskLevel())); editDTO.setTxnRiskLevel(toUpper(editDTO.getTxnRiskLevel()));
editDTO.setOwnerId(normalizeOwnerId(editDTO.getOwnerId())); editDTO.setOwnerId(normalizeOwnerId(editDTO.getOwnerId()));
applyIntermediaryRiskLevel(editDTO);
}
private void applyIntermediaryRiskLevel(CcdiAccountInfoAddDTO addDTO) {
if (OWNER_TYPE_INTERMEDIARY.equals(addDTO.getOwnerType())) {
addDTO.setTxnRiskLevel(RISK_LEVEL_HIGH);
}
}
private void applyIntermediaryRiskLevel(CcdiAccountInfoEditDTO editDTO) {
if (OWNER_TYPE_INTERMEDIARY.equals(editDTO.getOwnerType())) {
editDTO.setTxnRiskLevel(RISK_LEVEL_HIGH);
}
} }
private String normalizeCurrency(String currency) { private String normalizeCurrency(String currency) {
@@ -362,7 +388,8 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
private CcdiAccountInfoAddDTO toAddDto(CcdiAccountInfoExcel excel) { private CcdiAccountInfoAddDTO toAddDto(CcdiAccountInfoExcel excel) {
CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO(); CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO();
dto.setOwnerType(parseOwnerType(excel.getOwnerType())); String ownerType = parseOwnerType(excel.getOwnerType());
dto.setOwnerType(ownerType);
dto.setOwnerId(normalizeOwnerId(excel.getOwnerId())); dto.setOwnerId(normalizeOwnerId(excel.getOwnerId()));
dto.setAccountName(trimToNull(excel.getAccountName())); dto.setAccountName(trimToNull(excel.getAccountName()));
dto.setAccountNo(trimToNull(excel.getAccountNo())); dto.setAccountNo(trimToNull(excel.getAccountNo()));
@@ -382,7 +409,9 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
dto.setCreditSingleMaxAmount(parseDecimal(excel.getCreditSingleMaxAmount())); dto.setCreditSingleMaxAmount(parseDecimal(excel.getCreditSingleMaxAmount()));
dto.setDebitDailyMaxAmount(parseDecimal(excel.getDebitDailyMaxAmount())); dto.setDebitDailyMaxAmount(parseDecimal(excel.getDebitDailyMaxAmount()));
dto.setCreditDailyMaxAmount(parseDecimal(excel.getCreditDailyMaxAmount())); dto.setCreditDailyMaxAmount(parseDecimal(excel.getCreditDailyMaxAmount()));
dto.setTxnRiskLevel(parseLevel(excel.getTxnRiskLevel(), "风险等级")); dto.setTxnRiskLevel(OWNER_TYPE_INTERMEDIARY.equals(ownerType)
? RISK_LEVEL_HIGH
: parseLevel(excel.getTxnRiskLevel(), "风险等级"));
return dto; return dto;
} }
@@ -418,7 +447,7 @@ public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
return "RELATION"; return "RELATION";
} }
if ("中介".equals(value)) { if ("中介".equals(value)) {
return "INTERMEDIARY"; return OWNER_TYPE_INTERMEDIARY;
} }
if ("外部人员".equals(value)) { if ("外部人员".equals(value)) {
return "EXTERNAL"; return "EXTERNAL";

View File

@@ -119,10 +119,10 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
*/ */
@Override @Override
public CcdiBaseStaffVO selectBaseStaffById(Long staffId) { public CcdiBaseStaffVO selectBaseStaffById(Long staffId) {
CcdiBaseStaff staff = baseStaffMapper.selectById(staffId); CcdiBaseStaffVO vo = baseStaffMapper.selectBaseStaffByIdWithDept(staffId);
CcdiBaseStaffVO vo = convertToVO(staff); if (vo != null) {
if (staff != null) { vo.setStatusDesc(EmployeeStatus.getDescByCode(vo.getStatus()));
vo.setAssetInfoList(assetInfoService.selectByFamilyIdAndPersonId(staff.getIdCard(), staff.getIdCard()).stream().map(asset -> { vo.setAssetInfoList(assetInfoService.selectByFamilyIdAndPersonId(vo.getIdCard(), vo.getIdCard()).stream().map(asset -> {
CcdiAssetInfoVO assetInfoVO = new CcdiAssetInfoVO(); CcdiAssetInfoVO assetInfoVO = new CcdiAssetInfoVO();
BeanUtils.copyProperties(asset, assetInfoVO); BeanUtils.copyProperties(asset, assetInfoVO);
return assetInfoVO; return assetInfoVO;

View File

@@ -104,6 +104,9 @@
<if test="query.accountName != null and query.accountName != ''"> <if test="query.accountName != null and query.accountName != ''">
AND ai.account_name LIKE CONCAT('%', #{query.accountName}, '%') AND ai.account_name LIKE CONCAT('%', #{query.accountName}, '%')
</if> </if>
<if test="query.accountNo != null and query.accountNo != ''">
AND ai.account_no LIKE CONCAT('%', #{query.accountNo}, '%')
</if>
<if test="query.accountType != null and query.accountType != ''"> <if test="query.accountType != null and query.accountType != ''">
AND ai.account_type = #{query.accountType} AND ai.account_type = #{query.accountType}
</if> </if>

View File

@@ -42,7 +42,16 @@
AND e.status = #{query.status} AND e.status = #{query.status}
</if> </if>
</where> </where>
ORDER BY e.create_time DESC, e.staff_id DESC ORDER BY e.hire_date DESC, e.staff_id DESC
</select>
<select id="selectBaseStaffByIdWithDept" resultMap="CcdiBaseStaffVOResult">
SELECT
e.staff_id, e.name, e.dept_id, e.id_card, e.phone, e.annual_income, e.hire_date, e.is_party_member, e.status, e.create_time,
d.dept_name
FROM ccdi_base_staff e
LEFT JOIN sys_dept d ON e.dept_id = d.dept_id
WHERE e.staff_id = #{staffId}
</select> </select>
<!-- 批量插入或更新员工信息只更新非null字段 --> <!-- 批量插入或更新员工信息只更新非null字段 -->

View File

@@ -0,0 +1,147 @@
package com.ruoyi.info.collection.controller;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import com.ruoyi.info.collection.domain.vo.BaseStaffImportSubmitResultVO;
import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService;
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiBaseStaffControllerTest {
@InjectMocks
private CcdiBaseStaffController controller;
@Mock
private ICcdiBaseStaffService baseStaffService;
@Mock
private ICcdiBaseStaffImportService importAsyncService;
@Mock
private ICcdiBaseStaffAssetImportService baseStaffAssetImportService;
@Test
void importTemplate_shouldDownloadDualSheetTemplate() {
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
controller.importTemplate(null);
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(
null,
CcdiBaseStaffExcel.class,
"员工信息",
CcdiBaseStaffAssetInfoExcel.class,
"员工资产信息",
"员工信息维护导入模板"
));
}
}
@Test
void importData_shouldWarnWhenBothSheetsAreEmpty() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"base-staff-empty.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"empty".getBytes(StandardCharsets.UTF_8)
);
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
.thenReturn(List.of());
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
.thenReturn(List.of());
AjaxResult result = controller.importData(file);
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
assertEquals("至少需要一条数据", result.get(AjaxResult.MSG_TAG));
verifyNoInteractions(baseStaffService);
}
}
@Test
void importData_shouldSubmitOnlyStaffTaskWhenOnlyStaffSheetHasRows() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"base-staff.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"staff".getBytes(StandardCharsets.UTF_8)
);
CcdiBaseStaffExcel staffExcel = new CcdiBaseStaffExcel();
staffExcel.setStaffId(1001L);
BaseStaffImportSubmitResultVO submitResult = new BaseStaffImportSubmitResultVO();
submitResult.setStaffTaskId("staff-task-1");
when(baseStaffService.importBaseStaffWithAssets(List.of(staffExcel), List.of())).thenReturn(submitResult);
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
.thenReturn(List.of(staffExcel));
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
.thenReturn(List.of());
AjaxResult result = controller.importData(file);
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
assertEquals("导入任务已提交,正在后台处理", result.get(AjaxResult.MSG_TAG));
Object data = result.get(AjaxResult.DATA_TAG);
assertEquals("staff-task-1", data.getClass().getMethod("getStaffTaskId").invoke(data));
assertNull(data.getClass().getMethod("getAssetTaskId").invoke(data));
}
}
@Test
void importData_shouldSubmitTwoTasksWhenBothSheetsHaveRows() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"base-staff-both.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"both".getBytes(StandardCharsets.UTF_8)
);
CcdiBaseStaffExcel staffExcel = new CcdiBaseStaffExcel();
staffExcel.setStaffId(1002L);
CcdiBaseStaffAssetInfoExcel assetExcel = new CcdiBaseStaffAssetInfoExcel();
assetExcel.setPersonId("320101199001010011");
BaseStaffImportSubmitResultVO submitResult = new BaseStaffImportSubmitResultVO();
submitResult.setStaffTaskId("staff-task-2");
submitResult.setAssetTaskId("asset-task-2");
when(baseStaffService.importBaseStaffWithAssets(List.of(staffExcel), List.of(assetExcel))).thenReturn(submitResult);
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
.thenReturn(List.of(staffExcel));
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
.thenReturn(List.of(assetExcel));
AjaxResult result = controller.importData(file);
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
Object data = result.get(AjaxResult.DATA_TAG);
assertEquals("staff-task-2", data.getClass().getMethod("getStaffTaskId").invoke(data));
assertEquals("asset-task-2", data.getClass().getMethod("getAssetTaskId").invoke(data));
}
}
}

View File

@@ -0,0 +1,37 @@
package com.ruoyi.info.collection.controller;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.info.collection.domain.vo.EnumOptionVO;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
class CcdiEnumControllerTest {
private final CcdiEnumController controller = new CcdiEnumController();
@Test
void getEnterpriseRiskLevelOptions_shouldReturnConfiguredOptions() {
AjaxResult result = controller.getEnterpriseRiskLevelOptions();
List<?> data = (List<?>) result.get("data");
assertEquals(3, data.size());
EnumOptionVO first = (EnumOptionVO) data.get(0);
assertEquals("1", first.getValue());
assertEquals("高风险", first.getLabel());
}
@Test
void getEnterpriseSourceOptions_shouldReturnConfiguredOptions() {
AjaxResult result = controller.getEnterpriseSourceOptions();
List<?> data = (List<?>) result.get("data");
assertFalse(data.isEmpty());
EnumOptionVO first = (EnumOptionVO) data.get(0);
assertEquals("GENERAL", first.getValue());
assertEquals("一般企业", first.getLabel());
}
}

View File

@@ -0,0 +1,173 @@
package com.ruoyi.info.collection.controller;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportSubmitResultVO;
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
import com.ruoyi.info.collection.service.ICcdiAssetInfoImportService;
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiStaffFmyRelationControllerTest {
@InjectMocks
private CcdiStaffFmyRelationController controller;
@Mock
private ICcdiStaffFmyRelationService relationService;
@Mock
private ICcdiStaffFmyRelationImportService relationImportService;
@Mock
private ICcdiAssetInfoImportService assetInfoImportService;
@Test
void importTemplate_shouldDownloadDualSheetTemplate() {
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
controller.importTemplate(null);
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(
null,
CcdiStaffFmyRelationExcel.class,
"员工亲属关系信息",
CcdiAssetInfoExcel.class,
"亲属资产信息",
"员工亲属关系维护导入模板"
));
}
}
@Test
void importData_shouldErrorWhenBothSheetsAreEmpty() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"staff-family-empty.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"empty".getBytes(StandardCharsets.UTF_8)
);
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
.thenReturn(List.of());
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
.thenReturn(List.of());
AjaxResult result = controller.importData(file);
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
assertEquals("至少需要一条数据", result.get(AjaxResult.MSG_TAG));
verifyNoInteractions(relationService, assetInfoImportService);
}
}
@Test
void importData_shouldSubmitOnlyRelationTaskWhenOnlyRelationSheetHasRows() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"staff-family-relation.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"relation".getBytes(StandardCharsets.UTF_8)
);
CcdiStaffFmyRelationExcel relationExcel = new CcdiStaffFmyRelationExcel();
relationExcel.setPersonId("320101199001010011");
StaffFmyRelationImportSubmitResultVO submitResult = new StaffFmyRelationImportSubmitResultVO();
submitResult.setRelationTaskId("relation-task-1");
when(relationService.importRelationWithAssets(List.of(relationExcel), List.of())).thenReturn(submitResult);
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
.thenReturn(List.of(relationExcel));
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
.thenReturn(List.of());
AjaxResult result = controller.importData(file);
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
assertEquals("导入任务已提交,正在后台处理", result.get(AjaxResult.MSG_TAG));
Object data = result.get(AjaxResult.DATA_TAG);
assertEquals("relation-task-1", data.getClass().getMethod("getRelationTaskId").invoke(data));
assertNull(data.getClass().getMethod("getAssetTaskId").invoke(data));
}
}
@Test
void importData_shouldSubmitTwoTasksWhenBothSheetsHaveRows() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"staff-family-both.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"both".getBytes(StandardCharsets.UTF_8)
);
CcdiStaffFmyRelationExcel relationExcel = new CcdiStaffFmyRelationExcel();
relationExcel.setPersonId("320101199001010012");
CcdiAssetInfoExcel assetExcel = new CcdiAssetInfoExcel();
assetExcel.setPersonId("320101199001010099");
StaffFmyRelationImportSubmitResultVO submitResult = new StaffFmyRelationImportSubmitResultVO();
submitResult.setRelationTaskId("relation-task-2");
submitResult.setAssetTaskId("asset-task-2");
when(relationService.importRelationWithAssets(List.of(relationExcel), List.of(assetExcel))).thenReturn(submitResult);
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
.thenReturn(List.of(relationExcel));
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
.thenReturn(List.of(assetExcel));
AjaxResult result = controller.importData(file);
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
Object data = result.get(AjaxResult.DATA_TAG);
assertEquals("relation-task-2", data.getClass().getMethod("getRelationTaskId").invoke(data));
assertEquals("asset-task-2", data.getClass().getMethod("getAssetTaskId").invoke(data));
}
}
@Test
void getImportFailures_shouldReturnPagedRowsWithSheetAndRowInfo() {
StaffFmyRelationImportFailureVO failure1 = new StaffFmyRelationImportFailureVO();
failure1.setSheetName("员工亲属关系信息");
failure1.setRowNum(2);
failure1.setPersonId("A1");
StaffFmyRelationImportFailureVO failure2 = new StaffFmyRelationImportFailureVO();
failure2.setSheetName("员工亲属关系信息");
failure2.setRowNum(3);
failure2.setPersonId("A2");
when(relationImportService.getImportFailures("task-1")).thenReturn(List.of(failure1, failure2));
TableDataInfo result = controller.getImportFailures("task-1", 2, 1);
assertEquals(2, result.getTotal());
assertEquals(1, result.getRows().size());
StaffFmyRelationImportFailureVO row = (StaffFmyRelationImportFailureVO) result.getRows().get(0);
assertEquals("员工亲属关系信息", row.getSheetName());
assertEquals(3, row.getRowNum());
assertEquals("A2", row.getPersonId());
}
}

View File

@@ -38,6 +38,18 @@ class CcdiAccountInfoMapperTest {
assertTrue(sql.contains("ai.owner_type <> 'credit_customer'"), sql); assertTrue(sql.contains("ai.owner_type <> 'credit_customer'"), sql);
} }
@Test
void selectAccountInfoPage_shouldFilterByAccountNo() throws Exception {
MappedStatement mappedStatement = loadMappedStatement(
"com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper.selectAccountInfoPage");
CcdiAccountInfoQueryDTO queryDTO = new CcdiAccountInfoQueryDTO();
queryDTO.setAccountNo("6222");
String sql = renderSql(mappedStatement, Map.of("query", queryDTO)).toLowerCase();
assertTrue(sql.contains("ai.account_no like concat('%', ?, '%')"), sql);
}
private MappedStatement loadMappedStatement(String statementId) throws Exception { private MappedStatement loadMappedStatement(String statementId) throws Exception {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource())); configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));

View File

@@ -0,0 +1,25 @@
package com.ruoyi.info.collection.mapper;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CcdiEnterpriseBaseInfoMapperTest {
@Test
void mapperXml_shouldContainPageQueryAndImportColumns() throws Exception {
try (InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("mapper/info/collection/CcdiEnterpriseBaseInfoMapper.xml")) {
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
assertTrue(xml.contains("selectEnterpriseBaseInfoPage"), xml);
assertTrue(xml.contains("risk_level"), xml);
assertTrue(xml.contains("ent_source"), xml);
assertTrue(xml.contains("data_source"), xml);
assertTrue(xml.contains("ORDER BY create_time DESC"), xml);
}
}
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.info.collection.mapper;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CcdiIntermediaryMapperTest {
@Test
void mapperXml_shouldContainThreeRecordTypesAndRelatedKeywordQuery() throws Exception {
try (InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("mapper/info/collection/CcdiIntermediaryMapper.xml")) {
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
assertTrue(xml.contains("INTERMEDIARY"), xml);
assertTrue(xml.contains("RELATIVE"), xml);
assertTrue(xml.contains("ENTERPRISE_RELATION"), xml);
assertTrue(xml.contains("relatedIntermediaryKeyword"), xml);
assertTrue(xml.contains("related_intermediary_name"), xml);
assertTrue(xml.contains("relation_text"), xml);
assertTrue(xml.contains("CAST('实体'"), xml);
}
}
}

View File

@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiAccountInfo; import com.ruoyi.info.collection.domain.CcdiAccountInfo;
import com.ruoyi.info.collection.domain.CcdiBaseStaff; import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiAccountInfoExcel;
import com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper; import com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper; import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper; import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
@@ -16,6 +17,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.BeanWrapperImpl;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List;
import java.util.Date; import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -109,6 +111,64 @@ class CcdiAccountInfoServiceImplTest {
assertNull(wrapper.getPropertyValue("txnRiskLevel")); assertNull(wrapper.getPropertyValue("txnRiskLevel"));
} }
@Test
void insertInternalIntermediaryAccount_shouldForceHighRiskLevelOnAccountInfo() {
CcdiAccountInfoAddDTO dto = buildBaseAddDto();
dto.setOwnerType("INTERMEDIARY");
dto.setOwnerId("91330100MA00000001");
dto.setBankScope("INTERNAL");
dto.setIsActualControl(1);
dto.setAvgMonthTxnCount(8);
dto.setAvgMonthTxnAmount(new BigDecimal("9988.66"));
dto.setTxnFrequencyLevel("HIGH");
dto.setDebitSingleMaxAmount(new BigDecimal("111.11"));
dto.setCreditSingleMaxAmount(new BigDecimal("222.22"));
dto.setDebitDailyMaxAmount(new BigDecimal("333.33"));
dto.setCreditDailyMaxAmount(new BigDecimal("444.44"));
dto.setTxnRiskLevel("LOW");
when(accountInfoMapper.selectCount(any())).thenReturn(0L);
when(accountInfoMapper.insert(any(CcdiAccountInfo.class))).thenReturn(1);
service.insertAccountInfo(dto);
ArgumentCaptor<CcdiAccountInfo> captor = ArgumentCaptor.forClass(CcdiAccountInfo.class);
verify(accountInfoMapper).insert(captor.capture());
BeanWrapperImpl wrapper = new BeanWrapperImpl(captor.getValue());
assertNull(wrapper.getPropertyValue("isActualControl"));
assertNull(wrapper.getPropertyValue("avgMonthTxnCount"));
assertNull(wrapper.getPropertyValue("avgMonthTxnAmount"));
assertNull(wrapper.getPropertyValue("txnFrequencyLevel"));
assertEquals("HIGH", wrapper.getPropertyValue("txnRiskLevel"));
}
@Test
void importExternalIntermediaryAccount_shouldForceHighRiskLevelOnAccountInfo() {
CcdiAccountInfoExcel excel = new CcdiAccountInfoExcel();
excel.setOwnerType("中介");
excel.setOwnerId("91330100MA00000002");
excel.setAccountName("测试中介");
excel.setAccountNo("6222024000000099");
excel.setAccountType("银行账户");
excel.setBankScope("行外");
excel.setOpenBank("中国银行");
excel.setBankCode("BOC");
excel.setCurrency("CNY");
excel.setStatus("正常");
excel.setEffectiveDate("2026-07-10");
excel.setTxnRiskLevel("LOW");
when(accountInfoMapper.selectOne(any())).thenReturn(null);
when(accountInfoMapper.selectCount(any())).thenReturn(0L);
when(accountInfoMapper.insert(any(CcdiAccountInfo.class))).thenReturn(1);
service.importAccountInfo(List.of(excel), false);
ArgumentCaptor<CcdiAccountInfo> captor = ArgumentCaptor.forClass(CcdiAccountInfo.class);
verify(accountInfoMapper).insert(captor.capture());
assertEquals("HIGH", captor.getValue().getTxnRiskLevel());
}
private CcdiAccountInfoAddDTO buildBaseAddDto() { private CcdiAccountInfoAddDTO buildBaseAddDto() {
CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO(); CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO();
dto.setAccountNo("6222024000000001"); dto.setAccountNo("6222024000000001");

View File

@@ -0,0 +1,163 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import com.ruoyi.info.collection.domain.vo.ImportFailureVO;
import com.ruoyi.info.collection.service.impl.CcdiBaseStaffImportServiceImpl;
import com.ruoyi.system.mapper.SysDeptMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiBaseStaffDualImportServiceTest {
@InjectMocks
private CcdiBaseStaffImportServiceImpl service;
@Mock
private com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper baseStaffMapper;
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Mock
private HashOperations<String, Object, Object> hashOperations;
@Mock
private ValueOperations<String, Object> valueOperations;
@Mock
private SysDeptMapper deptMapper;
@Test
void importBaseStaffAsync_shouldTreatExistingEmployeeAsFailureInsteadOfUpdate() {
CcdiBaseStaffExcel excel = new CcdiBaseStaffExcel();
excel.setStaffId(1001L);
excel.setName("张三");
excel.setDeptId(10L);
excel.setIdCard("11010519491231002X");
excel.setPhone("13812345678");
excel.setStatus("0");
excel.setPartyMember(1);
CcdiBaseStaff existing = new CcdiBaseStaff();
existing.setStaffId(1001L);
existing.setIdCard("11010519491231002X");
when(baseStaffMapper.selectBatchIds(List.of(1001L))).thenReturn(List.of(existing));
when(baseStaffMapper.selectList(any())).thenReturn(List.of(existing));
lenient().when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
service.importBaseStaffAsync(List.of(excel), "task-existing");
verify(baseStaffMapper, never()).insertBatch(any());
verify(baseStaffMapper, never()).insertOrUpdateBatch(any());
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
verify(valueOperations).set(eq("import:baseStaff:task-existing:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
ImportFailureVO failure = (ImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
assertEquals("员工信息", failure.getSheetName());
assertEquals(2, failure.getRowNum());
assertEquals(1001L, failure.getStaffId());
assertEquals("该员工ID已存在", failure.getErrorMessage());
}
@Test
void validateStaffData_shouldRejectExistingIdCardWhenStaffIdDoesNotExist() {
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
RuntimeException exception = org.junit.jupiter.api.Assertions.assertThrows(
RuntimeException.class,
() -> service.validateStaffData(buildExcelDto(), Set.of(), Set.of("11010519491231002X"))
);
assertEquals("该身份证号已存在", exception.getMessage());
}
@Test
void importBaseStaffAsync_shouldSaveFailureWhenDeptIsInvalid() {
CcdiBaseStaffExcel validExcel = buildExcel(1001L, 10L, "11010519491231002X");
CcdiBaseStaffExcel invalidExcel = buildExcel(1002L, 99L, "320101199001010014");
when(baseStaffMapper.selectBatchIds(List.of(1001L, 1002L))).thenReturn(List.of());
when(baseStaffMapper.selectList(any())).thenReturn(List.of());
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
when(deptMapper.selectDeptById(99L)).thenReturn(null);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
service.importBaseStaffAsync(List.of(validExcel, invalidExcel), "task-invalid-dept");
verify(baseStaffMapper).insertBatch(any());
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
verify(valueOperations).set(eq("import:baseStaff:task-invalid-dept:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
ImportFailureVO failure = (ImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
assertEquals("员工信息", failure.getSheetName());
assertEquals(3, failure.getRowNum());
assertEquals(1002L, failure.getStaffId());
assertEquals("所属部门ID[99]不存在或已停用/删除,请检查机构号", failure.getErrorMessage());
ArgumentCaptor<Map<String, Object>> statusCaptor = ArgumentCaptor.forClass(Map.class);
verify(hashOperations).putAll(eq("import:baseStaff:task-invalid-dept"), statusCaptor.capture());
assertEquals("PARTIAL_SUCCESS", statusCaptor.getValue().get("status"));
assertEquals(1, statusCaptor.getValue().get("successCount"));
assertEquals(1, statusCaptor.getValue().get("failureCount"));
}
private com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO buildExcelDto() {
com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO dto = new com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO();
dto.setName("李四");
dto.setStaffId(2001L);
dto.setDeptId(10L);
dto.setIdCard("11010519491231002X");
dto.setPhone("13812345678");
dto.setStatus("0");
dto.setPartyMember(1);
return dto;
}
private CcdiBaseStaffExcel buildExcel(Long staffId, Long deptId, String idCard) {
CcdiBaseStaffExcel excel = new CcdiBaseStaffExcel();
excel.setStaffId(staffId);
excel.setName("张三");
excel.setDeptId(deptId);
excel.setIdCard(idCard);
excel.setPhone("13812345678");
excel.setStatus("0");
excel.setPartyMember(1);
return excel;
}
private SysDept buildDept(Long deptId, String status, String delFlag) {
SysDept dept = new SysDept();
dept.setDeptId(deptId);
dept.setDeptName("测试部门");
dept.setStatus(status);
dept.setDelFlag(delFlag);
return dept;
}
}

View File

@@ -0,0 +1,113 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
import com.ruoyi.info.collection.domain.dto.CcdiIntermediaryRelativeAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiIntermediaryPersonAddDTO;
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryMapper;
import com.ruoyi.info.collection.service.impl.CcdiIntermediaryServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.RedisTemplate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiIntermediaryServiceImplTest {
@InjectMocks
private CcdiIntermediaryServiceImpl service;
@Mock
private CcdiBizIntermediaryMapper bizIntermediaryMapper;
@Mock
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
@Mock
private CcdiIntermediaryMapper intermediaryMapper;
@Mock
private CcdiIntermediaryEnterpriseRelationMapper enterpriseRelationMapper;
@Mock
private ICcdiIntermediaryPersonImportService personImportService;
@Mock
private ICcdiIntermediaryEntityImportService entityImportService;
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Test
void insertIntermediaryPerson_shouldForceBenrenAndClearRelatedNumId() {
CcdiIntermediaryPersonAddDTO addDTO = new CcdiIntermediaryPersonAddDTO();
addDTO.setName("测试中介");
addDTO.setPersonId("320101199001010011");
addDTO.setPersonSubType("配偶");
addDTO.setRelatedNumId("parent-id");
when(bizIntermediaryMapper.selectCount(any())).thenReturn(0L);
when(bizIntermediaryMapper.insert(any(CcdiBizIntermediary.class))).thenReturn(1);
int result = service.insertIntermediaryPerson(addDTO);
assertEquals(1, result);
ArgumentCaptor<CcdiBizIntermediary> captor = ArgumentCaptor.forClass(CcdiBizIntermediary.class);
verify(bizIntermediaryMapper).insert(captor.capture());
assertEquals("本人", captor.getValue().getPersonSubType());
assertNull(captor.getValue().getRelatedNumId());
assertEquals("MANUAL", captor.getValue().getDataSource());
}
@Test
void insertIntermediaryRelative_shouldRejectBenrenSubType() {
CcdiBizIntermediary owner = new CcdiBizIntermediary();
owner.setBizId("biz-1");
owner.setPersonSubType("本人");
CcdiIntermediaryRelativeAddDTO addDTO = new CcdiIntermediaryRelativeAddDTO();
addDTO.setName("测试亲属");
addDTO.setPersonId("320101199001010022");
addDTO.setPersonSubType("本人");
when(bizIntermediaryMapper.selectById("biz-1")).thenReturn(owner);
RuntimeException exception = assertThrows(RuntimeException.class,
() -> service.insertIntermediaryRelative("biz-1", addDTO));
assertEquals("亲属关系不能为本人", exception.getMessage());
verify(bizIntermediaryMapper, never()).insert(any(CcdiBizIntermediary.class));
}
@Test
void deleteIntermediaryByIds_shouldDeleteRelativesAndEnterpriseRelationsWhenRemovingOwner() {
CcdiBizIntermediary owner = new CcdiBizIntermediary();
owner.setBizId("biz-1");
owner.setPersonSubType("本人");
when(bizIntermediaryMapper.selectById("biz-1")).thenReturn(owner);
when(bizIntermediaryMapper.delete(any())).thenReturn(2);
when(enterpriseRelationMapper.delete(any())).thenReturn(1);
when(bizIntermediaryMapper.deleteById("biz-1")).thenReturn(1);
int result = service.deleteIntermediaryByIds(new String[]{"biz-1"});
assertEquals(1, result);
verify(bizIntermediaryMapper).delete(any());
verify(enterpriseRelationMapper).delete(any());
verify(bizIntermediaryMapper).deleteById("biz-1");
}
}

View File

@@ -0,0 +1,78 @@
package com.ruoyi.info.collection.service;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CcdiPurchaseTransactionFeatureContractTest {
@Test
void shouldExposeSupplierListContractsAcrossPurchaseTransactionModels() throws Exception {
assertHasField(
"com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO",
"supplierList"
);
assertHasField(
"com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO",
"supplierList"
);
assertHasField(
"com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO",
"supplierList"
);
assertHasField(
"com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO",
"supplierCount"
);
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier"));
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionSupplierDTO"));
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionSupplierVO"));
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel"));
}
@Test
void shouldDefineSupplierSubTableAndBiddingMigrationScripts() throws Exception {
String initSql = Files.readString(repoPath("sql/ccdi_purchase_transaction.sql"));
assertTrue(initSql.contains("CREATE TABLE `ccdi_purchase_transaction_supplier`"));
assertTrue(initSql.contains("`is_bid_winner`"));
assertTrue(initSql.contains("`sort_order`"));
assertTrue(initSql.contains("utf8mb4_general_ci"));
String menuSql = Files.readString(repoPath("sql/ccdi_purchase_transaction_menu.sql"));
assertTrue(menuSql.contains("招投标信息维护"));
Path migrationPath = repoPath("sql/migration/2026-04-22-bidding-info-maintenance-supplier-detail.sql");
assertTrue(Files.exists(migrationPath), "应提供招投标供应商明细迁移脚本");
String migrationSql = Files.readString(migrationPath);
assertTrue(migrationSql.contains("ccdi_purchase_transaction_supplier"));
assertTrue(migrationSql.contains("INSERT INTO ccdi_purchase_transaction_supplier"));
assertTrue(migrationSql.contains("UPDATE sys_menu"));
assertTrue(migrationSql.contains("招投标信息维护"));
}
@Test
void shouldUseTwoSheetTemplateForBiddingImport() throws Exception {
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel"));
String controller = Files.readString(
Path.of("src/main/java/com/ruoyi/info/collection/controller/CcdiPurchaseTransactionController.java")
);
assertTrue(controller.contains("招投标主信息"));
assertTrue(controller.contains("供应商明细"));
}
private void assertHasField(String className, String fieldName) throws Exception {
Class<?> clazz = Class.forName(className);
Field field = clazz.getDeclaredField(fieldName);
assertNotNull(field);
}
private Path repoPath(String relativePath) {
return Path.of("..", relativePath);
}
}

View File

@@ -0,0 +1,248 @@
package com.ruoyi.info.collection.utils;
import com.alibaba.excel.annotation.ExcelProperty;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
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.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class EasyExcelUtilImportDropdownValidationTest {
@Test
void importExcel_shouldPassWhenAllDictDropdownColumnsKeepListValidation() throws Exception {
byte[] bytes = baseStaffWorkbook(true, true, true, 2);
List<CcdiBaseStaffExcel> rows = EasyExcelUtil.importExcel(
new ByteArrayInputStream(bytes),
CcdiBaseStaffExcel.class,
"员工信息"
);
assertEquals(2, rows.size());
}
@Test
void importExcel_shouldFailWhenPartyMemberDropdownIsMissing() throws Exception {
byte[] bytes = baseStaffWorkbook(false, true, true, 2);
ServiceException exception = assertThrows(ServiceException.class, () ->
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
);
assertTrue(exception.getMessage().contains("是否党员 列缺少下拉框"));
}
@Test
void importExcel_shouldFailWhenStatusDropdownIsMissing() throws Exception {
byte[] bytes = baseStaffWorkbook(true, false, true, 2);
ServiceException exception = assertThrows(ServiceException.class, () ->
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
);
assertEquals("员工信息 Sheet 的 状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
}
@Test
void importExcel_shouldReportAllMissingDropdownColumnsInSameSheet() throws Exception {
byte[] bytes = baseStaffWorkbook(false, false, true, 2);
ServiceException exception = assertThrows(ServiceException.class, () ->
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
);
assertEquals("员工信息 Sheet 的 是否党员、状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
}
@Test
void importExcel_shouldFailWhenValidationIsNotListType() throws Exception {
byte[] bytes = baseStaffWorkbook(true, true, false, 2);
ServiceException exception = assertThrows(ServiceException.class, () ->
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
);
assertTrue(exception.getMessage().contains("状态 列缺少下拉框"));
}
@Test
void importExcel_shouldFailWhenListValidationDoesNotCoverEveryActualDataRow() throws Exception {
byte[] bytes = baseStaffWorkbook(true, true, true, 1);
ServiceException exception = assertThrows(ServiceException.class, () ->
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
);
assertTrue(exception.getMessage().contains("状态 列缺少下拉框"));
}
@Test
void importExcel_shouldFailWhenSecondSheetDropdownIsMissing() throws Exception {
byte[] bytes = baseStaffDualSheetWorkbookWithMissingAssetStatusDropdown();
ServiceException exception = assertThrows(ServiceException.class, () ->
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffAssetInfoExcel.class, "员工资产信息")
);
assertEquals("员工资产信息 Sheet 的 资产状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
}
@Test
void importExcel_shouldSkipDropdownStructureValidationWhenClassHasNoDictDropdownFields() throws Exception {
byte[] bytes = plainWorkbookWithoutDropdown();
List<PlainExcel> rows = EasyExcelUtil.importExcel(
new ByteArrayInputStream(bytes),
PlainExcel.class,
"普通信息"
);
assertEquals(1, rows.size());
}
private byte[] baseStaffWorkbook(boolean partyDropdown, boolean statusDropdown, boolean statusAsList,
int statusLastRow) throws Exception {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("员工信息");
Row header = sheet.createRow(0);
String[] headers = {"姓名", "员工ID", "所属部门ID", "身份证号", "电话", "年收入(元/年)",
"入职时间", "是否党员", "状态"};
for (int i = 0; i < headers.length; i++) {
header.createCell(i).setCellValue(headers[i]);
}
createBaseStaffRow(sheet, 1, "张三", 9020001L, "33010619850202101X", "0", "1");
createBaseStaffRow(sheet, 2, "李四", 9020002L, "330106198603031022", "1", "1");
if (partyDropdown) {
addListValidation(sheet, 7, 1, 2, "0", "1");
}
if (statusDropdown) {
if (statusAsList) {
addListValidation(sheet, 8, 1, statusLastRow, "0", "1");
} else {
addIntegerValidation(sheet, 8, 1, 2);
}
}
workbook.write(outputStream);
return outputStream.toByteArray();
}
}
private byte[] baseStaffDualSheetWorkbookWithMissingAssetStatusDropdown() throws Exception {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Sheet staffSheet = workbook.createSheet("员工信息");
Row staffHeader = staffSheet.createRow(0);
String[] staffHeaders = {"姓名", "员工ID", "所属部门ID", "身份证号", "电话", "年收入(元/年)",
"入职时间", "是否党员", "状态"};
for (int i = 0; i < staffHeaders.length; i++) {
staffHeader.createCell(i).setCellValue(staffHeaders[i]);
}
createBaseStaffRow(staffSheet, 1, "张三", 9020001L, "33010619850202101X", "0", "1");
addListValidation(staffSheet, 7, 1, 1, "0", "1");
addListValidation(staffSheet, 8, 1, 1, "0", "1");
Sheet assetSheet = workbook.createSheet("员工资产信息");
Row assetHeader = assetSheet.createRow(0);
String[] assetHeaders = {"员工身份证号*", "资产大类*", "资产小类*", "资产名称*", "产权占比",
"购买/评估日期", "资产原值", "当前估值*", "估值截止日期", "资产状态*", "备注"};
for (int i = 0; i < assetHeaders.length; i++) {
assetHeader.createCell(i).setCellValue(assetHeaders[i]);
}
Row assetRow = assetSheet.createRow(1);
assetRow.createCell(0).setCellValue("33010619850202101X");
assetRow.createCell(1).setCellValue("房产");
assetRow.createCell(2).setCellValue("住宅");
assetRow.createCell(3).setCellValue("测试住宅");
assetRow.createCell(7).setCellValue(1000000D);
assetRow.createCell(9).setCellValue("正常");
workbook.write(outputStream);
return outputStream.toByteArray();
}
}
private void createBaseStaffRow(Sheet sheet, int rowIndex, String name, long staffId, String idCard,
String partyMember, String status) {
Row row = sheet.createRow(rowIndex);
row.createCell(0).setCellValue(name);
row.createCell(1).setCellValue(staffId);
row.createCell(2).setCellValue(103L);
row.createCell(3, CellType.STRING).setCellValue(idCard);
row.createCell(4, CellType.STRING).setCellValue("13370000001");
row.createCell(5).setCellValue(new BigDecimal("180000").doubleValue());
row.createCell(6).setCellValue("2026-04-30");
row.createCell(7, CellType.STRING).setCellValue(partyMember);
row.createCell(8, CellType.STRING).setCellValue(status);
}
private void addListValidation(Sheet sheet, int columnIndex, int firstRow, int lastRow, String... options) {
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = helper.createExplicitListConstraint(options);
DataValidation validation = helper.createValidation(
constraint,
new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex)
);
sheet.addValidationData(validation);
}
private void addIntegerValidation(Sheet sheet, int columnIndex, int firstRow, int lastRow) {
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = helper.createIntegerConstraint(
DataValidationConstraint.OperatorType.BETWEEN,
"0",
"1"
);
DataValidation validation = helper.createValidation(
constraint,
new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex)
);
sheet.addValidationData(validation);
}
private byte[] plainWorkbookWithoutDropdown() throws Exception {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("普通信息");
Row header = sheet.createRow(0);
header.createCell(0).setCellValue("名称");
Row row = sheet.createRow(1);
row.createCell(0).setCellValue("张三");
workbook.write(outputStream);
return outputStream.toByteArray();
}
}
public static class PlainExcel {
@ExcelProperty(value = "名称", index = 0)
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -17,6 +17,7 @@ public class LsfxConstants {
/** 数据渠道编码 */ /** 数据渠道编码 */
public static final String DATA_CHANNEL_ZJRCU = "ZJRCU"; public static final String DATA_CHANNEL_ZJRCU = "ZJRCU";
public static final String DATA_CHANNEL_JZL = "JZL";
/** 分析类型 */ /** 分析类型 */
public static final String ANALYSIS_TYPE = "-1"; public static final String ANALYSIS_TYPE = "-1";

View File

@@ -14,7 +14,7 @@ public class FetchInnerFlowRequest {
/** 客户身份证号 */ /** 客户身份证号 */
private String customerNo; private String customerNo;
/** 数据渠道编码(固定值:ZJRCU) */ /** 数据渠道编码(ZJRCU-行内JZL-金综) */
private String dataChannelCode; private String dataChannelCode;
/** 发起请求的时间(格式:yyyyMMdd) */ /** 发起请求的时间(格式:yyyyMMdd) */

View File

@@ -51,7 +51,7 @@ public class GetBankStatementResponse {
private String leName; private String leName;
/** 企业银行账号 */ /** 企业银行账号 */
private String accountMaskNo; private String accountNo;
/** 账号日期ID */ /** 账号日期ID */
private Integer accountingDateId; private Integer accountingDateId;
@@ -102,7 +102,7 @@ public class GetBankStatementResponse {
private String customerName; private String customerName;
/** 对手方账号 */ /** 对手方账号 */
private String customerAccountMaskNo; private String customerAccountNo;
/** 对手方银行 */ /** 对手方银行 */
private String customerBank; private String customerBank;

View File

@@ -0,0 +1,61 @@
package com.ruoyi.lsfx.client;
import com.ruoyi.lsfx.constants.LsfxConstants;
import com.ruoyi.lsfx.domain.response.UploadFileResponse;
import com.ruoyi.lsfx.util.HttpUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.test.util.ReflectionTestUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class LsfxAnalysisClientTest {
@Mock
private HttpUtil httpUtil;
@InjectMocks
private LsfxAnalysisClient client;
@TempDir
Path tempDir;
@Test
void uploadFile_shouldPassOriginalFilenameToMultipartResource() throws Exception {
ReflectionTestUtils.setField(client, "baseUrl", "http://lsfx");
ReflectionTestUtils.setField(client, "uploadFileEndpoint", "/upload");
ReflectionTestUtils.setField(client, "clientId", "client-1");
Path tempFile = tempDir.resolve("batch_0_123456.xlsx");
Files.writeString(tempFile, "content");
UploadFileResponse response = new UploadFileResponse();
response.setData(new UploadFileResponse.UploadData());
ArgumentCaptor<Map<String, Object>> paramsCaptor = ArgumentCaptor.forClass(Map.class);
ArgumentCaptor<Map<String, String>> headersCaptor = ArgumentCaptor.forClass(Map.class);
when(httpUtil.uploadFile(eq("http://lsfx/upload"), paramsCaptor.capture(), headersCaptor.capture(), eq(UploadFileResponse.class)))
.thenReturn(response);
client.uploadFile(200, tempFile.toFile(), "银行流水A.xlsx");
assertEquals(200, paramsCaptor.getValue().get("groupId"));
Resource filePart = assertInstanceOf(Resource.class, paramsCaptor.getValue().get("files"));
assertEquals("银行流水A.xlsx", filePart.getFilename());
assertEquals("client-1", headersCaptor.getValue().get(LsfxConstants.HEADER_CLIENT_ID));
}
}

View File

@@ -0,0 +1,59 @@
package com.ruoyi.lsfx.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HttpUtilTest {
@Mock
private RestTemplate restTemplate;
@TempDir
Path tempDir;
@Test
void uploadFile_shouldUseExplicitResourceFilename() throws Exception {
HttpUtil httpUtil = new HttpUtil();
ReflectionTestUtils.setField(httpUtil, "restTemplate", restTemplate);
Path tempFile = tempDir.resolve("batch_0_123456.xlsx");
Files.writeString(tempFile, "content");
ArgumentCaptor<HttpEntity> captor = ArgumentCaptor.forClass(HttpEntity.class);
when(restTemplate.postForEntity(eq("http://lsfx/upload"), captor.capture(), eq(String.class)))
.thenReturn(ResponseEntity.ok("ok"));
Map<String, Object> params = new HashMap<>();
params.put("groupId", 200);
params.put("files", HttpUtil.namedFileResource(tempFile.toFile(), "银行流水A.xlsx"));
String result = httpUtil.uploadFile("http://lsfx/upload", params, null, String.class);
assertEquals("ok", result);
MultiValueMap<String, Object> body = (MultiValueMap<String, Object>) captor.getValue().getBody();
Object filePart = body.getFirst("files");
Resource resource = assertInstanceOf(Resource.class, filePart);
assertEquals("银行流水A.xlsx", resource.getFilename());
}
}

View File

@@ -0,0 +1 @@
mock-maker-subclass

View File

@@ -10,6 +10,7 @@ public final class CcdiProjectStatusConstants {
public static final String ARCHIVED = "2"; public static final String ARCHIVED = "2";
public static final String TAGGING = "3"; public static final String TAGGING = "3";
public static final String TAG_FAILED = "4"; public static final String TAG_FAILED = "4";
public static final String DELETED = "5";
private CcdiProjectStatusConstants() { private CcdiProjectStatusConstants() {
} }

View File

@@ -3,9 +3,11 @@ package com.ruoyi.ccdi.project.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementCounterpartyVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiBankStatementService; import com.ruoyi.ccdi.project.service.ICcdiBankStatementService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -17,7 +19,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -39,26 +40,42 @@ public class CcdiBankStatementController extends BaseController {
@Resource @Resource
private ICcdiBankStatementService bankStatementService; private ICcdiBankStatementService bankStatementService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 分页查询流水明细 * 分页查询流水明细
*/ */
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "分页查询流水明细") @Operation(summary = "分页查询流水明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public TableDataInfo list(CcdiBankStatementQueryDTO queryDTO) { public TableDataInfo list(CcdiBankStatementQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
PageDomain pageDomain = TableSupport.buildPageRequest(); PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiBankStatementListVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); Page<CcdiBankStatementListVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiBankStatementListVO> result = bankStatementService.selectStatementPage(page, queryDTO); Page<CcdiBankStatementListVO> result = bankStatementService.selectStatementPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal()); return getDataTable(result.getRecords(), result.getTotal());
} }
/**
* 分页查询对手方聚合列表
*/
@GetMapping("/counterparty/list")
@Operation(summary = "分页查询对手方聚合列表")
public TableDataInfo counterpartyList(CcdiBankStatementQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiBankStatementCounterpartyVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiBankStatementCounterpartyVO> result = bankStatementService.selectCounterpartyPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
/** /**
* 查询项目级筛选项 * 查询项目级筛选项
*/ */
@GetMapping("/options") @GetMapping("/options")
@Operation(summary = "查询项目级筛选项") @Operation(summary = "查询项目级筛选项")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getOptions(Long projectId) { public AjaxResult getOptions(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiBankStatementFilterOptionsVO options = bankStatementService.getFilterOptions(projectId); CcdiBankStatementFilterOptionsVO options = bankStatementService.getFilterOptions(projectId);
return AjaxResult.success(options); return AjaxResult.success(options);
} }
@@ -68,9 +85,13 @@ public class CcdiBankStatementController extends BaseController {
*/ */
@GetMapping("/detail/{bankStatementId}") @GetMapping("/detail/{bankStatementId}")
@Operation(summary = "查询流水详情") @Operation(summary = "查询流水详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") public AjaxResult getDetail(@PathVariable Long bankStatementId, String modelCode, String suspiciousType) {
public AjaxResult getDetail(@PathVariable Long bankStatementId) { projectAccessService.assertCanReadByBankStatementId(bankStatementId);
CcdiBankStatementDetailVO detail = bankStatementService.getStatementDetail(bankStatementId); CcdiBankStatementDetailVO detail = bankStatementService.getStatementDetail(
bankStatementId,
modelCode,
suspiciousType
);
return AjaxResult.success(detail); return AjaxResult.success(detail);
} }
@@ -79,8 +100,8 @@ public class CcdiBankStatementController extends BaseController {
*/ */
@PostMapping("/export") @PostMapping("/export")
@Operation(summary = "导出流水明细") @Operation(summary = "导出流水明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:export')")
public void export(HttpServletResponse response, CcdiBankStatementQueryDTO queryDTO) { public void export(HttpServletResponse response, CcdiBankStatementQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiBankStatementExcel> list = bankStatementService.selectStatementListForExport(queryDTO); List<CcdiBankStatementExcel> list = bankStatementService.selectStatementListForExport(queryDTO);
ExcelUtil<CcdiBankStatementExcel> util = new ExcelUtil<>(CcdiBankStatementExcel.class); ExcelUtil<CcdiBankStatementExcel> util = new ExcelUtil<>(CcdiBankStatementExcel.class);
util.exportExcel(response, list, "流水明细"); util.exportExcel(response, list, "流水明细");

View File

@@ -1,6 +1,7 @@
package com.ruoyi.ccdi.project.controller; package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiBankTagRebuildDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiBankTagRebuildDTO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiBankTagService; import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -9,6 +10,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -27,12 +29,17 @@ public class CcdiBankTagController extends BaseController {
@Resource @Resource
private ICcdiBankTagService bankTagService; private ICcdiBankTagService bankTagService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 手动提交流水标签重算任务 * 手动提交流水标签重算任务
*/ */
@Operation(summary = "手动重算项目流水标签") @Operation(summary = "手动重算项目流水标签")
@PostMapping("/rebuild") @PostMapping("/rebuild")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult rebuild(@Validated @RequestBody CcdiBankTagRebuildDTO dto) { public AjaxResult rebuild(@Validated @RequestBody CcdiBankTagRebuildDTO dto) {
projectAccessService.assertCanOperate(dto.getProjectId());
String operator = SecurityUtils.getUsername(); String operator = SecurityUtils.getUsername();
log.info("【流水标签】收到手动重算请求: projectId={}, modelCode={}, operator={}", log.info("【流水标签】收到手动重算请求: projectId={}, modelCode={}, operator={}",
dto.getProjectId(), dto.getModelCode(), operator); dto.getProjectId(), dto.getModelCode(), operator);

View File

@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceSaveDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceSaveDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiEvidenceVO; import com.ruoyi.ccdi.project.domain.vo.CcdiEvidenceVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiEvidenceService; import com.ruoyi.ccdi.project.service.ICcdiEvidenceService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -34,13 +35,17 @@ public class CcdiEvidenceController extends BaseController {
@Resource @Resource
private ICcdiEvidenceService evidenceService; private ICcdiEvidenceService evidenceService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 保存证据 * 保存证据
*/ */
@PostMapping @PostMapping
@Operation(summary = "保存证据") @Operation(summary = "保存证据")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult saveEvidence(@Validated @RequestBody CcdiEvidenceSaveDTO dto) { public AjaxResult saveEvidence(@Validated @RequestBody CcdiEvidenceSaveDTO dto) {
projectAccessService.assertCanOperate(dto.getProjectId());
CcdiEvidenceVO vo = evidenceService.saveEvidence(dto, SecurityUtils.getUsername()); CcdiEvidenceVO vo = evidenceService.saveEvidence(dto, SecurityUtils.getUsername());
return AjaxResult.success("证据入库成功", vo); return AjaxResult.success("证据入库成功", vo);
} }
@@ -50,8 +55,8 @@ public class CcdiEvidenceController extends BaseController {
*/ */
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "查询项目证据列表") @Operation(summary = "查询项目证据列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult listEvidence(CcdiEvidenceQueryDTO queryDTO) { public AjaxResult listEvidence(CcdiEvidenceQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiEvidenceVO> list = evidenceService.listEvidence(queryDTO); List<CcdiEvidenceVO> list = evidenceService.listEvidence(queryDTO);
return AjaxResult.success(list); return AjaxResult.success(list);
} }
@@ -61,8 +66,8 @@ public class CcdiEvidenceController extends BaseController {
*/ */
@GetMapping("/{evidenceId}") @GetMapping("/{evidenceId}")
@Operation(summary = "查询证据详情") @Operation(summary = "查询证据详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getEvidence(@PathVariable Long evidenceId) { public AjaxResult getEvidence(@PathVariable Long evidenceId) {
projectAccessService.assertCanReadByEvidenceId(evidenceId);
CcdiEvidenceVO vo = evidenceService.getEvidence(evidenceId); CcdiEvidenceVO vo = evidenceService.getEvidence(evidenceId);
return AjaxResult.success(vo); return AjaxResult.success(vo);
} }

View File

@@ -6,6 +6,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiPullBankInfoSubmitDTO;
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord; import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiIdCardParseVO; import com.ruoyi.ccdi.project.domain.vo.CcdiIdCardParseVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService; import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -13,10 +14,12 @@ import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport; import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.lsfx.constants.LsfxConstants;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -40,17 +43,22 @@ public class CcdiFileUploadController extends BaseController {
@Resource @Resource
private ICcdiFileUploadService fileUploadService; private ICcdiFileUploadService fileUploadService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 批量上传文件(异步) * 批量上传文件(异步)
*/ */
@PostMapping("/batch") @PostMapping("/batch")
@Operation(summary = "批量上传文件", description = "异步批量上传流水文件") @Operation(summary = "批量上传文件", description = "异步批量上传流水文件")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult batchUpload(@RequestParam Long projectId, public AjaxResult batchUpload(@RequestParam Long projectId,
@RequestParam MultipartFile[] files) { @RequestParam MultipartFile[] files) {
// 参数校验 // 参数校验
if (projectId == null) { if (projectId == null) {
return AjaxResult.error("项目ID不能为空"); return AjaxResult.error("项目ID不能为空");
} }
projectAccessService.assertCanOperate(projectId);
if (files == null || files.length == 0) { if (files == null || files.length == 0) {
return AjaxResult.error("请选择要上传的文件"); return AjaxResult.error("请选择要上传的文件");
} }
@@ -58,7 +66,7 @@ public class CcdiFileUploadController extends BaseController {
return AjaxResult.error("单次最多上传100个文件"); return AjaxResult.error("单次最多上传100个文件");
} }
// 校验文件大小和格式 // 校验文件数量、空文件和大小,文件名业务规则统一在 Service 层处理
for (MultipartFile file : files) { for (MultipartFile file : files) {
if (file.isEmpty()) { if (file.isEmpty()) {
return AjaxResult.error("文件不能为空"); return AjaxResult.error("文件不能为空");
@@ -66,15 +74,6 @@ public class CcdiFileUploadController extends BaseController {
if (file.getSize() > 50 * 1024 * 1024) { if (file.getSize() > 50 * 1024 * 1024) {
return AjaxResult.error("文件 " + file.getOriginalFilename() + " 超过50MB限制"); return AjaxResult.error("文件 " + file.getOriginalFilename() + " 超过50MB限制");
} }
String fileName = file.getOriginalFilename();
if (fileName == null || fileName.trim().isEmpty()) {
return AjaxResult.error("文件名不能为空");
}
String lowerFileName = fileName.toLowerCase();
if (!lowerFileName.endsWith(".xlsx") && !lowerFileName.endsWith(".csv")
&& !lowerFileName.endsWith(".pdf")) {
return AjaxResult.error("文件 " + fileName + " 格式不支持, 仅支持 PDF, CSV, XLSX 文件");
}
} }
try { try {
@@ -95,6 +94,7 @@ public class CcdiFileUploadController extends BaseController {
*/ */
@PostMapping("/parse-id-card-file") @PostMapping("/parse-id-card-file")
@Operation(summary = "解析身份证文件", description = "解析首个sheet第一列的身份证号") @Operation(summary = "解析身份证文件", description = "解析首个sheet第一列的身份证号")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult parseIdCardFile(@RequestParam MultipartFile file) { public AjaxResult parseIdCardFile(@RequestParam MultipartFile file) {
if (file == null || file.isEmpty()) { if (file == null || file.isEmpty()) {
return AjaxResult.error("身份证文件不能为空"); return AjaxResult.error("身份证文件不能为空");
@@ -104,18 +104,29 @@ public class CcdiFileUploadController extends BaseController {
} }
/** /**
* 提交拉取本行信息任务 * 提交拉取行内/金综流水任务
*/ */
@PostMapping("/pull-bank-info") @PostMapping("/pull-bank-info")
@Operation(summary = "拉取本行信息", description = "身份证号批量提交拉取本行信息任务") @Operation(summary = "拉取行内/金综流水", description = "证件号码批量提交拉取行内/金综流水任务")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) { public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) {
if (dto == null || dto.getProjectId() == null) { if (dto == null || dto.getProjectId() == null) {
return AjaxResult.error("项目ID不能为空"); return AjaxResult.error("项目ID不能为空");
} }
projectAccessService.assertCanOperate(dto.getProjectId());
if (CollectionUtils.isEmpty(dto.getIdCards())) { if (CollectionUtils.isEmpty(dto.getIdCards())) {
return AjaxResult.error("身份证号不能为空"); return AjaxResult.error("身份证号不能为空");
} }
if (!StringUtils.hasText(dto.getStartDate()) || !StringUtils.hasText(dto.getEndDate())) { if (!StringUtils.hasText(dto.getDataChannelCode())) {
return AjaxResult.error("流水来源不能为空");
}
String dataChannelCode = dto.getDataChannelCode().trim().toUpperCase();
if (!LsfxConstants.DATA_CHANNEL_ZJRCU.equals(dataChannelCode)
&& !LsfxConstants.DATA_CHANNEL_JZL.equals(dataChannelCode)) {
return AjaxResult.error("流水来源不支持");
}
if (LsfxConstants.DATA_CHANNEL_ZJRCU.equals(dataChannelCode)
&& (!StringUtils.hasText(dto.getStartDate()) || !StringUtils.hasText(dto.getEndDate()))) {
return AjaxResult.error("开始日期和结束日期不能为空"); return AjaxResult.error("开始日期和结束日期不能为空");
} }
@@ -124,6 +135,7 @@ public class CcdiFileUploadController extends BaseController {
String batchId = fileUploadService.submitPullBankInfo( String batchId = fileUploadService.submitPullBankInfo(
dto.getProjectId(), dto.getProjectId(),
dto.getIdCards(), dto.getIdCards(),
dataChannelCode,
dto.getStartDate(), dto.getStartDate(),
dto.getEndDate(), dto.getEndDate(),
userId, userId,
@@ -138,6 +150,7 @@ public class CcdiFileUploadController extends BaseController {
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "查询上传记录列表", description = "分页查询文件上传记录") @Operation(summary = "查询上传记录列表", description = "分页查询文件上传记录")
public TableDataInfo list(CcdiFileUploadQueryDTO queryDTO) { public TableDataInfo list(CcdiFileUploadQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
PageDomain pageDomain = TableSupport.buildPageRequest(); PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiFileUploadRecord> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); Page<CcdiFileUploadRecord> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiFileUploadRecord> result = fileUploadService.selectPage(page, queryDTO); Page<CcdiFileUploadRecord> result = fileUploadService.selectPage(page, queryDTO);
@@ -150,6 +163,7 @@ public class CcdiFileUploadController extends BaseController {
@GetMapping("/statistics/{projectId}") @GetMapping("/statistics/{projectId}")
@Operation(summary = "查询上传统计", description = "统计各状态的文件数量") @Operation(summary = "查询上传统计", description = "统计各状态的文件数量")
public AjaxResult getStatistics(@PathVariable Long projectId) { public AjaxResult getStatistics(@PathVariable Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiFileUploadStatisticsVO statistics = fileUploadService.countByStatus(projectId); CcdiFileUploadStatisticsVO statistics = fileUploadService.countByStatus(projectId);
return AjaxResult.success(statistics); return AjaxResult.success(statistics);
} }
@@ -160,6 +174,7 @@ public class CcdiFileUploadController extends BaseController {
@GetMapping("/detail/{id}") @GetMapping("/detail/{id}")
@Operation(summary = "查询记录详情", description = "根据ID查询文件上传记录详情") @Operation(summary = "查询记录详情", description = "根据ID查询文件上传记录详情")
public AjaxResult getDetail(@PathVariable Long id) { public AjaxResult getDetail(@PathVariable Long id) {
projectAccessService.assertCanReadByFileRecordId(id);
CcdiFileUploadRecord record = fileUploadService.getById(id); CcdiFileUploadRecord record = fileUploadService.getById(id);
return AjaxResult.success(record); return AjaxResult.success(record);
} }
@@ -169,7 +184,9 @@ public class CcdiFileUploadController extends BaseController {
*/ */
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
@Operation(summary = "删除上传文件", description = "按上传记录ID删除文件并清理流水") @Operation(summary = "删除上传文件", description = "按上传记录ID删除文件并清理流水")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult deleteFile(@PathVariable Long id) { public AjaxResult deleteFile(@PathVariable Long id) {
projectAccessService.assertCanOperateByFileRecordId(id);
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
String message = fileUploadService.deleteFileUploadRecord(id, userId); String message = fileUploadService.deleteFileUploadRecord(id, userId);
return AjaxResult.success(message); return AjaxResult.success(message);

View File

@@ -8,6 +8,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphEdgeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphNodeVO; import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphStatementVO; import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphStatementVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphVO; import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiFundGraphService; import com.ruoyi.ccdi.project.service.ICcdiFundGraphService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -38,10 +39,14 @@ public class CcdiFundGraphController extends BaseController {
@Resource @Resource
private ICcdiFundGraphService fundGraphService; private ICcdiFundGraphService fundGraphService;
@Resource
private CcdiProjectAccessService projectAccessService;
@GetMapping("/search") @GetMapping("/search")
@Operation(summary = "查询资金流图谱主体") @Operation(summary = "查询资金流图谱主体")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult searchSubjects(CcdiFundGraphQueryDTO queryDTO) { public AjaxResult searchSubjects(CcdiFundGraphQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiFundGraphNodeVO> subjects = fundGraphService.searchSubjects(queryDTO); List<CcdiFundGraphNodeVO> subjects = fundGraphService.searchSubjects(queryDTO);
return AjaxResult.success(subjects); return AjaxResult.success(subjects);
} }
@@ -50,6 +55,7 @@ public class CcdiFundGraphController extends BaseController {
@Operation(summary = "查询一层资金流图谱") @Operation(summary = "查询一层资金流图谱")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getGraph(CcdiFundGraphQueryDTO queryDTO) { public AjaxResult getGraph(CcdiFundGraphQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiFundGraphVO graph = fundGraphService.getFundGraph(queryDTO); CcdiFundGraphVO graph = fundGraphService.getFundGraph(queryDTO);
return AjaxResult.success(graph); return AjaxResult.success(graph);
} }
@@ -58,6 +64,7 @@ public class CcdiFundGraphController extends BaseController {
@Operation(summary = "查询资金边流水明细") @Operation(summary = "查询资金边流水明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public TableDataInfo getEdgeDetail(CcdiFundGraphEdgeDetailQueryDTO queryDTO) { public TableDataInfo getEdgeDetail(CcdiFundGraphEdgeDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
PageDomain pageDomain = TableSupport.buildPageRequest(); PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiFundGraphStatementVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); Page<CcdiFundGraphStatementVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiFundGraphStatementVO> result = fundGraphService.getEdgeDetails(page, queryDTO); Page<CcdiFundGraphStatementVO> result = fundGraphService.getEdgeDetails(page, queryDTO);
@@ -66,9 +73,10 @@ public class CcdiFundGraphController extends BaseController {
@PostMapping("/manual-edge") @PostMapping("/manual-edge")
@Operation(summary = "新增手工资金流向") @Operation(summary = "新增手工资金流向")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult saveManualEdge(@RequestBody CcdiFundGraphManualEdgeSaveDTO saveDTO) { public AjaxResult saveManualEdge(@RequestBody CcdiFundGraphManualEdgeSaveDTO saveDTO) {
try { try {
projectAccessService.assertCanOperate(saveDTO == null ? null : saveDTO.getProjectId());
CcdiFundGraphEdgeVO edge = fundGraphService.saveManualEdge(saveDTO, SecurityUtils.getUsername()); CcdiFundGraphEdgeVO edge = fundGraphService.saveManualEdge(saveDTO, SecurityUtils.getUsername());
return AjaxResult.success(edge); return AjaxResult.success(edge);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {

View File

@@ -11,9 +11,11 @@ import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveAllDTO;
import com.ruoyi.ccdi.project.domain.vo.ModelListVO; import com.ruoyi.ccdi.project.domain.vo.ModelListVO;
import com.ruoyi.ccdi.project.domain.vo.ModelParamVO; import com.ruoyi.ccdi.project.domain.vo.ModelParamVO;
import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO; import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiModelParamService; import com.ruoyi.ccdi.project.service.ICcdiModelParamService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -31,12 +33,17 @@ public class CcdiModelParamController extends BaseController {
@Resource @Resource
private ICcdiModelParamService modelParamService; private ICcdiModelParamService modelParamService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 查询模型列表 * 查询模型列表
*/ */
@Operation(summary = "查询模型列表") @Operation(summary = "查询模型列表")
@GetMapping("/modelList") @GetMapping("/modelList")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult listModels(@RequestParam(required = false) Long projectId) { public AjaxResult listModels(@RequestParam(required = false) Long projectId) {
assertCanReadProjectParam(projectId);
List<ModelListVO> list = modelParamService.selectModelList(projectId); List<ModelListVO> list = modelParamService.selectModelList(projectId);
return success(list); return success(list);
} }
@@ -46,7 +53,9 @@ public class CcdiModelParamController extends BaseController {
*/ */
@Operation(summary = "查询模型参数列表") @Operation(summary = "查询模型参数列表")
@GetMapping("/list") @GetMapping("/list")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult list(@Validated ModelParamQueryDTO queryDTO) { public AjaxResult list(@Validated ModelParamQueryDTO queryDTO) {
assertCanReadProjectParam(queryDTO.getProjectId());
List<ModelParamVO> list = modelParamService.selectParamList(queryDTO); List<ModelParamVO> list = modelParamService.selectParamList(queryDTO);
return success(list); return success(list);
} }
@@ -57,7 +66,9 @@ public class CcdiModelParamController extends BaseController {
@Operation(summary = "保存模型参数") @Operation(summary = "保存模型参数")
@Log(title = "模型参数配置", businessType = BusinessType.UPDATE) @Log(title = "模型参数配置", businessType = BusinessType.UPDATE)
@PostMapping("/save") @PostMapping("/save")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult save(@Validated @RequestBody ModelParamSaveDTO saveDTO) { public AjaxResult save(@Validated @RequestBody ModelParamSaveDTO saveDTO) {
assertCanOperateProjectParam(saveDTO.getProjectId());
modelParamService.saveParams(saveDTO); modelParamService.saveParams(saveDTO);
return success("保存成功"); return success("保存成功");
} }
@@ -67,7 +78,9 @@ public class CcdiModelParamController extends BaseController {
*/ */
@Operation(summary = "查询所有模型及其参数") @Operation(summary = "查询所有模型及其参数")
@GetMapping("/listAll") @GetMapping("/listAll")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult listAll(@Validated ModelParamAllQueryDTO queryDTO) { public AjaxResult listAll(@Validated ModelParamAllQueryDTO queryDTO) {
assertCanReadProjectParam(queryDTO.getProjectId());
ModelParamAllVO result = modelParamService.selectAllParams(queryDTO.getProjectId()); ModelParamAllVO result = modelParamService.selectAllParams(queryDTO.getProjectId());
return success(result); return success(result);
} }
@@ -78,8 +91,24 @@ public class CcdiModelParamController extends BaseController {
@Operation(summary = "批量保存所有模型参数") @Operation(summary = "批量保存所有模型参数")
@Log(title = "模型参数配置", businessType = BusinessType.UPDATE) @Log(title = "模型参数配置", businessType = BusinessType.UPDATE)
@PostMapping("/saveAll") @PostMapping("/saveAll")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult saveAll(@Validated @RequestBody ModelParamSaveAllDTO saveAllDTO) { public AjaxResult saveAll(@Validated @RequestBody ModelParamSaveAllDTO saveAllDTO) {
assertCanOperateProjectParam(saveAllDTO.getProjectId());
modelParamService.saveAllParams(saveAllDTO); modelParamService.saveAllParams(saveAllDTO);
return success("保存成功"); return success("保存成功");
} }
private void assertCanReadProjectParam(Long projectId) {
if (projectId == null || projectId <= 0) {
return;
}
projectAccessService.assertCanRead(projectId);
}
private void assertCanOperateProjectParam(Long projectId) {
if (projectId == null || projectId <= 0) {
return;
}
projectAccessService.assertCanOperate(projectId);
}
} }

View File

@@ -74,18 +74,28 @@ public class CcdiProjectController extends BaseController {
*/ */
@DeleteMapping("/{projectId}") @DeleteMapping("/{projectId}")
@Operation(summary = "删除项目") @Operation(summary = "删除项目")
@PreAuthorize("@ss.hasPermi('ccdi:project:remove')") @PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public AjaxResult deleteProject(@PathVariable Long projectId) { public AjaxResult deleteProject(@PathVariable Long projectId) {
boolean success = projectService.deleteProject(projectId); boolean success = projectService.deleteProject(projectId, SecurityUtils.getUsername());
return success ? AjaxResult.success("项目删除成功") : AjaxResult.error("项目删除失败"); return success ? AjaxResult.success("项目删除成功") : AjaxResult.error("项目删除失败");
} }
/**
* 恢复项目
*/
@PostMapping("/{projectId}/restore")
@Operation(summary = "恢复项目")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult restoreProject(@PathVariable Long projectId) {
projectService.restoreProject(projectId, SecurityUtils.getUsername());
return AjaxResult.success("项目恢复成功");
}
/** /**
* 查询项目详情 * 查询项目详情
*/ */
@GetMapping("/{projectId}") @GetMapping("/{projectId}")
@Operation(summary = "查询项目详情") @Operation(summary = "查询项目详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getProject(@PathVariable Long projectId) { public AjaxResult getProject(@PathVariable Long projectId) {
CcdiProjectVO vo = projectService.getProjectById(projectId); CcdiProjectVO vo = projectService.getProjectById(projectId);
return AjaxResult.success(vo); return AjaxResult.success(vo);
@@ -96,7 +106,6 @@ public class CcdiProjectController extends BaseController {
*/ */
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "查询项目列表") @Operation(summary = "查询项目列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public TableDataInfo listProject(CcdiProjectQueryDTO queryDTO) { public TableDataInfo listProject(CcdiProjectQueryDTO queryDTO) {
PageDomain pageDomain = TableSupport.buildPageRequest(); PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiProjectVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); Page<CcdiProjectVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
@@ -109,7 +118,6 @@ public class CcdiProjectController extends BaseController {
*/ */
@GetMapping("/history") @GetMapping("/history")
@Operation(summary = "查询历史项目列表") @Operation(summary = "查询历史项目列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public AjaxResult listHistoryProjects(CcdiProjectQueryDTO queryDTO) { public AjaxResult listHistoryProjects(CcdiProjectQueryDTO queryDTO) {
List<CcdiProjectHistoryListItemVO> result = projectService.listHistoryProjects(queryDTO); List<CcdiProjectHistoryListItemVO> result = projectService.listHistoryProjects(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
@@ -131,7 +139,6 @@ public class CcdiProjectController extends BaseController {
*/ */
@GetMapping("/statusCounts") @GetMapping("/statusCounts")
@Operation(summary = "查询项目状态统计") @Operation(summary = "查询项目状态统计")
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public AjaxResult getStatusCounts() { public AjaxResult getStatusCounts() {
CcdiProjectStatusCountsVO counts = projectService.getStatusCounts(); CcdiProjectStatusCountsVO counts = projectService.getStatusCounts();
return AjaxResult.success(counts); return AjaxResult.success(counts);

View File

@@ -2,14 +2,23 @@ package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.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.CcdiProjectRiskExclusionSaveDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; 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.CcdiProjectRiskPeopleOverviewExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel; 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.CcdiProjectAbnormalAccountPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
@@ -17,6 +26,9 @@ 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 com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountFlowVO;
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.ccdi.project.service.ICcdiProjectOverviewService;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
@@ -26,8 +38,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -45,6 +59,9 @@ public class CcdiProjectOverviewController extends BaseController {
@Resource @Resource
private ICcdiProjectOverviewService overviewService; private ICcdiProjectOverviewService overviewService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 查询风险仪表盘 * 查询风险仪表盘
*/ */
@@ -52,10 +69,23 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询风险仪表盘") @Operation(summary = "查询风险仪表盘")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getDashboard(Long projectId) { public AjaxResult getDashboard(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectOverviewDashboardVO dashboard = overviewService.getDashboard(projectId); CcdiProjectOverviewDashboardVO dashboard = overviewService.getDashboard(projectId);
return AjaxResult.success(dashboard); return AjaxResult.success(dashboard);
} }
/**
* 排除单条可疑预警
*/
@PostMapping("/risk-exclusions")
@Operation(summary = "排除单条可疑预警")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult excludeRisk(@Validated @RequestBody CcdiProjectRiskExclusionSaveDTO dto) {
projectAccessService.assertCanOperate(dto.getProjectId());
overviewService.excludeRisk(dto);
return AjaxResult.success("排除成功");
}
/** /**
* 查询风险人员总览 * 查询风险人员总览
*/ */
@@ -63,10 +93,35 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询风险人员总览") @Operation(summary = "查询风险人员总览")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getRiskPeople(CcdiProjectRiskPeopleQueryDTO queryDTO) { public AjaxResult getRiskPeople(CcdiProjectRiskPeopleQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(queryDTO); CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(queryDTO);
return AjaxResult.success(overview); return AjaxResult.success(overview);
} }
/**
* 查询外部人员预警
*/
@GetMapping("/external-persons")
@Operation(summary = "查询外部人员预警")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExternalPersons(CcdiProjectExternalPersonQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExternalPersonWarningVO warnings = overviewService.getExternalPersonWarnings(queryDTO);
return AjaxResult.success(warnings);
}
/**
* 查询外部人员风险汇总
*/
@GetMapping("/external-persons/summary")
@Operation(summary = "查询外部人员风险汇总")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExternalRiskSummary(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectExternalRiskSummaryVO summary = overviewService.getExternalRiskSummary(projectId);
return AjaxResult.success(summary);
}
/** /**
* 查询中高风险人员TOP10 * 查询中高风险人员TOP10
*/ */
@@ -74,6 +129,7 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询中高风险人员TOP10") @Operation(summary = "查询中高风险人员TOP10")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getTopRiskPeople(Long projectId) { public AjaxResult getTopRiskPeople(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectTopRiskPeopleVO topRiskPeople = overviewService.getTopRiskPeople(projectId); CcdiProjectTopRiskPeopleVO topRiskPeople = overviewService.getTopRiskPeople(projectId);
return AjaxResult.success(topRiskPeople); return AjaxResult.success(topRiskPeople);
} }
@@ -85,6 +141,7 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询风险模型卡片") @Operation(summary = "查询风险模型卡片")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getRiskModelCards(Long projectId) { public AjaxResult getRiskModelCards(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectRiskModelCardsVO cards = overviewService.getRiskModelCards(projectId); CcdiProjectRiskModelCardsVO cards = overviewService.getRiskModelCards(projectId);
return AjaxResult.success(cards); return AjaxResult.success(cards);
} }
@@ -96,10 +153,35 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询风险模型命中人员") @Operation(summary = "查询风险模型命中人员")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) { public AjaxResult getRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectRiskModelPeopleVO people = overviewService.getRiskModelPeople(queryDTO); CcdiProjectRiskModelPeopleVO people = overviewService.getRiskModelPeople(queryDTO);
return AjaxResult.success(people); return AjaxResult.success(people);
} }
/**
* 查询外部人员风险模型卡片
*/
@GetMapping("/external-risk-models/cards")
@Operation(summary = "查询外部人员风险模型卡片")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExternalRiskModelCards(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectRiskModelCardsVO cards = overviewService.getExternalRiskModelCards(projectId);
return AjaxResult.success(cards);
}
/**
* 查询外部人员风险模型命中人员
*/
@GetMapping("/external-risk-models/people")
@Operation(summary = "查询外部人员风险模型命中人员")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExternalRiskModelPeople(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectRiskModelPeopleVO people = overviewService.getExternalRiskModelPeople(queryDTO);
return AjaxResult.success(people);
}
/** /**
* 查询项目分析详情 * 查询项目分析详情
*/ */
@@ -107,6 +189,7 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询项目分析详情") @Operation(summary = "查询项目分析详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getPersonAnalysisDetail(CcdiProjectPersonAnalysisDetailQueryDTO queryDTO) { public AjaxResult getPersonAnalysisDetail(CcdiProjectPersonAnalysisDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectPersonAnalysisDetailVO detail = overviewService.getPersonAnalysisDetail(queryDTO); CcdiProjectPersonAnalysisDetailVO detail = overviewService.getPersonAnalysisDetail(queryDTO);
return AjaxResult.success(detail); return AjaxResult.success(detail);
} }
@@ -118,6 +201,7 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询涉疑交易明细") @Operation(summary = "查询涉疑交易明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getSuspiciousTransactions(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) { public AjaxResult getSuspiciousTransactions(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectSuspiciousTransactionPageVO pageVO = overviewService.getSuspiciousTransactions(queryDTO); CcdiProjectSuspiciousTransactionPageVO pageVO = overviewService.getSuspiciousTransactions(queryDTO);
return AjaxResult.success(pageVO); return AjaxResult.success(pageVO);
} }
@@ -129,6 +213,7 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询项目员工负面征信") @Operation(summary = "查询项目员工负面征信")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getEmployeeCreditNegative(CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO) { public AjaxResult getEmployeeCreditNegative(CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectEmployeeCreditNegativePageVO pageVO = overviewService.getEmployeeCreditNegative(queryDTO); CcdiProjectEmployeeCreditNegativePageVO pageVO = overviewService.getEmployeeCreditNegative(queryDTO);
return AjaxResult.success(pageVO); return AjaxResult.success(pageVO);
} }
@@ -140,10 +225,35 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询异常账户人员信息") @Operation(summary = "查询异常账户人员信息")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getAbnormalAccountPeople(CcdiProjectAbnormalAccountQueryDTO queryDTO) { public AjaxResult getAbnormalAccountPeople(CcdiProjectAbnormalAccountQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectAbnormalAccountPageVO pageVO = overviewService.getAbnormalAccountPeople(queryDTO); CcdiProjectAbnormalAccountPageVO pageVO = overviewService.getAbnormalAccountPeople(queryDTO);
return AjaxResult.success(pageVO); 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);
}
/**
* 查询未提交账户流水
*/
@GetMapping("/unsubmitted-accounts/flows")
@Operation(summary = "查询未提交账户流水")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getUnsubmittedAccountFlows(CcdiProjectUnsubmittedAccountQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectUnsubmittedAccountFlowVO> rows = overviewService.getUnsubmittedAccountFlows(queryDTO);
return AjaxResult.success(rows);
}
/** /**
* 导出涉疑交易明细 * 导出涉疑交易明细
*/ */
@@ -154,6 +264,7 @@ public class CcdiProjectOverviewController extends BaseController {
HttpServletResponse response, HttpServletResponse response,
CcdiProjectSuspiciousTransactionQueryDTO queryDTO CcdiProjectSuspiciousTransactionQueryDTO queryDTO
) { ) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectSuspiciousTransactionExcel> rows = overviewService.exportSuspiciousTransactions(queryDTO); List<CcdiProjectSuspiciousTransactionExcel> rows = overviewService.exportSuspiciousTransactions(queryDTO);
ExcelUtil<CcdiProjectSuspiciousTransactionExcel> util = ExcelUtil<CcdiProjectSuspiciousTransactionExcel> util =
new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class); new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class);
@@ -167,20 +278,87 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "导出风险人员总览") @Operation(summary = "导出风险人员总览")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskPeople(HttpServletResponse response, Long projectId) { public void exportRiskPeople(HttpServletResponse response, Long projectId) {
projectAccessService.assertCanRead(projectId);
List<CcdiProjectRiskPeopleOverviewExcel> rows = overviewService.exportRiskPeopleOverview(projectId); List<CcdiProjectRiskPeopleOverviewExcel> rows = overviewService.exportRiskPeopleOverview(projectId);
ExcelUtil<CcdiProjectRiskPeopleOverviewExcel> util = ExcelUtil<CcdiProjectRiskPeopleOverviewExcel> util =
new ExcelUtil<>(CcdiProjectRiskPeopleOverviewExcel.class); new ExcelUtil<>(CcdiProjectRiskPeopleOverviewExcel.class);
util.exportExcel(response, rows, "风险人员总览"); util.exportExcel(response, rows, "风险人员总览");
} }
/**
* 导出外部人员预警
*/
@PostMapping("/external-persons/export")
@Operation(summary = "导出外部人员预警")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportExternalPersons(HttpServletResponse response, Long projectId) {
projectAccessService.assertCanRead(projectId);
List<CcdiProjectExternalPersonWarningExcel> rows = overviewService.exportExternalPersonWarnings(projectId);
ExcelUtil<CcdiProjectExternalPersonWarningExcel> util =
new ExcelUtil<>(CcdiProjectExternalPersonWarningExcel.class);
util.exportExcel(response, rows, "外部人员预警");
}
/**
* 导出风险模型命中人员
*/
@PostMapping("/risk-models/people/export")
@Operation(summary = "导出风险模型命中人员")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskModelPeople(HttpServletResponse response, CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectRiskModelPeopleExcel> rows = overviewService.exportRiskModelPeople(queryDTO);
ExcelUtil<CcdiProjectRiskModelPeopleExcel> util =
new ExcelUtil<>(CcdiProjectRiskModelPeopleExcel.class);
util.exportExcel(response, rows, "风险模型命中人员");
}
/**
* 导出外部人员风险模型命中人员
*/
@PostMapping("/external-risk-models/people/export")
@Operation(summary = "导出外部人员风险模型命中人员")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportExternalRiskModelPeople(
HttpServletResponse response,
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectRiskModelPeopleExcel> rows = overviewService.exportExternalRiskModelPeople(queryDTO);
ExcelUtil<CcdiProjectRiskModelPeopleExcel> util =
new ExcelUtil<>(CcdiProjectRiskModelPeopleExcel.class);
util.exportExcel(response, rows, "外部人员风险模型命中人员");
}
/**
* 导出未提交账户流水
*/
@PostMapping("/unsubmitted-accounts/export")
@Operation(summary = "导出未提交账户流水")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportUnsubmittedAccountFlows(
HttpServletResponse response,
CcdiProjectUnsubmittedAccountQueryDTO queryDTO
) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectUnsubmittedAccountFlowExcel> rows = overviewService.exportUnsubmittedAccountFlows(queryDTO);
ExcelUtil<CcdiProjectUnsubmittedAccountFlowExcel> util =
new ExcelUtil<>(CcdiProjectUnsubmittedAccountFlowExcel.class);
util.exportExcel(response, rows, "未提交账户流水");
}
/** /**
* 导出风险明细 * 导出风险明细
*/ */
@PostMapping("/risk-details/export") @PostMapping("/risk-details/export")
@Operation(summary = "导出风险明细") @Operation(summary = "导出风险明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskDetails(HttpServletResponse response, Long projectId) { public void exportRiskDetails(
overviewService.exportRiskDetails(response, projectId); HttpServletResponse response,
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
overviewService.exportRiskDetails(response, queryDTO);
} }
/** /**
@@ -190,6 +368,7 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "导出结果总览报告") @Operation(summary = "导出结果总览报告")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportOverviewReport(HttpServletResponse response, Long projectId) { public void exportOverviewReport(HttpServletResponse response, Long projectId) {
projectAccessService.assertCanRead(projectId);
overviewService.exportOverviewReport(response, projectId); overviewService.exportOverviewReport(response, projectId);
} }
} }

View File

@@ -8,6 +8,8 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryD
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectIncreaseLendingQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectIncreaseLendingExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
@@ -16,15 +18,21 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectIncreaseLendingListVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService; import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.poi.ExcelUtil;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -39,6 +47,9 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Resource @Resource
private ICcdiProjectSpecialCheckService specialCheckService; private ICcdiProjectSpecialCheckService specialCheckService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 查询员工家庭资产负债列表 * 查询员工家庭资产负债列表
*/ */
@@ -46,6 +57,7 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询员工家庭资产负债列表") @Operation(summary = "查询员工家庭资产负债列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getFamilyAssetLiabilityList(@Validated CcdiProjectFamilyAssetLiabilityListQueryDTO queryDTO) { public AjaxResult getFamilyAssetLiabilityList(@Validated CcdiProjectFamilyAssetLiabilityListQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectFamilyAssetLiabilityListVO result = specialCheckService.getFamilyAssetLiabilityList(queryDTO); CcdiProjectFamilyAssetLiabilityListVO result = specialCheckService.getFamilyAssetLiabilityList(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -57,6 +69,7 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询员工家庭资产负债详情") @Operation(summary = "查询员工家庭资产负债详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getFamilyAssetLiabilityDetail(@Validated CcdiProjectFamilyAssetLiabilityDetailQueryDTO queryDTO) { public AjaxResult getFamilyAssetLiabilityDetail(@Validated CcdiProjectFamilyAssetLiabilityDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectFamilyAssetLiabilityDetailVO result = specialCheckService.getFamilyAssetLiabilityDetail(queryDTO); CcdiProjectFamilyAssetLiabilityDetailVO result = specialCheckService.getFamilyAssetLiabilityDetail(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -68,6 +81,7 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询采购拓展列表") @Operation(summary = "查询采购拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedPurchaseList(@Validated CcdiProjectExtendedPurchaseQueryDTO queryDTO) { public AjaxResult getExtendedPurchaseList(@Validated CcdiProjectExtendedPurchaseQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedPurchaseListVO result = specialCheckService.getExtendedPurchaseList(queryDTO); CcdiProjectExtendedPurchaseListVO result = specialCheckService.getExtendedPurchaseList(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -79,6 +93,7 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询采购拓展详情") @Operation(summary = "查询采购拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedPurchaseDetail(@Validated CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO) { public AjaxResult getExtendedPurchaseDetail(@Validated CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedPurchaseDetailVO result = specialCheckService.getExtendedPurchaseDetail(queryDTO); CcdiProjectExtendedPurchaseDetailVO result = specialCheckService.getExtendedPurchaseDetail(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -90,6 +105,7 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询招聘拓展列表") @Operation(summary = "查询招聘拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedRecruitmentList(@Validated CcdiProjectExtendedRecruitmentQueryDTO queryDTO) { public AjaxResult getExtendedRecruitmentList(@Validated CcdiProjectExtendedRecruitmentQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedRecruitmentListVO result = specialCheckService.getExtendedRecruitmentList(queryDTO); CcdiProjectExtendedRecruitmentListVO result = specialCheckService.getExtendedRecruitmentList(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -101,6 +117,7 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询招聘拓展详情") @Operation(summary = "查询招聘拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedRecruitmentDetail(@Validated CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO) { public AjaxResult getExtendedRecruitmentDetail(@Validated CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedRecruitmentDetailVO result = specialCheckService.getExtendedRecruitmentDetail(queryDTO); CcdiProjectExtendedRecruitmentDetailVO result = specialCheckService.getExtendedRecruitmentDetail(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -112,6 +129,7 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询调动拓展列表") @Operation(summary = "查询调动拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedTransferList(@Validated CcdiProjectExtendedTransferQueryDTO queryDTO) { public AjaxResult getExtendedTransferList(@Validated CcdiProjectExtendedTransferQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedTransferListVO result = specialCheckService.getExtendedTransferList(queryDTO); CcdiProjectExtendedTransferListVO result = specialCheckService.getExtendedTransferList(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -123,7 +141,33 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询调动拓展详情") @Operation(summary = "查询调动拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedTransferDetail(@Validated CcdiProjectExtendedTransferDetailQueryDTO queryDTO) { public AjaxResult getExtendedTransferDetail(@Validated CcdiProjectExtendedTransferDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedTransferDetailVO result = specialCheckService.getExtendedTransferDetail(queryDTO); CcdiProjectExtendedTransferDetailVO result = specialCheckService.getExtendedTransferDetail(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
/**
* 查询新增贷款列表
*/
@GetMapping("/increase-lending/list")
@Operation(summary = "查询新增贷款列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getIncreaseLendingList(@Validated CcdiProjectIncreaseLendingQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectIncreaseLendingListVO result = specialCheckService.getIncreaseLendingList(queryDTO);
return AjaxResult.success(result);
}
/**
* 导出新增贷款列表
*/
@PostMapping("/increase-lending/export")
@Operation(summary = "导出新增贷款列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportIncreaseLending(HttpServletResponse response, @Validated CcdiProjectIncreaseLendingQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectIncreaseLendingExcel> rows = specialCheckService.exportIncreaseLendingList(queryDTO);
ExcelUtil<CcdiProjectIncreaseLendingExcel> util = new ExcelUtil<>(CcdiProjectIncreaseLendingExcel.class);
util.exportExcel(response, rows, "新增贷款查询");
}
} }

View File

@@ -5,6 +5,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiRelationGraphSuspectedEnterpriseQue
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphNodeVO; import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphSuspectedEnterpriseVO; import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphSuspectedEnterpriseVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphVO; import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiRelationGraphService; import com.ruoyi.ccdi.project.service.ICcdiRelationGraphService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -29,10 +30,14 @@ public class CcdiRelationGraphController extends BaseController {
@Resource @Resource
private ICcdiRelationGraphService relationGraphService; private ICcdiRelationGraphService relationGraphService;
@Resource
private CcdiProjectAccessService projectAccessService;
@GetMapping("/search") @GetMapping("/search")
@Operation(summary = "查询关系图谱主体") @Operation(summary = "查询关系图谱主体")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult searchSubjects(CcdiRelationGraphQueryDTO queryDTO) { public AjaxResult searchSubjects(CcdiRelationGraphQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiRelationGraphNodeVO> subjects = relationGraphService.searchSubjects(queryDTO); List<CcdiRelationGraphNodeVO> subjects = relationGraphService.searchSubjects(queryDTO);
return AjaxResult.success(subjects); return AjaxResult.success(subjects);
} }
@@ -41,6 +46,7 @@ public class CcdiRelationGraphController extends BaseController {
@Operation(summary = "查询一层关系图谱") @Operation(summary = "查询一层关系图谱")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getGraph(CcdiRelationGraphQueryDTO queryDTO) { public AjaxResult getGraph(CcdiRelationGraphQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiRelationGraphVO graph = relationGraphService.getRelationGraph(queryDTO); CcdiRelationGraphVO graph = relationGraphService.getRelationGraph(queryDTO);
return AjaxResult.success(graph); return AjaxResult.success(graph);
} }
@@ -49,6 +55,7 @@ public class CcdiRelationGraphController extends BaseController {
@Operation(summary = "查询关系图谱疑似同名企业") @Operation(summary = "查询关系图谱疑似同名企业")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getSuspectedEnterprises(CcdiRelationGraphSuspectedEnterpriseQueryDTO queryDTO) { public AjaxResult getSuspectedEnterprises(CcdiRelationGraphSuspectedEnterpriseQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiRelationGraphSuspectedEnterpriseVO result = relationGraphService.getSuspectedEnterprises(queryDTO); CcdiRelationGraphSuspectedEnterpriseVO result = relationGraphService.getSuspectedEnterprises(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }

View File

@@ -32,7 +32,7 @@ public class CcdiProject implements Serializable {
/** 配置方式default-全局默认custom-自定义 */ /** 配置方式default-全局默认custom-自定义 */
private String configType; private String configType;
/** 项目状态0-进行中1-已完成2-已归档3-打标中4-打标失败 */ /** 项目状态0-进行中1-已完成2-已归档3-打标中4-打标失败5-已删除 */
private String status; private String status;
/** 是否归档0-未归档1-已归档 */ /** 是否归档0-未归档1-已归档 */
@@ -54,7 +54,7 @@ public class CcdiProject implements Serializable {
private Integer lsfxProjectId; private Integer lsfxProjectId;
/** 删除标志0-存在2-删除 */ /** 删除标志0-存在2-删除 */
@TableLogic @TableLogic(value = "0", delval = "2")
private String delFlag; private String delFlag;
/** 创建者 */ /** 创建者 */

View File

@@ -0,0 +1,24 @@
package com.ruoyi.ccdi.project.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 当前登录用户的项目访问范围。
*/
@Data
@AllArgsConstructor
public class ProjectAccessScope {
/** 当前用户名 */
private String username;
/** 是否可查看全部项目 */
private boolean viewAllProjects;
/** 是否超级管理员 */
private boolean superAdmin;
/** 是否项目管理员 */
private boolean projectManager;
}

View File

@@ -40,6 +40,9 @@ public class CcdiBankStatementQueryDTO {
/** 本方主体 */ /** 本方主体 */
private List<String> ourSubjects; private List<String> ourSubjects;
/** 本方证件号 */
private List<String> ourCertNos;
/** 本方银行 */ /** 本方银行 */
private List<String> ourBanks; private List<String> ourBanks;

View File

@@ -10,6 +10,9 @@ import java.math.BigDecimal;
@Data @Data
public class CcdiFundGraphManualEdgeSaveDTO { public class CcdiFundGraphManualEdgeSaveDTO {
/** 当前项目ID仅用于写权限校验不参与手工资金流归属过滤 */
private Long projectId;
/** 起点主体object_key为空时默认使用当前查询中心 */ /** 起点主体object_key为空时默认使用当前查询中心 */
private String fromObjectKey; private String fromObjectKey;

View File

@@ -0,0 +1,19 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 外部人员预警查询DTO
*/
@Data
public class CcdiProjectExternalPersonQueryDTO {
/** 项目ID */
private Long projectId;
/** 页码 */
private Integer pageNum;
/** 每页数量 */
private Integer pageSize;
}

View File

@@ -0,0 +1,41 @@
package com.ruoyi.ccdi.project.domain.dto;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Data;
/**
* 外部人员模型命中人员查询DTO
*/
@Data
public class CcdiProjectExternalRiskModelPeopleQueryDTO {
/** 项目ID */
private Long projectId;
/** 模型编码 */
private List<String> modelCodes;
/** 匹配方式 */
private String matchMode;
/** 关键字 */
private String keyword;
/** 页码 */
private Integer pageNum;
/** 每页数量 */
private Integer pageSize;
public String getModelCodesCsv() {
if (modelCodes == null || modelCodes.isEmpty()) {
return null;
}
return modelCodes.stream()
.filter(item -> item != null && !item.isBlank())
.map(String::trim)
.distinct()
.collect(Collectors.joining(","));
}
}

View File

@@ -0,0 +1,36 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 项目新增贷款查询入参
*/
@Data
public class CcdiProjectIncreaseLendingQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 柜员号 */
private String staffId;
/** 员工身份证号 */
private String staffIdCard;
/** 审核人柜员号 */
private String approver;
/** 发放日期起 */
private String loanStartDate;
/** 发放日期止 */
private String loanEndDate;
/** 页码 */
private Integer pageNum = 1;
/** 每页条数 */
private Integer pageSize = 10;
}

View File

@@ -14,4 +14,7 @@ public class CcdiProjectQueryDTO {
/** 项目状态 */ /** 项目状态 */
private String status; private String status;
/** 是否查询已删除项目列表 */
private Boolean includeDeleted;
} }

View File

@@ -0,0 +1,36 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 项目结果页排除可疑保存DTO
*/
@Data
public class CcdiProjectRiskExclusionSaveDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 人员证件号 */
private String staffIdCard;
/** 规则编码 */
@NotBlank(message = "规则编码不能为空")
private String ruleCode;
/** 排除类型STATEMENT/OBJECT */
@NotBlank(message = "排除类型不能为空")
private String exclusionType;
/** 流水ID */
private Long bankStatementId;
/** 排除原因 */
@NotBlank(message = "排除原因不能为空")
@Size(max = 1000, message = "排除原因不能超过1000个字符")
private String excludeReason;
}

View File

@@ -11,6 +11,9 @@ public class CcdiProjectSuspiciousTransactionQueryDTO {
/** 项目ID */ /** 项目ID */
private Long projectId; private Long projectId;
/** 模型编码 */
private String modelCode;
/** 涉疑类型 */ /** 涉疑类型 */
private String suspiciousType; private String suspiciousType;
@@ -19,4 +22,7 @@ public class CcdiProjectSuspiciousTransactionQueryDTO {
/** 每页数量 */ /** 每页数量 */
private Integer pageSize; private Integer pageSize;
/** 是否包含外部人员预警分支 */
private Boolean includeExternalPerson;
} }

View File

@@ -0,0 +1,25 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 未提交账户查询参数
*/
@Data
public class CcdiProjectUnsubmittedAccountQueryDTO {
/**
* 项目ID
*/
private Long projectId;
/**
* 主体名称
*/
private String subjectName;
/**
* 未提交账号
*/
private String accountNo;
}

View File

@@ -5,7 +5,7 @@ import lombok.Data;
import java.util.List; import java.util.List;
/** /**
* 拉取本行信息提交参数 * 拉取行内/金综流水提交参数
*/ */
@Data @Data
public class CcdiPullBankInfoSubmitDTO { public class CcdiPullBankInfoSubmitDTO {
@@ -16,6 +16,9 @@ public class CcdiPullBankInfoSubmitDTO {
/** 身份证号列表 */ /** 身份证号列表 */
private List<String> idCards; private List<String> idCards;
/** 数据渠道编码ZJRCU-行内JZL-金综 */
private String dataChannelCode;
/** 开始日期 */ /** 开始日期 */
private String startDate; private String startDate;

View File

@@ -8,6 +8,9 @@ import lombok.Data;
@Data @Data
public class CcdiRelationGraphSuspectedEnterpriseQueryDTO { public class CcdiRelationGraphSuspectedEnterpriseQueryDTO {
/** 项目ID */
private Long projectId;
/** 姓名 */ /** 姓名 */
private String personName; private String personName;

View File

@@ -194,8 +194,8 @@ public class CcdiBankStatement implements Serializable {
BeanUtils.copyProperties(item, entity); BeanUtils.copyProperties(item, entity);
// 4. 手动映射字段名不一致的情况 // 4. 手动映射字段名不一致的情况
entity.setLeAccountNo(item.getAccountMaskNo()); entity.setLeAccountNo(item.getAccountNo());
entity.setCustomerAccountNo(item.getCustomerAccountMaskNo()); entity.setCustomerAccountNo(item.getCustomerAccountNo());
entity.setLeAccountName(item.getLeName()); entity.setLeAccountName(item.getLeName());
entity.setAmountDr(item.getDrAmount()); entity.setAmountDr(item.getDrAmount());
entity.setAmountCr(item.getCrAmount()); entity.setAmountCr(item.getCrAmount());
@@ -207,6 +207,7 @@ public class CcdiBankStatement implements Serializable {
entity.setBatchSequence(item.getUploadSequnceNumber()); entity.setBatchSequence(item.getUploadSequnceNumber());
entity.setCustomerCertNo(item.getCustomerCertNo()); entity.setCustomerCertNo(item.getCustomerCertNo());
entity.setCustomerSocialCreditCode(item.getCustomerSocialCreditCode()); entity.setCustomerSocialCreditCode(item.getCustomerSocialCreditCode());
entity.setCretNo(normalizeCertNo(item.getCretNo()));
// 5. 特殊字段处理 // 5. 特殊字段处理
entity.setMetaJson(null); // 根据文档要求强制设为 null entity.setMetaJson(null); // 根据文档要求强制设为 null
@@ -219,4 +220,19 @@ public class CcdiBankStatement implements Serializable {
throw new RuntimeException("流水数据转换失败", e); throw new RuntimeException("流水数据转换失败", e);
} }
} }
private static String normalizeCertNo(String value) {
if (value == null || value.isBlank()) {
return null;
}
String normalized = value.trim()
.replace('', '-')
.replace('—', '-')
.replace('', '-');
int separatorIndex = normalized.indexOf('-');
if (separatorIndex >= 0) {
normalized = normalized.substring(0, separatorIndex).trim();
}
return normalized.isEmpty() ? null : normalized;
}
} }

View File

@@ -0,0 +1,58 @@
package com.ruoyi.ccdi.project.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 项目结果页排除可疑记录
*/
@Data
@TableName("ccdi_project_risk_exclusion")
public class CcdiProjectRiskExclusion implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO)
private Long id;
/** 项目ID */
private Long projectId;
/** 人员证件号 */
private String staffIdCard;
/** 规则编码 */
private String ruleCode;
/** 排除类型STATEMENT/OBJECT */
private String exclusionType;
/** 流水ID */
private Long bankStatementId;
/** 排除原因 */
private String excludeReason;
/** 创建者 */
private String createBy;
/** 创建时间 */
private Date createTime;
/** 更新者 */
private String updateBy;
/** 更新时间 */
private Date updateTime;
/** 备注 */
private String remark;
}

View File

@@ -1,6 +1,7 @@
package com.ruoyi.ccdi.project.domain.excel; package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel;
import java.math.BigDecimal;
import lombok.Data; import lombok.Data;
/** /**
@@ -26,4 +27,10 @@ public class CcdiProjectAbnormalAccountExcel {
@Excel(name = "状态") @Excel(name = "状态")
private String status; private String status;
@Excel(name = "命中原因")
private String reasonDetail;
@Excel(name = "涉及金额")
private BigDecimal involvedAmount;
} }

View File

@@ -0,0 +1,35 @@
package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
/**
* 外部人员预警导出对象
*/
@Data
public class CcdiProjectExternalPersonWarningExcel {
@Excel(name = "姓名")
private String name;
@Excel(name = "证件号")
private String idNo;
@Excel(name = "主体类型")
private String subjectType;
@Excel(name = "风险等级")
private String riskLevel;
@Excel(name = "命中模型数")
private Integer modelCount;
@Excel(name = "核心异常点")
private String riskPoint;
@Excel(name = "涉及对象")
private String relatedObject;
@Excel(name = "最近交易时间")
private String latestTradeTime;
}

View File

@@ -0,0 +1,84 @@
package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel;
import java.math.BigDecimal;
import lombok.Data;
/**
* 新增贷款查询导出对象
*/
@Data
public class CcdiProjectIncreaseLendingExcel {
/** 柜员号 */
@Excel(name = "柜员号")
private String staffId;
/** 员工姓名 */
@Excel(name = "员工姓名")
private String staffName;
/** 员工身份证 */
@Excel(name = "员工身份证")
private String staffIdCard;
/** 部门 */
@Excel(name = "部门")
private String deptName;
/** 合同编号 */
@Excel(name = "合同编号")
private String contractNo;
/** 放款机构号 */
@Excel(name = "放款机构号")
private String lendingOrgNo;
/** 借款人 */
@Excel(name = "借款人")
private String borrowerName;
/** 借款人证件号 */
@Excel(name = "借款人证件号")
private String borrowerCertNo;
/** 贷款产品 */
@Excel(name = "贷款产品")
private String loanProduct;
/** 合同金额 */
@Excel(name = "合同金额")
private BigDecimal contractAmount;
/** 当前余额 */
@Excel(name = "当前余额")
private BigDecimal loanBalance;
/** 发放日期 */
@Excel(name = "发放日期")
private String loanStartDate;
/** 到期日期 */
@Excel(name = "到期日期")
private String loanEndDate;
/** 合同状态 */
@Excel(name = "合同状态")
private String status;
/** 五级分类 */
@Excel(name = "五级分类")
private String fiveClassification;
/** 客户经理ID */
@Excel(name = "客户经理ID")
private String customerManagerId;
/** 客户经理 */
@Excel(name = "客户经理")
private String customerManagerName;
/** 审批人 */
@Excel(name = "审批人")
private String approver;
}

View File

@@ -0,0 +1,29 @@
package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
/**
* 风险模型命中人员导出对象
*/
@Data
public class CcdiProjectRiskModelPeopleExcel {
@Excel(name = "风险主体")
private String personName;
@Excel(name = "主体类型")
private String subjectType;
@Excel(name = "证件号")
private String idNo;
@Excel(name = "部门/涉及对象")
private String scopeName;
@Excel(name = "命中模型")
private String modelNames;
@Excel(name = "异常标签")
private String hitTags;
}

View File

@@ -14,21 +14,30 @@ public class CcdiProjectSuspiciousTransactionExcel {
@Excel(name = "交易时间") @Excel(name = "交易时间")
private String trxDate; private String trxDate;
@Excel(name = "可疑人员") @Excel(name = "本方账户")
private String suspiciousPersonName; private String leAccountNo;
@Excel(name = "关联人") @Excel(name = "本方主体")
private String relatedPersonName; private String leAccountName;
@Excel(name = "对方名称")
private String customerAccountName;
@Excel(name = "对方账户")
private String customerAccountNo;
@Excel(name = "关联员工") @Excel(name = "关联员工")
private String relatedStaffDisplay; private String relatedStaffDisplay;
@Excel(name = "关系") @Excel(name = "摘要")
private String relationType; private String userMemo;
@Excel(name = "摘要/交易类型") @Excel(name = "交易类型")
private String summaryAndCashType; private String cashType;
@Excel(name = "交易金额") @Excel(name = "交易金额")
private BigDecimal displayAmount; private BigDecimal displayAmount;
@Excel(name = "异常标签")
private String hitTags;
} }

View File

@@ -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;
}

View File

@@ -0,0 +1,44 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 流水对手方聚合VO
*
* @author ruoyi
*/
@Data
public class CcdiBankStatementCounterpartyVO {
/** 对方名称 */
private String customerAccountName;
/** 对方名称是否为空 */
private Boolean nameEmpty;
/** 关联账号数 */
private Integer accountCount;
/** 流入合计 */
private BigDecimal inAmount;
/** 流入笔数 */
private Long inCount;
/** 流出合计 */
private BigDecimal outAmount;
/** 流出笔数 */
private Long outCount;
/** 总交易金额 */
private BigDecimal totalAmount;
/** 总笔数 */
private Long totalCount;
/** 最近交易时间 */
private String latestTrxDate;
}

View File

@@ -8,6 +8,9 @@ import lombok.Data;
@Data @Data
public class CcdiBankStatementHitTagVO { public class CcdiBankStatementHitTagVO {
/** 模型编码 */
private String modelCode;
/** 规则编码 */ /** 规则编码 */
private String ruleCode; private String ruleCode;

View File

@@ -1,5 +1,6 @@
package com.ruoyi.ccdi.project.domain.vo; package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import lombok.Data; import lombok.Data;
/** /**
@@ -19,4 +20,8 @@ public class CcdiProjectAbnormalAccountItemVO {
private String abnormalTime; private String abnormalTime;
private String status; private String status;
private String reasonDetail;
private BigDecimal involvedAmount;
} }

View File

@@ -0,0 +1,35 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 外部人员预警项
*/
@Data
public class CcdiProjectExternalPersonWarningItemVO {
private String name;
private String idNo;
private String subjectType;
private String riskLevel;
private String riskLevelType;
private Integer riskCount;
private Integer modelCount;
private String riskPoint;
private String relatedObject;
private String latestTradeTime;
private List<CcdiProjectRiskHitTagVO> riskPointTagList;
private String actionLabel;
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 外部人员预警分页
*/
@Data
public class CcdiProjectExternalPersonWarningVO {
private List<CcdiProjectExternalPersonWarningItemVO> rows;
private Long total;
private Long pageNum;
private Long pageSize;
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 外部人员风险等级汇总
*/
@Data
public class CcdiProjectExternalRiskSummaryVO {
private Integer total;
private Integer high;
private Integer medium;
private Integer low;
private Integer noRisk;
}

View File

@@ -24,6 +24,15 @@ public class CcdiProjectFamilyAssetLiabilityListItemVO {
/** 家庭总年收入 */ /** 家庭总年收入 */
private BigDecimal totalIncome; private BigDecimal totalIncome;
/** 本人年收入 */
private BigDecimal selfIncome;
/** 配偶年收入 */
private BigDecimal spouseIncome;
/** 本人入职年限 */
private Integer employmentYears;
/** 家庭总资产 */ /** 家庭总资产 */
private BigDecimal totalAsset; private BigDecimal totalAsset;
@@ -33,6 +42,12 @@ public class CcdiProjectFamilyAssetLiabilityListItemVO {
/** 收入负债对比金额 */ /** 收入负债对比金额 */
private BigDecimal comparisonAmount; private BigDecimal comparisonAmount;
/** 可解释收入 */
private BigDecimal explainableIncome;
/** 资产收入倍数 */
private BigDecimal assetIncomeRatio;
/** 风险等级编码 */ /** 风险等级编码 */
private String riskLevelCode; private String riskLevelCode;

View File

@@ -15,6 +15,12 @@ public class CcdiProjectFamilyIncomeDetailVO {
/** 配偶年收入 */ /** 配偶年收入 */
private BigDecimal spouseIncome; private BigDecimal spouseIncome;
/** 本人入职年限 */
private Integer employmentYears;
/** 家庭总年收入 */ /** 家庭总年收入 */
private BigDecimal totalIncome; private BigDecimal totalIncome;
/** 可解释收入 */
private BigDecimal explainableIncome;
} }

View File

@@ -0,0 +1,65 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import lombok.Data;
/**
* 项目新增贷款查询列表项
*/
@Data
public class CcdiProjectIncreaseLendingListItemVO {
/** 员工ID/柜员号 */
private String staffId;
/** 员工姓名 */
private String staffName;
/** 员工身份证号 */
private String staffIdCard;
/** 所属部门 */
private String deptName;
/** 合同编号 */
private String contractNo;
/** 放款机构号 */
private String lendingOrgNo;
/** 借款人名称 */
private String borrowerName;
/** 借款人证件号码 */
private String borrowerCertNo;
/** 贷款产品 */
private String loanProduct;
/** 合同金额 */
private BigDecimal contractAmount;
/** 当前贷款余额 */
private BigDecimal loanBalance;
/** 贷款发放日期 */
private String loanStartDate;
/** 贷款到期日期 */
private String loanEndDate;
/** 合同状态 */
private String status;
/** 五级分类 */
private String fiveClassification;
/** 客户经理ID */
private String customerManagerId;
/** 客户经理姓名 */
private String customerManagerName;
/** 审批人 */
private String approver;
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 项目新增贷款查询列表结果
*/
@Data
public class CcdiProjectIncreaseLendingListVO {
/** 列表数据 */
private List<CcdiProjectIncreaseLendingListItemVO> rows;
/** 总数 */
private Long total;
}

View File

@@ -3,6 +3,8 @@ package com.ruoyi.ccdi.project.domain.vo;
import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; 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.ArrayList;
import java.util.List; import java.util.List;
import lombok.Data; import lombok.Data;
@@ -21,13 +23,21 @@ public class CcdiProjectOverviewReportVO {
private CcdiProjectOverviewDashboardVO dashboard = new CcdiProjectOverviewDashboardVO(); private CcdiProjectOverviewDashboardVO dashboard = new CcdiProjectOverviewDashboardVO();
private CcdiProjectExternalRiskSummaryVO externalRiskSummary = new CcdiProjectExternalRiskSummaryVO();
private List<CcdiProjectOverviewReportModelSummaryVO> modelSummaries = new ArrayList<>(); private List<CcdiProjectOverviewReportModelSummaryVO> modelSummaries = new ArrayList<>();
private List<CcdiProjectOverviewReportModelSummaryVO> externalModelSummaries = new ArrayList<>();
private List<CcdiProjectRiskModelPeopleItemVO> riskPeople = new ArrayList<>(); private List<CcdiProjectRiskModelPeopleItemVO> riskPeople = new ArrayList<>();
private List<CcdiProjectExternalPersonWarningExcel> externalPersonWarnings = new ArrayList<>();
private List<CcdiProjectOverviewReportSuspiciousTransactionVO> suspiciousTransactions = new ArrayList<>(); private List<CcdiProjectOverviewReportSuspiciousTransactionVO> suspiciousTransactions = new ArrayList<>();
private List<CcdiProjectEmployeeCreditNegativeExcel> illegalPeople = new ArrayList<>(); private List<CcdiProjectEmployeeCreditNegativeExcel> illegalPeople = new ArrayList<>();
private List<CcdiProjectAbnormalAccountExcel> abnormalAccounts = new ArrayList<>(); private List<CcdiProjectAbnormalAccountExcel> abnormalAccounts = new ArrayList<>();
private List<CcdiProjectUnsubmittedAccountFlowExcel> unsubmittedAccountFlows = new ArrayList<>();
} }

View File

@@ -13,4 +13,8 @@ public class CcdiProjectOverviewStatVO {
private String label; private String label;
private Integer value; private Integer value;
private Integer employeeValue;
private Integer externalValue;
} }

View File

@@ -12,6 +12,8 @@ public class CcdiProjectPersonAnalysisObjectRecordVO {
private String modelCode; private String modelCode;
private String ruleCode;
private String title; private String title;
private String subtitle; private String subtitle;

View File

@@ -17,4 +17,6 @@ public class CcdiProjectRiskHitTagVO {
private String ruleName; private String ruleName;
private String riskLevel; private String riskLevel;
private String reasonDetail;
} }

View File

@@ -26,4 +26,7 @@ public class CcdiProjectStatusCountsVO {
/** 打标失败项目数(状态4) */ /** 打标失败项目数(状态4) */
private Long status4; private Long status4;
/** 已删除项目数(状态5) */
private Long status5;
} }

View File

@@ -3,6 +3,8 @@ package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/** /**
* 涉疑交易明细行 * 涉疑交易明细行
@@ -14,6 +16,14 @@ public class CcdiProjectSuspiciousTransactionItemVO {
private String trxDate; private String trxDate;
private String leAccountNo;
private String leAccountName;
private String customerAccountName;
private String customerAccountNo;
private String suspiciousPersonName; private String suspiciousPersonName;
private String relatedPersonName; private String relatedPersonName;
@@ -35,4 +45,6 @@ public class CcdiProjectSuspiciousTransactionItemVO {
private Boolean hasNameListHit; private Boolean hasNameListHit;
private String nameListHitType; private String nameListHitType;
private List<CcdiBankStatementHitTagVO> hitTags = new ArrayList<>();
} }

View File

@@ -0,0 +1,35 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import lombok.Data;
/**
* 未提交账户流水行
*/
@Data
public class CcdiProjectUnsubmittedAccountFlowVO {
private Long bankStatementId;
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;
}

View File

@@ -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;
}

View File

@@ -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<CcdiProjectUnsubmittedAccountItemVO> accounts = new ArrayList<>();
}

View File

@@ -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<CcdiProjectUnsubmittedAccountSubjectVO> subjects = new ArrayList<>();
}

View File

@@ -61,4 +61,13 @@ public class CcdiProjectVO {
/** 创建者姓名(真实姓名) */ /** 创建者姓名(真实姓名) */
private String createByName; private String createByName;
/** 是否当前用户创建 */
private Boolean ownedByCurrentUser;
/** 当前用户是否可操作 */
private Boolean canOperate;
/** 当前用户是否可删除 */
private Boolean canDelete;
} }

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement; import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementCounterpartyVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
@@ -38,6 +39,9 @@ public interface CcdiBankStatementMapper extends BaseMapper<CcdiBankStatement> {
Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page, Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page,
@Param("query") CcdiBankStatementQueryDTO query); @Param("query") CcdiBankStatementQueryDTO query);
Page<CcdiBankStatementCounterpartyVO> selectCounterpartyPage(Page<CcdiBankStatementCounterpartyVO> page,
@Param("query") CcdiBankStatementQueryDTO query);
List<CcdiBankStatementListVO> selectStatementListForExport(@Param("query") CcdiBankStatementQueryDTO query); List<CcdiBankStatementListVO> selectStatementListForExport(@Param("query") CcdiBankStatementQueryDTO query);
CcdiBankStatementDetailVO selectStatementDetailById(@Param("bankStatementId") Long bankStatementId); CcdiBankStatementDetailVO selectStatementDetailById(@Param("bankStatementId") Long bankStatementId);

View File

@@ -90,6 +90,36 @@ public interface CcdiBankTagAnalysisMapper {
List<BankTagStatementHitVO> selectLargeTransferStatements(@Param("projectId") Long projectId, List<BankTagStatementHitVO> selectLargeTransferStatements(@Param("projectId") Long projectId,
@Param("threshold") BigDecimal threshold); @Param("threshold") BigDecimal threshold);
/**
* 外部人员单笔大额交易
*
* @param projectId 项目ID
* @param threshold 单笔大额阈值
* @return 流水命中结果
*/
List<BankTagStatementHitVO> selectExternalSingleLargeAmountStatements(@Param("projectId") Long projectId,
@Param("threshold") BigDecimal threshold);
/**
* 外部人员累计交易超限
*
* @param projectId 项目ID
* @param threshold 累计交易阈值
* @return 对象命中结果
*/
List<BankTagObjectHitVO> selectExternalCumulativeTransactionAmountObjects(@Param("projectId") Long projectId,
@Param("threshold") BigDecimal threshold);
/**
* 外部人员年流水超限
*
* @param projectId 项目ID
* @param threshold 年流水阈值
* @return 对象命中结果
*/
List<BankTagObjectHitVO> selectExternalAnnualTurnoverObjects(@Param("projectId") Long projectId,
@Param("threshold") BigDecimal threshold);
/** /**
* 与客户之间非正常资金往来 * 与客户之间非正常资金往来
* *
@@ -126,6 +156,26 @@ public interface CcdiBankTagAnalysisMapper {
*/ */
List<BankTagStatementHitVO> selectGamblingSensitiveKeywordStatements(@Param("projectId") Long projectId); List<BankTagStatementHitVO> selectGamblingSensitiveKeywordStatements(@Param("projectId") Long projectId);
/**
* 外部人员疑似赌博摘要
*
* @param projectId 项目ID
* @return 流水命中结果
*/
List<BankTagStatementHitVO> selectExternalGamblingMemoStatements(@Param("projectId") Long projectId);
/**
* 外部人员同日多对手方疑似赌博交易
*
* @param projectId 项目ID
* @param amountMinThreshold 可疑金额下限
* @param amountMaxThreshold 可疑金额上限
* @return 对象命中结果
*/
List<BankTagObjectHitVO> selectExternalMultiPartyGamblingTransferObjects(@Param("projectId") Long projectId,
@Param("amountMinThreshold") BigDecimal amountMinThreshold,
@Param("amountMaxThreshold") BigDecimal amountMaxThreshold);
/** /**
* 特殊金额交易 * 特殊金额交易
* *
@@ -134,6 +184,22 @@ public interface CcdiBankTagAnalysisMapper {
*/ */
List<BankTagStatementHitVO> selectSpecialAmountTransactionStatements(@Param("projectId") Long projectId); List<BankTagStatementHitVO> selectSpecialAmountTransactionStatements(@Param("projectId") Long projectId);
/**
* 外部人员与员工或员工亲属交易
*
* @param projectId 项目ID
* @return 流水命中结果
*/
List<BankTagStatementHitVO> selectExternalToStaffOrFamilyTransactionStatements(@Param("projectId") Long projectId);
/**
* 外部人员夜间交易
*
* @param projectId 项目ID
* @return 流水命中结果
*/
List<BankTagStatementHitVO> selectExternalNightTransactionStatements(@Param("projectId") Long projectId);
/** /**
* 月度固定收入疑似兼职 * 月度固定收入疑似兼职
* *

View File

@@ -26,11 +26,15 @@ public interface CcdiBankTagResultMapper extends BaseMapper<CcdiBankTagResult> {
* *
* @param projectId 项目ID * @param projectId 项目ID
* @param bankStatementIds 流水ID列表 * @param bankStatementIds 流水ID列表
* @param modelCode 模型编码
* @param suspiciousType 预警类型
* @return 命中的异常标签列表 * @return 命中的异常标签列表
*/ */
List<CcdiBankStatementHitTagVO> selectStatementTagsByProjectAndStatementIds( List<CcdiBankStatementHitTagVO> selectStatementTagsByProjectAndStatementIds(
@Param("projectId") Long projectId, @Param("projectId") Long projectId,
@Param("bankStatementIds") List<Long> bankStatementIds @Param("bankStatementIds") List<Long> bankStatementIds,
@Param("modelCode") String modelCode,
@Param("suspiciousType") String suspiciousType
); );
/** /**

View File

@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.project.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
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.ProjectAccessScope;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
@@ -25,7 +26,9 @@ public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
* @param queryDTO 查询条件 * @param queryDTO 查询条件
* @return 分页结果 * @return 分页结果
*/ */
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, @Param("queryDTO") CcdiProjectQueryDTO queryDTO); Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page,
@Param("queryDTO") CcdiProjectQueryDTO queryDTO,
@Param("scope") ProjectAccessScope scope);
/** /**
* 查询历史项目列表 * 查询历史项目列表
@@ -33,7 +36,34 @@ public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
* @param queryDTO 查询条件 * @param queryDTO 查询条件
* @return 历史项目列表 * @return 历史项目列表
*/ */
List<CcdiProjectHistoryListItemVO> selectHistoryProjects(@Param("queryDTO") CcdiProjectQueryDTO queryDTO); List<CcdiProjectHistoryListItemVO> selectHistoryProjects(@Param("queryDTO") CcdiProjectQueryDTO queryDTO,
@Param("scope") ProjectAccessScope scope);
/**
* 业务逻辑删除项目,仅更新项目主表状态和删除标记。
*
* @param projectId 项目ID
* @param operator 操作人
* @return 更新行数
*/
int markProjectDeleted(@Param("projectId") Long projectId, @Param("operator") String operator);
/**
* 恢复已删除项目为已完成。
*
* @param projectId 项目ID
* @param operator 操作人
* @return 更新行数
*/
int restoreDeletedProject(@Param("projectId") Long projectId, @Param("operator") String operator);
/**
* 统计已删除项目数量。
*
* @param scope 项目访问范围
* @return 已删除项目数量
*/
Long selectDeletedProjectCount(@Param("scope") ProjectAccessScope scope);
/** /**
* 更新项目总人数与风险人数 * 更新项目总人数与风险人数

View File

@@ -4,13 +4,18 @@ 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.CcdiProjectAbnormalAccountQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.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.dto.CcdiProjectUnsubmittedAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
@@ -20,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.CcdiProjectRiskModelCardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO; 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.List;
import java.util.Map; import java.util.Map;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@@ -121,6 +128,96 @@ public interface CcdiProjectOverviewMapper {
@Param("query") CcdiProjectRiskModelPeopleQueryDTO query @Param("query") CcdiProjectRiskModelPeopleQueryDTO query
); );
/**
* 查询风险模型命中人员导出列表
*
* @param query 查询条件
* @return 命中人员列表
*/
List<CcdiProjectRiskModelPeopleItemVO> selectRiskModelPeopleList(
@Param("query") CcdiProjectRiskModelPeopleQueryDTO query
);
/**
* 分页查询外部人员预警
*
* @param page 分页参数
* @param query 查询条件
* @return 外部人员预警分页
*/
Page<CcdiProjectExternalPersonWarningItemVO> selectExternalPersonWarningPage(
Page<CcdiProjectExternalPersonWarningItemVO> page,
@Param("query") CcdiProjectExternalPersonQueryDTO query
);
/**
* 查询外部人员预警导出列表
*
* @param projectId 项目ID
* @return 外部人员预警列表
*/
List<CcdiProjectExternalPersonWarningItemVO> selectExternalPersonWarningList(@Param("projectId") Long projectId);
/**
* 查询外部人员风险等级汇总
*
* @param projectId 项目ID
* @return 外部人员风险等级汇总
*/
CcdiProjectExternalRiskSummaryVO selectExternalRiskSummaryByProjectId(@Param("projectId") Long projectId);
/**
* 判断项目是否存在外部人员主体
*
* @param projectId 项目ID
* @return 存在时返回1否则返回空
*/
Integer selectExternalPersonSubjectExistsByProjectId(@Param("projectId") Long projectId);
/**
* 查询外部人员预警模型卡片
*
* @param projectId 项目ID
* @return 模型卡片
*/
List<CcdiProjectRiskModelCardVO> selectExternalRiskModelCardsByProjectId(@Param("projectId") Long projectId);
/**
* 分页查询外部人员模型命中人员
*
* @param page 分页参数
* @param query 查询条件
* @return 命中人员分页
*/
Page<CcdiProjectRiskModelPeopleItemVO> selectExternalRiskModelPeoplePage(
Page<CcdiProjectRiskModelPeopleItemVO> page,
@Param("query") CcdiProjectExternalRiskModelPeopleQueryDTO query
);
/**
* 查询外部人员模型命中人员导出列表
*
* @param query 查询条件
* @return 命中人员列表
*/
List<CcdiProjectRiskModelPeopleItemVO> selectExternalRiskModelPeopleList(
@Param("query") CcdiProjectExternalRiskModelPeopleQueryDTO query
);
/**
* 查询外部人员命中标签
*
* @param projectId 项目ID
* @param certNo 外部人员证件号
* @param selectedModelCodes 已选模型编码CSV可为空
* @return 命中标签列表
*/
List<CcdiProjectRiskHitTagVO> selectExternalRiskHitTagsByScope(
@Param("projectId") Long projectId,
@Param("certNo") String certNo,
@Param("selectedModelCodes") String selectedModelCodes
);
/** /**
* 分页查询涉疑交易明细 * 分页查询涉疑交易明细
* *
@@ -165,6 +262,26 @@ public interface CcdiProjectOverviewMapper {
*/ */
List<CcdiProjectAbnormalAccountItemVO> selectAbnormalAccountList(@Param("projectId") Long projectId); List<CcdiProjectAbnormalAccountItemVO> selectAbnormalAccountList(@Param("projectId") Long projectId);
/**
* 查询未提交账户列表
*
* @param query 查询条件
* @return 未提交账户列表
*/
List<CcdiProjectUnsubmittedAccountItemVO> selectUnsubmittedAccountList(
@Param("query") CcdiProjectUnsubmittedAccountQueryDTO query
);
/**
* 查询未提交账户流水
*
* @param query 查询条件
* @return 未提交账户流水
*/
List<CcdiProjectUnsubmittedAccountFlowVO> selectUnsubmittedAccountFlowList(
@Param("query") CcdiProjectUnsubmittedAccountQueryDTO query
);
/** /**
* 查询项目员工负面征信导出列表 * 查询项目员工负面征信导出列表
* *
@@ -240,4 +357,5 @@ public interface CcdiProjectOverviewMapper {
* @return 风险人数汇总 * @return 风险人数汇总
*/ */
Map<String, Object> selectRiskCountSummaryByProjectId(@Param("projectId") Long projectId); Map<String, Object> selectRiskCountSummaryByProjectId(@Param("projectId") Long projectId);
} }

View File

@@ -0,0 +1,27 @@
package com.ruoyi.ccdi.project.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectRiskExclusion;
import org.apache.ibatis.annotations.Param;
/**
* 项目结果页排除可疑记录 Mapper
*/
public interface CcdiProjectRiskExclusionMapper extends BaseMapper<CcdiProjectRiskExclusion> {
/**
* 幂等写入排除记录
*
* @param exclusion 排除记录
* @return 写入条数
*/
int upsertExclusion(@Param("exclusion") CcdiProjectRiskExclusion exclusion);
/**
* 查询待排除命中是否存在
*
* @param exclusion 排除记录
* @return 命中数量
*/
int countExistingRiskHit(@Param("exclusion") CcdiProjectRiskExclusion exclusion);
}

View File

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectIncreaseLendingQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectIncreaseLendingExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseSupplierVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseSupplierVO;
@@ -15,6 +17,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyDebtItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyDebtItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectIncreaseLendingListItemVO;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -145,6 +148,28 @@ public interface CcdiProjectSpecialCheckMapper {
@Param("query") CcdiProjectExtendedTransferQueryDTO queryDTO @Param("query") CcdiProjectExtendedTransferQueryDTO queryDTO
); );
/**
* 查询项目新增贷款列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 分页结果
*/
Page<CcdiProjectIncreaseLendingListItemVO> selectIncreaseLendingPage(
@Param("page") Page<CcdiProjectIncreaseLendingListItemVO> page,
@Param("query") CcdiProjectIncreaseLendingQueryDTO queryDTO
);
/**
* 查询项目新增贷款导出列表
*
* @param queryDTO 查询条件
* @return 导出列表
*/
List<CcdiProjectIncreaseLendingExcel> selectIncreaseLendingExportList(
@Param("query") CcdiProjectIncreaseLendingQueryDTO queryDTO
);
/** /**
* 查询专项核查调动拓展详情 * 查询专项核查调动拓展详情
* *

View File

@@ -0,0 +1,213 @@
package com.ruoyi.ccdi.project.service;
import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.ProjectAccessScope;
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
import com.ruoyi.ccdi.project.domain.entity.CcdiEvidence;
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
import com.ruoyi.ccdi.project.mapper.CcdiEvidenceMapper;
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
/**
* 项目访问控制。
*/
@Service
public class CcdiProjectAccessService {
private static final String ROLE_ADMIN = "admin";
private static final String ROLE_MANAGER = "manager";
@Resource
private CcdiProjectMapper projectMapper;
@Resource
private CcdiBankStatementMapper bankStatementMapper;
@Resource
private CcdiFileUploadRecordMapper fileUploadRecordMapper;
@Resource
private CcdiEvidenceMapper evidenceMapper;
public ProjectAccessScope buildCurrentScope() {
LoginUser loginUser = SecurityUtils.getLoginUser();
String username = SecurityUtils.getUsername();
boolean superAdmin = isSuperAdmin(loginUser);
boolean projectManager = hasRole(loginUser, ROLE_MANAGER);
return new ProjectAccessScope(username, superAdmin || projectManager, superAdmin, projectManager);
}
public boolean canOperate(CcdiProject project) {
if (project == null) {
return false;
}
ProjectAccessScope scope = buildCurrentScope();
return scope.isSuperAdmin() || Objects.equals(scope.getUsername(), project.getCreateBy());
}
public boolean canDelete(CcdiProject project) {
if (project == null) {
return false;
}
ProjectAccessScope scope = buildCurrentScope();
return isProjectAdmin(scope) || Objects.equals(scope.getUsername(), project.getCreateBy());
}
public void assertCanRead(Long projectId) {
CcdiProject project = getRequiredProject(projectId);
assertProjectNotDeleted(project);
ProjectAccessScope scope = buildCurrentScope();
if (scope.isViewAllProjects() || Objects.equals(scope.getUsername(), project.getCreateBy())) {
return;
}
throw new ServiceException("无权查看该项目");
}
public void assertCanOperate(Long projectId) {
CcdiProject project = getRequiredProject(projectId);
assertProjectNotDeleted(project);
if (canOperate(project)) {
return;
}
throw new ServiceException("无权操作该项目");
}
public void assertCanDelete(Long projectId) {
CcdiProject project = getRequiredProject(projectId);
assertProjectNotDeleted(project);
if (canDelete(project)) {
return;
}
throw new ServiceException("无权删除该项目");
}
public void assertCanManageDeletedProjects() {
if (isProjectAdmin(buildCurrentScope())) {
return;
}
throw new ServiceException("无权管理已删除项目");
}
public void assertCanReadByBankStatementId(Long bankStatementId) {
CcdiBankStatement statement = getRequiredBankStatement(bankStatementId);
assertCanRead(statement.getProjectId());
}
public void assertCanReadByFileRecordId(Long fileRecordId) {
CcdiFileUploadRecord record = getRequiredFileRecord(fileRecordId);
assertCanRead(record.getProjectId());
}
public void assertCanOperateByFileRecordId(Long fileRecordId) {
CcdiFileUploadRecord record = getRequiredFileRecord(fileRecordId);
assertCanOperate(record.getProjectId());
}
public void assertCanReadByEvidenceId(Long evidenceId) {
CcdiEvidence evidence = getRequiredEvidence(evidenceId);
assertCanRead(evidence.getProjectId());
}
public void assertSourceProjectsReadable(List<Long> sourceProjectIds) {
if (CollectionUtils.isEmpty(sourceProjectIds)) {
return;
}
for (Long sourceProjectId : sourceProjectIds) {
assertCanRead(sourceProjectId);
}
}
private CcdiProject getRequiredProject(Long projectId) {
if (projectId == null) {
throw new ServiceException("项目ID不能为空");
}
CcdiProject project = projectMapper.selectById(projectId);
if (project == null) {
throw new ServiceException("项目不存在");
}
return project;
}
private void assertProjectNotDeleted(CcdiProject project) {
if (project == null) {
return;
}
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus()) || "2".equals(project.getDelFlag())) {
throw new ServiceException("项目已删除");
}
}
private boolean isProjectAdmin(ProjectAccessScope scope) {
return scope != null && (scope.isSuperAdmin() || scope.isProjectManager());
}
private CcdiBankStatement getRequiredBankStatement(Long bankStatementId) {
if (bankStatementId == null) {
throw new ServiceException("流水ID不能为空");
}
CcdiBankStatement statement = bankStatementMapper.selectById(bankStatementId);
if (statement == null) {
throw new ServiceException("流水记录不存在");
}
return statement;
}
private CcdiFileUploadRecord getRequiredFileRecord(Long fileRecordId) {
if (fileRecordId == null) {
throw new ServiceException("文件记录ID不能为空");
}
CcdiFileUploadRecord record = fileUploadRecordMapper.selectById(fileRecordId);
if (record == null) {
throw new ServiceException("文件记录不存在");
}
return record;
}
private CcdiEvidence getRequiredEvidence(Long evidenceId) {
if (evidenceId == null) {
throw new ServiceException("证据ID不能为空");
}
CcdiEvidence evidence = evidenceMapper.selectById(evidenceId);
if (evidence == null) {
throw new ServiceException("证据不存在");
}
return evidence;
}
private boolean isSuperAdmin(LoginUser loginUser) {
if (loginUser == null) {
return false;
}
if (SecurityUtils.isAdmin(loginUser.getUserId())) {
return true;
}
return hasRole(loginUser, ROLE_ADMIN);
}
private boolean hasRole(LoginUser loginUser, String roleKey) {
if (loginUser == null || loginUser.getUser() == null
|| CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
return false;
}
for (SysRole role : loginUser.getUser().getRoles()) {
if (roleKey.equals(role.getRoleKey())) {
return true;
}
}
return false;
}
}

View File

@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.project.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementCounterpartyVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
@@ -34,6 +35,16 @@ public interface ICcdiBankStatementService {
Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page, Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page,
CcdiBankStatementQueryDTO queryDTO); CcdiBankStatementQueryDTO queryDTO);
/**
* 分页查询对手方聚合列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 分页结果
*/
Page<CcdiBankStatementCounterpartyVO> selectCounterpartyPage(Page<CcdiBankStatementCounterpartyVO> page,
CcdiBankStatementQueryDTO queryDTO);
/** /**
* 查询导出列表 * 查询导出列表
* *
@@ -49,4 +60,18 @@ public interface ICcdiBankStatementService {
* @return 详情 * @return 详情
*/ */
CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId); CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId);
/**
* 按涉疑交易筛选范围查询流水详情
*
* @param bankStatementId 流水ID
* @param modelCode 模型编码
* @param suspiciousType 预警类型
* @return 详情
*/
CcdiBankStatementDetailVO getStatementDetail(
Long bankStatementId,
String modelCode,
String suspiciousType
);
} }

View File

@@ -35,10 +35,11 @@ public interface ICcdiFileUploadService {
List<String> parseIdCardFile(MultipartFile file); List<String> parseIdCardFile(MultipartFile file);
/** /**
* 提交拉取本行信息任务 * 提交拉取行内/金综流水任务
* *
* @param projectId 项目ID * @param projectId 项目ID
* @param idCards 身份证号列表 * @param idCards 身份证号列表
* @param dataChannelCode 数据渠道编码
* @param startDate 开始日期 * @param startDate 开始日期
* @param endDate 结束日期 * @param endDate 结束日期
* @param userId 当前登录用户ID * @param userId 当前登录用户ID
@@ -47,6 +48,7 @@ public interface ICcdiFileUploadService {
*/ */
String submitPullBankInfo(Long projectId, String submitPullBankInfo(Long projectId,
List<String> idCards, List<String> idCards,
String dataChannelCode,
String startDate, String startDate,
String endDate, String endDate,
Long userId, Long userId,

View File

@@ -2,16 +2,25 @@ package com.ruoyi.ccdi.project.service;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.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.CcdiProjectRiskExclusionSaveDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; 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.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; 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.CcdiProjectRiskPeopleOverviewExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel; 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.CcdiProjectAbnormalAccountPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
@@ -19,6 +28,8 @@ 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 com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountFlowVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectUnsubmittedAccountVO;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
@@ -36,6 +47,13 @@ public interface ICcdiProjectOverviewService {
*/ */
CcdiProjectOverviewDashboardVO getDashboard(Long projectId); CcdiProjectOverviewDashboardVO getDashboard(Long projectId);
/**
* 排除单条可疑预警
*
* @param dto 排除参数
*/
void excludeRisk(CcdiProjectRiskExclusionSaveDTO dto);
/** /**
* 查询风险人员总览 * 查询风险人员总览
* *
@@ -82,6 +100,80 @@ public interface ICcdiProjectOverviewService {
return new CcdiProjectRiskModelPeopleVO(); return new CcdiProjectRiskModelPeopleVO();
} }
/**
* 导出风险模型命中人员
*
* @param queryDTO 查询条件
* @return 导出列表
*/
default List<CcdiProjectRiskModelPeopleExcel> exportRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
return List.of();
}
/**
* 查询外部人员预警
*
* @param queryDTO 查询条件
* @return 外部人员预警
*/
default CcdiProjectExternalPersonWarningVO getExternalPersonWarnings(CcdiProjectExternalPersonQueryDTO queryDTO) {
return new CcdiProjectExternalPersonWarningVO();
}
/**
* 查询外部人员风险等级汇总
*
* @param projectId 项目ID
* @return 外部人员风险等级汇总
*/
default CcdiProjectExternalRiskSummaryVO getExternalRiskSummary(Long projectId) {
return new CcdiProjectExternalRiskSummaryVO();
}
/**
* 导出外部人员预警
*
* @param projectId 项目ID
* @return 导出列表
*/
default List<CcdiProjectExternalPersonWarningExcel> exportExternalPersonWarnings(Long projectId) {
return List.of();
}
/**
* 查询外部人员风险模型卡片
*
* @param projectId 项目ID
* @return 风险模型卡片
*/
default CcdiProjectRiskModelCardsVO getExternalRiskModelCards(Long projectId) {
return new CcdiProjectRiskModelCardsVO();
}
/**
* 查询外部人员风险模型命中人员
*
* @param queryDTO 查询条件
* @return 命中人员
*/
default CcdiProjectRiskModelPeopleVO getExternalRiskModelPeople(
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
) {
return new CcdiProjectRiskModelPeopleVO();
}
/**
* 导出外部人员风险模型命中人员
*
* @param queryDTO 查询条件
* @return 导出列表
*/
default List<CcdiProjectRiskModelPeopleExcel> exportExternalRiskModelPeople(
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
) {
return List.of();
}
/** /**
* 查询涉疑交易明细 * 查询涉疑交易明细
* *
@@ -125,6 +217,18 @@ public interface ICcdiProjectOverviewService {
default void exportRiskDetails(HttpServletResponse response, Long projectId) { default void exportRiskDetails(HttpServletResponse response, Long projectId) {
} }
/**
* 按涉疑交易筛选范围统一导出风险明细
*
* @param response 响应流
* @param queryDTO 涉疑交易筛选条件
*/
default void exportRiskDetails(
HttpServletResponse response,
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
) {
}
/** /**
* 一键导出结果总览报告 * 一键导出结果总览报告
* *
@@ -178,6 +282,52 @@ public interface ICcdiProjectOverviewService {
return List.of(); return List.of();
} }
/**
* 查询未提交账户
*
* @param queryDTO 查询条件
* @return 未提交账户结果
*/
default CcdiProjectUnsubmittedAccountVO getUnsubmittedAccounts(
CcdiProjectUnsubmittedAccountQueryDTO queryDTO
) {
return new CcdiProjectUnsubmittedAccountVO();
}
/**
* 查询未提交账户流水
*
* @param queryDTO 查询条件
* @return 未提交账户流水
*/
default List<CcdiProjectUnsubmittedAccountFlowVO> getUnsubmittedAccountFlows(
CcdiProjectUnsubmittedAccountQueryDTO queryDTO
) {
return List.of();
}
/**
* 导出未提交账户流水
*
* @param projectId 项目ID
* @return 导出列表
*/
default List<CcdiProjectUnsubmittedAccountFlowExcel> exportUnsubmittedAccountFlows(Long projectId) {
return List.of();
}
/**
* 导出未提交账户流水
*
* @param queryDTO 查询条件
* @return 导出列表
*/
default List<CcdiProjectUnsubmittedAccountFlowExcel> exportUnsubmittedAccountFlows(
CcdiProjectUnsubmittedAccountQueryDTO queryDTO
) {
return List.of();
}
/** /**
* 重算结果总览员工结果并同步项目风险人数 * 重算结果总览员工结果并同步项目风险人数
* *

View File

@@ -36,9 +36,18 @@ public interface ICcdiProjectService {
* 删除项目 * 删除项目
* *
* @param projectId 项目ID * @param projectId 项目ID
* @param operator 操作人
* @return 是否成功 * @return 是否成功
*/ */
boolean deleteProject(Long projectId); boolean deleteProject(Long projectId, String operator);
/**
* 恢复已删除项目
*
* @param projectId 项目ID
* @param operator 操作人
*/
void restoreProject(Long projectId, String operator);
/** /**
* 查询项目详情 * 查询项目详情

View File

@@ -8,6 +8,8 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryD
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectIncreaseLendingQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectIncreaseLendingExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
@@ -16,6 +18,8 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectIncreaseLendingListVO;
import java.util.List;
/** /**
* 项目专项核查服务接口 * 项目专项核查服务接口
@@ -91,4 +95,20 @@ public interface ICcdiProjectSpecialCheckService {
* @return 详情结果 * @return 详情结果
*/ */
CcdiProjectExtendedTransferDetailVO getExtendedTransferDetail(CcdiProjectExtendedTransferDetailQueryDTO queryDTO); CcdiProjectExtendedTransferDetailVO getExtendedTransferDetail(CcdiProjectExtendedTransferDetailQueryDTO queryDTO);
/**
* 查询项目新增贷款列表
*
* @param queryDTO 查询条件
* @return 列表结果
*/
CcdiProjectIncreaseLendingListVO getIncreaseLendingList(CcdiProjectIncreaseLendingQueryDTO queryDTO);
/**
* 导出项目新增贷款列表
*
* @param queryDTO 查询条件
* @return 导出列表
*/
List<CcdiProjectIncreaseLendingExcel> exportIncreaseLendingList(CcdiProjectIncreaseLendingQueryDTO queryDTO);
} }

View File

@@ -27,10 +27,13 @@ public class BankTagRuleConfigResolver {
private static final Map<String, Set<String>> RULE_PARAM_MAPPING = Map.ofEntries( private static final Map<String, Set<String>> RULE_PARAM_MAPPING = Map.ofEntries(
Map.entry("SINGLE_LARGE_INCOME", Set.of("SINGLE_TRANSACTION_AMOUNT")), Map.entry("SINGLE_LARGE_INCOME", Set.of("SINGLE_TRANSACTION_AMOUNT")),
Map.entry("CUMULATIVE_INCOME", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")), Map.entry("CUMULATIVE_INCOME", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")),
Map.entry("EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")),
Map.entry("ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")), Map.entry("ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")),
Map.entry("EXTERNAL_ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")),
Map.entry("LARGE_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT")), Map.entry("LARGE_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT")),
Map.entry("FREQUENT_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT", "FREQUENT_CASH_DEPOSIT")), Map.entry("FREQUENT_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT", "FREQUENT_CASH_DEPOSIT")),
Map.entry("LARGE_TRANSFER", Set.of("FREQUENT_TRANSFER")), Map.entry("LARGE_TRANSFER", Set.of("FREQUENT_TRANSFER")),
Map.entry("EXTERNAL_SINGLE_LARGE_AMOUNT", Set.of("FREQUENT_TRANSFER")),
Map.entry("FOREX_BUY_AMT", Set.of("SINGLE_PURCHASE_AMOUNT")), Map.entry("FOREX_BUY_AMT", Set.of("SINGLE_PURCHASE_AMOUNT")),
Map.entry("FOREX_SELL_AMT", Set.of("SINGLE_SETTLEMENT_AMOUNT")), Map.entry("FOREX_SELL_AMT", Set.of("SINGLE_SETTLEMENT_AMOUNT")),
Map.entry("WITHDRAW_CNT", Set.of("WITHDRAW_CNT")), Map.entry("WITHDRAW_CNT", Set.of("WITHDRAW_CNT")),
@@ -38,10 +41,18 @@ public class BankTagRuleConfigResolver {
Map.entry("STOCK_TFR_LARGE", Set.of("STOCK_TFR_LARGE")), Map.entry("STOCK_TFR_LARGE", Set.of("STOCK_TFR_LARGE")),
Map.entry("LARGE_STOCK_TRADING", Set.of("STOCK_TFR_LARGE")), Map.entry("LARGE_STOCK_TRADING", Set.of("STOCK_TFR_LARGE")),
Map.entry("MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")), Map.entry("MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")),
Map.entry("EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")),
Map.entry("MONTHLY_FIXED_INCOME", Set.of("MONTHLY_FIXED_INCOME")), Map.entry("MONTHLY_FIXED_INCOME", Set.of("MONTHLY_FIXED_INCOME")),
Map.entry("FIXED_COUNTERPARTY_TRANSFER", Set.of("FIXED_COUNTERPARTY_TRANSFER_MIN", "FIXED_COUNTERPARTY_TRANSFER_MAX")) Map.entry("FIXED_COUNTERPARTY_TRANSFER", Set.of("FIXED_COUNTERPARTY_TRANSFER_MIN", "FIXED_COUNTERPARTY_TRANSFER_MAX"))
); );
private static final Map<String, String> RULE_PARAM_MODEL_MAPPING = Map.of(
"EXTERNAL_SINGLE_LARGE_AMOUNT", "LARGE_TRANSACTION",
"EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT", "LARGE_TRANSACTION",
"EXTERNAL_ANNUAL_TURNOVER", "LARGE_TRANSACTION",
"EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER", "SUSPICIOUS_GAMBLING"
);
@Resource @Resource
private CcdiProjectMapper projectMapper; private CcdiProjectMapper projectMapper;
@@ -69,12 +80,13 @@ public class BankTagRuleConfigResolver {
} }
Long effectiveProjectId = "default".equals(project.getConfigType()) ? 0L : projectId; Long effectiveProjectId = "default".equals(project.getConfigType()) ? 0L : projectId;
List<CcdiModelParam> params = modelParamMapper.selectByProjectAndModel(effectiveProjectId, ruleMeta.getModelCode()); String paramModelCode = RULE_PARAM_MODEL_MAPPING.getOrDefault(ruleMeta.getRuleCode(), ruleMeta.getModelCode());
List<CcdiModelParam> params = modelParamMapper.selectByProjectAndModel(effectiveProjectId, paramModelCode);
Map<String, String> thresholdValues = new LinkedHashMap<>(); Map<String, String> thresholdValues = new LinkedHashMap<>();
Set<String> requiredParamCodes = RULE_PARAM_MAPPING.getOrDefault(ruleMeta.getRuleCode(), Set.of()); Set<String> requiredParamCodes = RULE_PARAM_MAPPING.getOrDefault(ruleMeta.getRuleCode(), Set.of());
log.info("【流水标签】解析规则参数: projectId={}, effectiveProjectId={}, ruleCode={}, requiredParams={}", log.info("【流水标签】解析规则参数: projectId={}, effectiveProjectId={}, ruleCode={}, paramModelCode={}, requiredParams={}",
projectId, effectiveProjectId, ruleMeta.getRuleCode(), requiredParamCodes); projectId, effectiveProjectId, ruleMeta.getRuleCode(), paramModelCode, requiredParamCodes);
for (CcdiModelParam param : params) { for (CcdiModelParam param : params) {
if (requiredParamCodes.contains(param.getParamCode())) { if (requiredParamCodes.contains(param.getParamCode())) {
thresholdValues.put(param.getParamCode(), param.getParamValue()); thresholdValues.put(param.getParamCode(), param.getParamValue());

View File

@@ -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.dto.CcdiBankStatementQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementCounterpartyVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
@@ -32,6 +33,8 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
private static final Set<String> ALLOWED_TAB_TYPES = Set.of("all", "in", "out"); private static final Set<String> ALLOWED_TAB_TYPES = Set.of("all", "in", "out");
private static final Set<String> ALLOWED_ORDER_DIRECTIONS = Set.of("asc", "desc"); private static final Set<String> ALLOWED_ORDER_DIRECTIONS = Set.of("asc", "desc");
private static final Set<String> ALLOWED_SUSPICIOUS_TYPES =
Set.of("ALL", "MODEL_RULE", "EXTERNAL_PERSON", "NAME_LIST");
@Resource @Resource
private CcdiBankStatementMapper bankStatementMapper; private CcdiBankStatementMapper bankStatementMapper;
@@ -55,6 +58,16 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
return result; return result;
} }
@Override
public Page<CcdiBankStatementCounterpartyVO> selectCounterpartyPage(
Page<CcdiBankStatementCounterpartyVO> page,
CcdiBankStatementQueryDTO queryDTO
) {
CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO;
normalizeQuery(normalizedQuery);
return bankStatementMapper.selectCounterpartyPage(page, normalizedQuery);
}
@Override @Override
public List<CcdiBankStatementExcel> selectStatementListForExport(CcdiBankStatementQueryDTO queryDTO) { public List<CcdiBankStatementExcel> selectStatementListForExport(CcdiBankStatementQueryDTO queryDTO) {
CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO; CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO;
@@ -69,13 +82,24 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
@Override @Override
public CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId) { public CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId) {
return getStatementDetail(bankStatementId, null, null);
}
@Override
public CcdiBankStatementDetailVO getStatementDetail(
Long bankStatementId,
String modelCode,
String suspiciousType
) {
CcdiBankStatementDetailVO detail = bankStatementMapper.selectStatementDetailById(bankStatementId); CcdiBankStatementDetailVO detail = bankStatementMapper.selectStatementDetailById(bankStatementId);
if (detail == null || detail.getProjectId() == null || detail.getBankStatementId() == null) { if (detail == null || detail.getProjectId() == null || detail.getBankStatementId() == null) {
return detail; return detail;
} }
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap( Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(
detail.getProjectId(), detail.getProjectId(),
List.of(detail.getBankStatementId()) List.of(detail.getBankStatementId()),
normalizeUpperCase(modelCode),
normalizeSuspiciousType(suspiciousType)
); );
detail.setHitTags(new ArrayList<>(hitTagMap.getOrDefault(detail.getBankStatementId(), Collections.emptyList()))); detail.setHitTags(new ArrayList<>(hitTagMap.getOrDefault(detail.getBankStatementId(), Collections.emptyList())));
return detail; return detail;
@@ -93,18 +117,33 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
if (bankStatementIds.isEmpty()) { if (bankStatementIds.isEmpty()) {
return; return;
} }
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(projectId, bankStatementIds); Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(
projectId,
bankStatementIds,
null,
null
);
rows.forEach(row -> row.setHitTags(new ArrayList<>( rows.forEach(row -> row.setHitTags(new ArrayList<>(
hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList()) hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList())
))); )));
} }
private Map<Long, List<CcdiBankStatementHitTagVO>> loadHitTagMap(Long projectId, List<Long> bankStatementIds) { private Map<Long, List<CcdiBankStatementHitTagVO>> loadHitTagMap(
Long projectId,
List<Long> bankStatementIds,
String modelCode,
String suspiciousType
) {
if (projectId == null || bankStatementIds == null || bankStatementIds.isEmpty()) { if (projectId == null || bankStatementIds == null || bankStatementIds.isEmpty()) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
List<CcdiBankStatementHitTagVO> hitTags = List<CcdiBankStatementHitTagVO> hitTags =
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds); bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
projectId,
bankStatementIds,
modelCode,
suspiciousType
);
if (hitTags == null || hitTags.isEmpty()) { if (hitTags == null || hitTags.isEmpty()) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
@@ -125,6 +164,7 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
queryDTO.setCounterpartyAccount(normalizeText(queryDTO.getCounterpartyAccount())); queryDTO.setCounterpartyAccount(normalizeText(queryDTO.getCounterpartyAccount()));
queryDTO.setTransactionType(normalizeText(queryDTO.getTransactionType())); queryDTO.setTransactionType(normalizeText(queryDTO.getTransactionType()));
queryDTO.setOurSubjects(normalizeList(queryDTO.getOurSubjects())); queryDTO.setOurSubjects(normalizeList(queryDTO.getOurSubjects()));
queryDTO.setOurCertNos(normalizeList(queryDTO.getOurCertNos()));
queryDTO.setOurBanks(normalizeList(queryDTO.getOurBanks())); queryDTO.setOurBanks(normalizeList(queryDTO.getOurBanks()));
queryDTO.setOurAccounts(normalizeList(queryDTO.getOurAccounts())); queryDTO.setOurAccounts(normalizeList(queryDTO.getOurAccounts()));
queryDTO.setCounterpartyNameEmpty(Boolean.TRUE.equals(queryDTO.getCounterpartyNameEmpty())); queryDTO.setCounterpartyNameEmpty(Boolean.TRUE.equals(queryDTO.getCounterpartyNameEmpty()));
@@ -152,6 +192,21 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
if ("trxDate".equalsIgnoreCase(normalized)) { if ("trxDate".equalsIgnoreCase(normalized)) {
return "trxDate"; return "trxDate";
} }
if ("latestTrxDate".equalsIgnoreCase(normalized)) {
return "latestTrxDate";
}
if ("inAmount".equalsIgnoreCase(normalized)) {
return "inAmount";
}
if ("outAmount".equalsIgnoreCase(normalized)) {
return "outAmount";
}
if ("totalAmount".equalsIgnoreCase(normalized)) {
return "totalAmount";
}
if ("totalCount".equalsIgnoreCase(normalized)) {
return "totalCount";
}
return "trxDate"; return "trxDate";
} }
@@ -165,6 +220,16 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
return normalized == null ? null : normalized.toLowerCase(Locale.ROOT); return normalized == null ? null : normalized.toLowerCase(Locale.ROOT);
} }
private String normalizeUpperCase(String value) {
String normalized = normalizeText(value);
return normalized == null ? null : normalized.toUpperCase(Locale.ROOT);
}
private String normalizeSuspiciousType(String suspiciousType) {
String normalized = normalizeUpperCase(suspiciousType);
return normalized != null && ALLOWED_SUSPICIOUS_TYPES.contains(normalized) ? normalized : null;
}
private String normalizeText(String value) { private String normalizeText(String value) {
if (value == null) { if (value == null) {
return null; return null;

View File

@@ -225,9 +225,15 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
case "LARGE_TRANSFER" -> analysisMapper.selectLargeTransferStatements( case "LARGE_TRANSFER" -> analysisMapper.selectLargeTransferStatements(
projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER")) projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER"))
); );
case "EXTERNAL_SINGLE_LARGE_AMOUNT" -> analysisMapper.selectExternalSingleLargeAmountStatements(
projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER"))
);
case "ABNORMAL_CUSTOMER_TRANSACTION" -> analysisMapper.selectAbnormalCustomerTransactionStatements(projectId); case "ABNORMAL_CUSTOMER_TRANSACTION" -> analysisMapper.selectAbnormalCustomerTransactionStatements(projectId);
case "EXTERNAL_NIGHT_TRANSACTION" -> analysisMapper.selectExternalNightTransactionStatements(projectId);
case "GAMBLING_SENSITIVE_KEYWORD" -> analysisMapper.selectGamblingSensitiveKeywordStatements(projectId); case "GAMBLING_SENSITIVE_KEYWORD" -> analysisMapper.selectGamblingSensitiveKeywordStatements(projectId);
case "EXTERNAL_GAMBLING_MEMO" -> analysisMapper.selectExternalGamblingMemoStatements(projectId);
case "SPECIAL_AMOUNT_TRANSACTION" -> analysisMapper.selectSpecialAmountTransactionStatements(projectId); case "SPECIAL_AMOUNT_TRANSACTION" -> analysisMapper.selectSpecialAmountTransactionStatements(projectId);
case "EXTERNAL_TO_STAFF_FAMILY_TRANSACTION" -> analysisMapper.selectExternalToStaffOrFamilyTransactionStatements(projectId);
case "SUSPICIOUS_INCOME_KEYWORD" -> analysisMapper.selectSuspiciousIncomeKeywordStatements(projectId); case "SUSPICIOUS_INCOME_KEYWORD" -> analysisMapper.selectSuspiciousIncomeKeywordStatements(projectId);
case "HOUSE_REGISTRATION_MISMATCH" -> analysisMapper.selectHouseRegistrationMismatchStatements(projectId); case "HOUSE_REGISTRATION_MISMATCH" -> analysisMapper.selectHouseRegistrationMismatchStatements(projectId);
case "PROPERTY_FEE_REGISTRATION_MISMATCH" -> analysisMapper.selectPropertyFeeRegistrationMismatchStatements(projectId); case "PROPERTY_FEE_REGISTRATION_MISMATCH" -> analysisMapper.selectPropertyFeeRegistrationMismatchStatements(projectId);
@@ -258,9 +264,15 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
case "CUMULATIVE_INCOME" -> analysisMapper.selectCumulativeIncomeObjects( case "CUMULATIVE_INCOME" -> analysisMapper.selectCumulativeIncomeObjects(
projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT")) projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT"))
); );
case "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT" -> analysisMapper.selectExternalCumulativeTransactionAmountObjects(
projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT"))
);
case "ANNUAL_TURNOVER" -> analysisMapper.selectAnnualTurnoverObjects( case "ANNUAL_TURNOVER" -> analysisMapper.selectAnnualTurnoverObjects(
projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER")) projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER"))
); );
case "EXTERNAL_ANNUAL_TURNOVER" -> analysisMapper.selectExternalAnnualTurnoverObjects(
projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER"))
);
case "FREQUENT_CASH_DEPOSIT" -> analysisMapper.selectFrequentCashDepositObjects( case "FREQUENT_CASH_DEPOSIT" -> analysisMapper.selectFrequentCashDepositObjects(
projectId, projectId,
toBigDecimal(config.getThresholdValue("LARGE_CASH_DEPOSIT")), toBigDecimal(config.getThresholdValue("LARGE_CASH_DEPOSIT")),
@@ -272,6 +284,11 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")), toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")),
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX")) toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX"))
); );
case "EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER" -> analysisMapper.selectExternalMultiPartyGamblingTransferObjects(
projectId,
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")),
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX"))
);
case "MONTHLY_FIXED_INCOME" -> analysisMapper.selectMonthlyFixedIncomeObjects( case "MONTHLY_FIXED_INCOME" -> analysisMapper.selectMonthlyFixedIncomeObjects(
projectId, toBigDecimal(config.getThresholdValue("MONTHLY_FIXED_INCOME")) projectId, toBigDecimal(config.getThresholdValue("MONTHLY_FIXED_INCOME"))
); );

View File

@@ -46,6 +46,7 @@ import java.nio.file.StandardCopyOption;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.regex.Matcher;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
@@ -64,6 +65,9 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
private static final int MAX_ERROR_MESSAGE_LENGTH = 2000; private static final int MAX_ERROR_MESSAGE_LENGTH = 2000;
private static final Pattern ID_CARD_PATTERN = private static final Pattern ID_CARD_PATTERN =
Pattern.compile("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])([0-2]\\d|3[01])\\d{3}[0-9Xx]$"); Pattern.compile("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])([0-2]\\d|3[01])\\d{3}[0-9Xx]$");
private static final Pattern UPLOAD_FILE_NAME_ID_CARD_PATTERN =
Pattern.compile("(?<!\\d)\\d{17}[0-9Xx](?!\\d)");
private static final Pattern FILE_NAME_SPACE_PATTERN = Pattern.compile("[\\s\\u3000]+");
@Data @Data
private static class FetchBankStatementResult { private static class FetchBankStatementResult {
@@ -150,6 +154,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
@Override @Override
public String submitPullBankInfo(Long projectId, public String submitPullBankInfo(Long projectId,
List<String> idCards, List<String> idCards,
String dataChannelCode,
String startDate, String startDate,
String endDate, String endDate,
Long userId, Long userId,
@@ -157,17 +162,20 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
if (projectId == null) { if (projectId == null) {
throw new IllegalArgumentException("项目ID不能为空"); throw new IllegalArgumentException("项目ID不能为空");
} }
if (!StringUtils.hasText(startDate) || !StringUtils.hasText(endDate)) {
throw new IllegalArgumentException("开始日期和结束日期不能为空");
}
if (idCards == null || idCards.isEmpty()) { if (idCards == null || idCards.isEmpty()) {
throw new IllegalArgumentException("身份证号不能为空"); throw new IllegalArgumentException("身份证号不能为空");
} }
String normalizedDataChannelCode = normalizePullBankInfoDataChannelCode(dataChannelCode);
LocalDate start = LocalDate.parse(startDate); if (LsfxConstants.DATA_CHANNEL_ZJRCU.equals(normalizedDataChannelCode)) {
LocalDate end = LocalDate.parse(endDate); if (!StringUtils.hasText(startDate) || !StringUtils.hasText(endDate)) {
if (start.isAfter(end)) { throw new IllegalArgumentException("开始日期和结束日期不能为空");
throw new IllegalArgumentException("开始日期不能晚于结束日期"); }
LocalDate start = LocalDate.parse(startDate);
LocalDate end = LocalDate.parse(endDate);
if (start.isAfter(end)) {
throw new IllegalArgumentException("开始日期不能晚于结束日期");
}
} }
projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许上传或拉取数据"); projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许上传或拉取数据");
@@ -214,13 +222,26 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
@Override @Override
public void afterCommit() { public void afterCommit() {
CompletableFuture.runAsync(() -> submitPullBankInfoTasks( CompletableFuture.runAsync(() -> submitPullBankInfoTasks(
projectId, lsfxProjectId, records, normalizedIdCards, startDate, endDate, batchId projectId, lsfxProjectId, records, normalizedIdCards,
normalizedDataChannelCode, startDate, endDate, batchId
)); ));
} }
}); });
return batchId; return batchId;
} }
private String normalizePullBankInfoDataChannelCode(String dataChannelCode) {
if (!StringUtils.hasText(dataChannelCode)) {
throw new IllegalArgumentException("流水来源不能为空");
}
String normalized = dataChannelCode.trim().toUpperCase();
if (!LsfxConstants.DATA_CHANNEL_ZJRCU.equals(normalized)
&& !LsfxConstants.DATA_CHANNEL_JZL.equals(normalized)) {
throw new IllegalArgumentException("流水来源不支持");
}
return normalized;
}
@Override @Override
public String deleteFileUploadRecord(Long id, Long operatorUserId) { public String deleteFileUploadRecord(Long id, Long operatorUserId) {
CcdiFileUploadRecord record = recordMapper.selectById(id); CcdiFileUploadRecord record = recordMapper.selectById(id);
@@ -347,6 +368,8 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
log.info("【文件上传】项目信息验证通过: projectId={}, lsfxProjectId={}", projectId, lsfxProjectId); log.info("【文件上传】项目信息验证通过: projectId={}, lsfxProjectId={}", projectId, lsfxProjectId);
List<String> normalizedFileNames = normalizeAndValidateUploadFileNames(files);
// Critical Fix #2 & #4: 保存临时文件和创建记录在同一个循环中,确保一一对应 // Critical Fix #2 & #4: 保存临时文件和创建记录在同一个循环中,确保一一对应
List<String> tempFilePaths = new ArrayList<>(); List<String> tempFilePaths = new ArrayList<>();
List<CcdiFileUploadRecord> records = new ArrayList<>(); List<CcdiFileUploadRecord> records = new ArrayList<>();
@@ -362,10 +385,11 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
// 同一个循环中保存临时文件和创建记录,确保索引一一对应 // 同一个循环中保存临时文件和创建记录,确保索引一一对应
for (int i = 0; i < files.length; i++) { for (int i = 0; i < files.length; i++) {
MultipartFile file = files[i]; MultipartFile file = files[i];
String normalizedFileName = normalizedFileNames.get(i);
// 1. 保存临时文件 // 1. 保存临时文件
String originalFilename = file.getOriginalFilename(); String originalFilename = file.getOriginalFilename();
String tempFileName = batchId + "_" + i + "_" + System.currentTimeMillis() + "_" + originalFilename; String tempFileName = batchId + "_" + i + "_" + System.currentTimeMillis() + "_" + normalizedFileName;
Path tempFilePath = tempDir.resolve(tempFileName); Path tempFilePath = tempDir.resolve(tempFileName);
Files.copy(file.getInputStream(), tempFilePath, StandardCopyOption.REPLACE_EXISTING); Files.copy(file.getInputStream(), tempFilePath, StandardCopyOption.REPLACE_EXISTING);
@@ -378,7 +402,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
CcdiFileUploadRecord record = new CcdiFileUploadRecord(); CcdiFileUploadRecord record = new CcdiFileUploadRecord();
record.setProjectId(projectId); record.setProjectId(projectId);
record.setLsfxProjectId(lsfxProjectId); record.setLsfxProjectId(lsfxProjectId);
record.setFileName(originalFilename); record.setFileName(normalizedFileName);
record.setFileSize(file.getSize()); record.setFileSize(file.getSize());
record.setFileStatus("uploading"); record.setFileStatus("uploading");
record.setUploadTime(now); record.setUploadTime(now);
@@ -423,6 +447,42 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
return batchId; return batchId;
} }
private List<String> normalizeAndValidateUploadFileNames(MultipartFile[] files) {
List<String> normalizedFileNames = new ArrayList<>();
for (MultipartFile file : files) {
String originalFileName = file.getOriginalFilename() == null ? "" : file.getOriginalFilename();
String normalizedFileName = FILE_NAME_SPACE_PATTERN.matcher(originalFileName).replaceAll("");
validateUploadFileName(normalizedFileName);
normalizedFileNames.add(normalizedFileName);
}
return normalizedFileNames;
}
private void validateUploadFileName(String fileName) {
if (!StringUtils.hasText(fileName)) {
throw new IllegalArgumentException("文件名不能为空");
}
String lowerFileName = fileName.toLowerCase(Locale.ROOT);
if (!lowerFileName.endsWith(".xlsx") && !lowerFileName.endsWith(".csv")
&& !lowerFileName.endsWith(".pdf")) {
throw new IllegalArgumentException("文件 " + fileName + " 格式不支持, 仅支持 PDF, CSV, XLSX 文件");
}
String mainFileName = getMainFileName(fileName);
if (!UPLOAD_FILE_NAME_ID_CARD_PATTERN.matcher(mainFileName).find()) {
throw new IllegalArgumentException("文件 " + fileName + " 文件名未包含身份证信息");
}
}
private String getMainFileName(String fileName) {
int extensionIndex = fileName.lastIndexOf('.');
if (extensionIndex <= 0) {
return fileName;
}
return fileName.substring(0, extensionIndex);
}
/** /**
* 调度线程:循环提交任务到线程池 * 调度线程:循环提交任务到线程池
* 支持等待30秒重试机制 * 支持等待30秒重试机制
@@ -537,6 +597,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
Integer lsfxProjectId, Integer lsfxProjectId,
List<CcdiFileUploadRecord> records, List<CcdiFileUploadRecord> records,
List<String> idCards, List<String> idCards,
String dataChannelCode,
String startDate, String startDate,
String endDate, String endDate,
String batchId) { String batchId) {
@@ -558,7 +619,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
while (!submitted && retryCount < 2) { while (!submitted && retryCount < 2) {
try { try {
CompletableFuture<Boolean> future = CompletableFuture.supplyAsync( CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(
() -> processPullBankInfoAsync(projectId, lsfxProjectId, record, idCard, startDate, endDate), () -> processPullBankInfoAsync(projectId, lsfxProjectId, record, idCard, dataChannelCode, startDate, endDate),
fileUploadExecutor fileUploadExecutor
); );
futures.add(future); futures.add(future);
@@ -597,16 +658,23 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
Integer lsfxProjectId, Integer lsfxProjectId,
CcdiFileUploadRecord record, CcdiFileUploadRecord record,
String idCard, String idCard,
String dataChannelCode,
String startDate, String startDate,
String endDate ) { String endDate ) {
try { try {
String normalizedDataChannelCode = normalizePullBankInfoDataChannelCode(dataChannelCode);
FetchInnerFlowRequest request = new FetchInnerFlowRequest(); FetchInnerFlowRequest request = new FetchInnerFlowRequest();
request.setGroupId(lsfxProjectId); request.setGroupId(lsfxProjectId);
request.setCustomerNo(idCard); request.setCustomerNo(idCard);
request.setDataChannelCode(LsfxConstants.DEFAULT_DATA_CHANNEL_CODE); request.setDataChannelCode(normalizedDataChannelCode);
request.setRequestDateId(Integer.parseInt(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE))); request.setRequestDateId(Integer.parseInt(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)));
request.setDataStartDateId(Integer.parseInt(startDate.replace("-", ""))); if (LsfxConstants.DATA_CHANNEL_JZL.equals(normalizedDataChannelCode)) {
request.setDataEndDateId(Integer.parseInt(endDate.replace("-", ""))); request.setDataStartDateId(0);
request.setDataEndDateId(0);
} else {
request.setDataStartDateId(Integer.parseInt(startDate.replace("-", "")));
request.setDataEndDateId(Integer.parseInt(endDate.replace("-", "")));
}
request.setUploadUserId(LsfxConstants.DEFAULT_USER_ID); request.setUploadUserId(LsfxConstants.DEFAULT_USER_ID);
FetchInnerFlowResponse response = lsfxClient.fetchInnerFlow(request); FetchInnerFlowResponse response = lsfxClient.fetchInnerFlow(request);
@@ -777,7 +845,9 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
enterpriseNamesStr, accountNosStr); enterpriseNamesStr, accountNosStr);
log.info("【文件上传】步骤7: 获取流水数据"); log.info("【文件上传】步骤7: 获取流水数据");
FetchBankStatementResult fetchResult = fetchAndSaveBankStatements(projectId, lsfxProjectId, logId); String fallbackCretNo = extractIdCardFromFileName(record.getFileName());
FetchBankStatementResult fetchResult = fetchAndSaveBankStatements(projectId, lsfxProjectId, logId,
fallbackCretNo);
if (!fetchResult.isSuccess()) { if (!fetchResult.isSuccess()) {
updateFailedRecord(record, fetchResult.getErrorMessage()); updateFailedRecord(record, fetchResult.getErrorMessage());
return; return;
@@ -850,7 +920,8 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
* @param logId 文件ID * @param logId 文件ID
*/ */
private FetchBankStatementResult fetchAndSaveBankStatements(Long projectId, Integer groupId, private FetchBankStatementResult fetchAndSaveBankStatements(Long projectId, Integer groupId,
Integer logId) { Integer logId,
String fallbackCretNo) {
log.info("【文件上传】开始获取流水数据: projectId={}, groupId={}, logId={}", log.info("【文件上传】开始获取流水数据: projectId={}, groupId={}, logId={}",
projectId, groupId, logId); projectId, groupId, logId);
@@ -915,6 +986,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
if (statement != null) { if (statement != null) {
statement.setBatchId(logId); statement.setBatchId(logId);
statement.setProjectId(projectId); statement.setProjectId(projectId);
fillMissingCretNo(statement, fallbackCretNo);
normalizeDedupFields(statement); normalizeDedupFields(statement);
batchList.add(statement); batchList.add(statement);
@@ -958,6 +1030,22 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
} }
} }
private String extractIdCardFromFileName(String fileName) {
if (!StringUtils.hasText(fileName)) {
return null;
}
String mainFileName = getMainFileName(fileName);
Matcher matcher = UPLOAD_FILE_NAME_ID_CARD_PATTERN.matcher(mainFileName);
return matcher.find() ? matcher.group() : null;
}
private void fillMissingCretNo(CcdiBankStatement statement, String fallbackCretNo) {
if (statement == null || StringUtils.hasText(statement.getCretNo()) || !StringUtils.hasText(fallbackCretNo)) {
return;
}
statement.setCretNo(fallbackCretNo);
}
private void cleanupBankStatements(Long projectId, Integer logId) { private void cleanupBankStatements(Long projectId, Integer logId) {
bankStatementMapper.deleteByProjectIdAndBatchId(projectId, logId); bankStatementMapper.deleteByProjectIdAndBatchId(projectId, logId);
} }

View File

@@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.service.impl;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
@@ -65,7 +66,7 @@ public class CcdiProjectOverviewReportPdfExporter {
writeCover(writer, report); writeCover(writer, report);
writeUploadSubjects(writer, report.getUploadSubjects()); writeUploadSubjects(writer, report.getUploadSubjects());
writeParams(writer, report.getParams()); writeParams(writer, report.getParams());
writeRiskModels(writer, report); writeRiskOverview(writer, report);
writeRiskDetails(writer, report); writeRiskDetails(writer, report);
writer.close(); writer.close();
document.save(response.getOutputStream()); document.save(response.getOutputStream());
@@ -118,9 +119,9 @@ public class CcdiProjectOverviewReportPdfExporter {
); );
} }
private void writeRiskModels(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException { private void writeRiskOverview(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException {
writer.section("三、风险模型"); writer.section("三、风险总览");
writer.metrics(report.getDashboard().getStats()); writer.metrics(buildOverallRiskMetrics(report));
writer.subsection("风险模型汇总"); writer.subsection("风险模型汇总");
writer.table( writer.table(
List.of("模型名称", "预警数量", "涉及人员"), List.of("模型名称", "预警数量", "涉及人员"),
@@ -151,6 +152,40 @@ public class CcdiProjectOverviewReportPdfExporter {
new float[] { 0.1F, 0.11F, 0.16F, 0.14F, 0.24F, 0.25F }, new float[] { 0.1F, 0.11F, 0.16F, 0.14F, 0.24F, 0.25F },
"暂无风险人员与异常点数据" "暂无风险人员与异常点数据"
); );
if (hasExternalRisk(report)) {
writer.subsection("外部人员预警");
writer.metrics(buildExternalMetrics(report));
writer.table(
List.of("外部模型", "预警数量", "涉及人数"),
report.getExternalModelSummaries().stream()
.map(item -> List.of(
safeText(item.getModelName()),
String.valueOf(defaultZero(item.getWarningCount())),
formatCount(item.getPeopleCount(), "")
))
.collect(Collectors.toList()),
new float[] { 0.5F, 0.2F, 0.3F },
"暂无外部人员模型汇总数据"
);
writer.table(
List.of("姓名", "证件号", "主体类型", "风险等级", "命中模型数", "核心异常点", "涉及对象", "最近交易时间"),
report.getExternalPersonWarnings().stream()
.map(item -> List.of(
safeText(item.getName()),
maskIdCard(item.getIdNo()),
safeText(item.getSubjectType()),
safeText(item.getRiskLevel()),
String.valueOf(defaultZero(item.getModelCount())),
safeText(item.getRiskPoint()),
safeText(item.getRelatedObject()),
safeText(item.getLatestTradeTime())
))
.collect(Collectors.toList()),
new float[] { 0.09F, 0.15F, 0.1F, 0.09F, 0.1F, 0.25F, 0.12F, 0.1F },
"暂无外部人员预警数据"
);
}
} }
private void writeRiskDetails(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException { private void writeRiskDetails(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException {
@@ -195,7 +230,7 @@ public class CcdiProjectOverviewReportPdfExporter {
writer.subsection("3. 异常账户信息表(共" + report.getAbnormalAccounts().size() + "条)"); writer.subsection("3. 异常账户信息表(共" + report.getAbnormalAccounts().size() + "条)");
writer.table( writer.table(
List.of("账号", "开户人", "银行", "异常类型", "异常发生时间", "状态"), List.of("账号", "开户人", "银行", "异常类型", "异常发生时间", "状态", "命中原因", "涉及金额"),
report.getAbnormalAccounts().stream() report.getAbnormalAccounts().stream()
.map(item -> List.of( .map(item -> List.of(
maskAccount(item.getAccountNo()), maskAccount(item.getAccountNo()),
@@ -203,12 +238,33 @@ public class CcdiProjectOverviewReportPdfExporter {
safeText(item.getBankName()), safeText(item.getBankName()),
safeText(item.getAbnormalType()), safeText(item.getAbnormalType()),
safeText(item.getAbnormalTime()), safeText(item.getAbnormalTime()),
safeText(item.getStatus()) safeText(item.getStatus()),
safeText(item.getReasonDetail()),
formatMoney(item.getInvolvedAmount())
)) ))
.collect(Collectors.toList()), .collect(Collectors.toList()),
new float[] { 0.18F, 0.13F, 0.2F, 0.23F, 0.14F, 0.12F }, 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 { private LoadedChineseFont loadChineseFont(PDDocument document) throws IOException {
@@ -328,6 +384,69 @@ public class CcdiProjectOverviewReportPdfExporter {
return result; return result;
} }
private List<CcdiProjectOverviewStatVO> buildOverallRiskMetrics(CcdiProjectOverviewReportVO report) {
List<CcdiProjectOverviewStatVO> employeeStats = report.getDashboard().getStats();
if (employeeStats == null || employeeStats.isEmpty()) {
return List.of(
buildMetric("总人数", report.getExternalRiskSummary().getTotal()),
buildMetric("高风险", report.getExternalRiskSummary().getHigh()),
buildMetric("中风险", report.getExternalRiskSummary().getMedium()),
buildMetric("低风险", report.getExternalRiskSummary().getLow()),
buildMetric("无风险", report.getExternalRiskSummary().getNoRisk())
);
}
int employeeTotal = metricValue(employeeStats, "people");
int high = metricValue(employeeStats, "riskPeople");
int medium = metricValue(employeeStats, "medium");
int low = metricValue(employeeStats, "low");
int noRisk = metricValue(employeeStats, "count");
int externalTotal = defaultZero(report.getExternalRiskSummary().getTotal());
int externalHigh = defaultZero(report.getExternalRiskSummary().getHigh());
int externalMedium = defaultZero(report.getExternalRiskSummary().getMedium());
int externalLow = defaultZero(report.getExternalRiskSummary().getLow());
int externalNoRisk = defaultZero(report.getExternalRiskSummary().getNoRisk());
return List.of(
buildMetric("总人数", employeeTotal + externalTotal),
buildMetric("高风险", high + externalHigh),
buildMetric("中风险", medium + externalMedium),
buildMetric("低风险", low + externalLow),
buildMetric("无风险", noRisk + externalNoRisk)
);
}
private Integer metricValue(List<CcdiProjectOverviewStatVO> stats, String key) {
return defaultZero(stats.stream()
.filter(stat -> key.equals(stat.getKey()))
.findFirst()
.map(CcdiProjectOverviewStatVO::getValue)
.orElse(0));
}
private boolean hasExternalRisk(CcdiProjectOverviewReportVO report) {
return defaultZero(report.getExternalRiskSummary().getHigh()) > 0
|| defaultZero(report.getExternalRiskSummary().getMedium()) > 0
|| defaultZero(report.getExternalRiskSummary().getLow()) > 0
|| !report.getExternalModelSummaries().isEmpty()
|| !report.getExternalPersonWarnings().isEmpty();
}
private List<CcdiProjectOverviewStatVO> buildExternalMetrics(CcdiProjectOverviewReportVO report) {
return List.of(
buildMetric("外部人员", report.getExternalRiskSummary().getTotal()),
buildMetric("高风险", report.getExternalRiskSummary().getHigh()),
buildMetric("中风险", report.getExternalRiskSummary().getMedium()),
buildMetric("低风险", report.getExternalRiskSummary().getLow()),
buildMetric("无风险人员", report.getExternalRiskSummary().getNoRisk())
);
}
private CcdiProjectOverviewStatVO buildMetric(String label, Integer value) {
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
stat.setLabel(label);
stat.setValue(defaultZero(value));
return stat;
}
private String formatPeopleSummary(CcdiProjectOverviewReportModelSummaryVO item) { private String formatPeopleSummary(CcdiProjectOverviewReportModelSummaryVO item) {
String names = safeText(item.getPeopleNames()); String names = safeText(item.getPeopleNames());
if ("-".equals(names)) { if ("-".equals(names)) {

View File

@@ -1,18 +1,27 @@
package com.ruoyi.ccdi.project.service.impl; 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.CcdiModelParam;
import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.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.CcdiProjectRiskExclusionSaveDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; 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.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; 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.CcdiProjectRiskPeopleOverviewExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel; 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.CcdiProjectOverviewEmployeeResult;
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectRiskExclusion;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
@@ -25,40 +34,54 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisAbnormalGroupVO
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewStatVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewStatVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
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.CcdiProjectRiskHitTagVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO;
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.CcdiProjectSuspiciousTransactionItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO; 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.CcdiBankStatementMapper;
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;
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper; 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.ICcdiModelParamService; import com.ruoyi.ccdi.project.mapper.CcdiProjectRiskExclusionMapper;
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
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 com.ruoyi.common.utils.SecurityUtils;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -70,6 +93,16 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
private static final String ACTION_LABEL = "查看详情"; private static final String ACTION_LABEL = "查看详情";
private static final String SUSPICIOUS_TYPE_ALL = "ALL";
private static final String SUSPICIOUS_TYPE_MODEL_RULE = "MODEL_RULE";
private static final String SUSPICIOUS_TYPE_EXTERNAL_PERSON = "EXTERNAL_PERSON";
private static final String EXCLUSION_TYPE_STATEMENT = "STATEMENT";
private static final String EXCLUSION_TYPE_OBJECT = "OBJECT";
@Resource @Resource
private CcdiProjectOverviewMapper overviewMapper; private CcdiProjectOverviewMapper overviewMapper;
@@ -79,12 +112,18 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
@Resource @Resource
private CcdiBankStatementMapper bankStatementMapper; private CcdiBankStatementMapper bankStatementMapper;
@Resource
private CcdiModelParamMapper modelParamMapper;
@Resource @Resource
private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper; private CcdiProjectOverviewEmployeeResultMapper overviewEmployeeResultMapper;
@Resource @Resource
private CcdiBankTagResultMapper bankTagResultMapper; private CcdiBankTagResultMapper bankTagResultMapper;
@Resource
private CcdiProjectRiskExclusionMapper riskExclusionMapper;
@Resource @Resource
private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder; private CcdiProjectOverviewEmployeeResultBuilder overviewEmployeeResultBuilder;
@@ -94,10 +133,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
@Resource @Resource
private CcdiProjectOverviewReportPdfExporter reportPdfExporter; private CcdiProjectOverviewReportPdfExporter reportPdfExporter;
@Resource
@Lazy
private ICcdiModelParamService modelParamService;
@Override @Override
public CcdiProjectOverviewDashboardVO getDashboard(Long projectId) { public CcdiProjectOverviewDashboardVO getDashboard(Long projectId) {
CcdiProject project = overviewMapper.selectDashboardBaseByProjectId(projectId); CcdiProject project = overviewMapper.selectDashboardBaseByProjectId(projectId);
@@ -124,6 +159,24 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
return dashboard; return dashboard;
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void excludeRisk(CcdiProjectRiskExclusionSaveDTO dto) {
CcdiProjectRiskExclusion exclusion = buildRiskExclusion(dto);
CcdiProject project = getRequiredProject(exclusion.getProjectId());
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus()) || Integer.valueOf(1).equals(project.getIsArchived())) {
throw new ServiceException("已归档项目暂不允许排除可疑");
}
if (CcdiProjectStatusConstants.TAGGING.equals(project.getStatus())) {
throw new ServiceException("项目正在打标中,暂不允许排除可疑");
}
if (riskExclusionMapper.countExistingRiskHit(exclusion) <= 0) {
throw new ServiceException("未找到可排除的预警命中");
}
riskExclusionMapper.upsertExclusion(exclusion);
refreshOverviewEmployeeResults(exclusion.getProjectId(), exclusion.getUpdateBy());
}
@Override @Override
public CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(CcdiProjectRiskPeopleQueryDTO queryDTO) { public CcdiProjectRiskPeopleOverviewVO getRiskPeopleOverview(CcdiProjectRiskPeopleQueryDTO queryDTO) {
Long projectId = queryDTO.getProjectId(); Long projectId = queryDTO.getProjectId();
@@ -179,6 +232,9 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
queryDTO.getStaffIdCard() queryDTO.getStaffIdCard()
)); ));
attachStatementHitTags(statementRows, queryDTO.getProjectId()); attachStatementHitTags(statementRows, queryDTO.getProjectId());
statementRows = statementRows.stream()
.filter(row -> row.getHitTags() != null && !row.getHitTags().isEmpty())
.toList();
normalizeObjectRows(objectRows); normalizeObjectRows(objectRows);
CcdiProjectPersonAnalysisDetailVO detail = new CcdiProjectPersonAnalysisDetailVO(); CcdiProjectPersonAnalysisDetailVO detail = new CcdiProjectPersonAnalysisDetailVO();
@@ -218,12 +274,122 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
return people; return people;
} }
@Override
public List<CcdiProjectRiskModelPeopleExcel> exportRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
ensureProjectExists(queryDTO.getProjectId());
normalizeRiskModelPeopleQuery(queryDTO);
return defaultList(overviewMapper.selectRiskModelPeopleList(queryDTO)).stream()
.map(item -> buildRiskModelPeopleExcelRow(item, "员工"))
.toList();
}
@Override
public CcdiProjectExternalPersonWarningVO getExternalPersonWarnings(CcdiProjectExternalPersonQueryDTO queryDTO) {
ensureProjectExists(queryDTO.getProjectId());
Page<CcdiProjectExternalPersonWarningItemVO> page = new Page<>(
defaultRiskPeoplePageNum(queryDTO.getPageNum()),
defaultRiskPeoplePageSize(queryDTO.getPageSize())
);
Page<CcdiProjectExternalPersonWarningItemVO> resultPage =
overviewMapper.selectExternalPersonWarningPage(page, queryDTO);
List<CcdiProjectExternalPersonWarningItemVO> rows =
defaultList(resultPage == null ? null : resultPage.getRecords()).stream()
.peek(item -> item.setActionLabel(ACTION_LABEL))
.toList();
CcdiProjectExternalPersonWarningVO warnings = new CcdiProjectExternalPersonWarningVO();
warnings.setRows(rows);
warnings.setTotal(resultPage == null ? 0L : resultPage.getTotal());
warnings.setPageNum(page.getCurrent());
warnings.setPageSize(page.getSize());
return warnings;
}
@Override
public CcdiProjectExternalRiskSummaryVO getExternalRiskSummary(Long projectId) {
ensureProjectExists(projectId);
CcdiProjectExternalRiskSummaryVO summary = overviewMapper.selectExternalRiskSummaryByProjectId(projectId);
if (summary == null) {
return new CcdiProjectExternalRiskSummaryVO();
}
summary.setTotal(defaultZero(summary.getTotal()));
summary.setHigh(defaultZero(summary.getHigh()));
summary.setMedium(defaultZero(summary.getMedium()));
summary.setLow(defaultZero(summary.getLow()));
summary.setNoRisk(defaultZero(summary.getNoRisk()));
return summary;
}
@Override
public List<CcdiProjectExternalPersonWarningExcel> exportExternalPersonWarnings(Long projectId) {
ensureProjectExists(projectId);
return defaultList(overviewMapper.selectExternalPersonWarningList(projectId)).stream()
.map(this::buildExternalPersonWarningExcelRow)
.toList();
}
@Override
public CcdiProjectRiskModelCardsVO getExternalRiskModelCards(Long projectId) {
ensureProjectExists(projectId);
CcdiProjectRiskModelCardsVO cards = new CcdiProjectRiskModelCardsVO();
cards.setCardList(defaultList(overviewMapper.selectExternalRiskModelCardsByProjectId(projectId)));
return cards;
}
@Override
public CcdiProjectRiskModelPeopleVO getExternalRiskModelPeople(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
ensureProjectExists(queryDTO.getProjectId());
normalizeExternalRiskModelPeopleQuery(queryDTO);
Page<CcdiProjectRiskModelPeopleItemVO> page = new Page<>(
defaultPageNum(queryDTO.getPageNum()),
defaultPageSize(queryDTO.getPageSize())
);
Page<CcdiProjectRiskModelPeopleItemVO> resultPage =
overviewMapper.selectExternalRiskModelPeoplePage(page, queryDTO);
List<CcdiProjectRiskModelPeopleItemVO> rows = defaultList(resultPage == null ? null : resultPage.getRecords())
.stream()
.peek(item -> item.setActionLabel(ACTION_LABEL))
.toList();
CcdiProjectRiskModelPeopleVO people = new CcdiProjectRiskModelPeopleVO();
people.setRows(rows);
people.setTotal(resultPage == null ? 0L : resultPage.getTotal());
return people;
}
@Override
public List<CcdiProjectRiskModelPeopleExcel> exportExternalRiskModelPeople(
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
) {
ensureProjectExists(queryDTO.getProjectId());
normalizeExternalRiskModelPeopleQuery(queryDTO);
return defaultList(overviewMapper.selectExternalRiskModelPeopleList(queryDTO)).stream()
.map(item -> buildRiskModelPeopleExcelRow(item, item.getStaffCode()))
.toList();
}
@Override @Override
public CcdiProjectSuspiciousTransactionPageVO getSuspiciousTransactions( public CcdiProjectSuspiciousTransactionPageVO getSuspiciousTransactions(
CcdiProjectSuspiciousTransactionQueryDTO queryDTO CcdiProjectSuspiciousTransactionQueryDTO queryDTO
) { ) {
ensureProjectExists(queryDTO.getProjectId()); ensureProjectExists(queryDTO.getProjectId());
normalizeSuspiciousTransactionQuery(queryDTO); normalizeSuspiciousTransactionQuery(queryDTO);
if (isSuspiciousTransactionScopeMismatch(queryDTO)) {
return emptySuspiciousTransactionPage();
}
prepareSuspiciousTransactionExternalBranch(queryDTO);
if (isExternalScopeWithoutSubject(queryDTO)) {
return emptySuspiciousTransactionPage();
}
Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>( Page<CcdiProjectSuspiciousTransactionItemVO> page = new Page<>(
defaultPageNum(queryDTO.getPageNum()), defaultPageNum(queryDTO.getPageNum()),
@@ -231,9 +397,12 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
); );
Page<CcdiProjectSuspiciousTransactionItemVO> resultPage = Page<CcdiProjectSuspiciousTransactionItemVO> resultPage =
overviewMapper.selectSuspiciousTransactionPage(page, queryDTO); overviewMapper.selectSuspiciousTransactionPage(page, queryDTO);
List<CcdiProjectSuspiciousTransactionItemVO> rows =
defaultList(resultPage == null ? null : resultPage.getRecords());
attachSuspiciousTransactionHitTags(rows, queryDTO);
CcdiProjectSuspiciousTransactionPageVO result = new CcdiProjectSuspiciousTransactionPageVO(); CcdiProjectSuspiciousTransactionPageVO result = new CcdiProjectSuspiciousTransactionPageVO();
result.setRows(defaultList(resultPage == null ? null : resultPage.getRecords())); result.setRows(rows);
result.setTotal(resultPage == null ? 0L : resultPage.getTotal()); result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
return result; return result;
} }
@@ -244,8 +413,16 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
) { ) {
ensureProjectExists(queryDTO.getProjectId()); ensureProjectExists(queryDTO.getProjectId());
normalizeSuspiciousTransactionQuery(queryDTO); normalizeSuspiciousTransactionQuery(queryDTO);
if (isSuspiciousTransactionScopeMismatch(queryDTO)) {
return List.of();
}
prepareSuspiciousTransactionExternalBranch(queryDTO);
return defaultList(overviewMapper.selectSuspiciousTransactionList(queryDTO)).stream() if (isExternalScopeWithoutSubject(queryDTO)) {
return List.of();
}
return defaultList(overviewMapper.selectReportSuspiciousTransactionList(queryDTO)).stream()
.map(this::buildSuspiciousTransactionExcelRow) .map(this::buildSuspiciousTransactionExcelRow)
.toList(); .toList();
} }
@@ -304,17 +481,77 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
.toList(); .toList();
} }
@Override
public CcdiProjectUnsubmittedAccountVO getUnsubmittedAccounts(CcdiProjectUnsubmittedAccountQueryDTO queryDTO) {
ensureProjectExists(queryDTO.getProjectId());
List<CcdiProjectUnsubmittedAccountItemVO> 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<String, List<CcdiProjectUnsubmittedAccountItemVO>> 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<CcdiProjectUnsubmittedAccountFlowVO> getUnsubmittedAccountFlows(
CcdiProjectUnsubmittedAccountQueryDTO queryDTO
) {
ensureProjectExists(queryDTO.getProjectId());
return defaultList(overviewMapper.selectUnsubmittedAccountFlowList(queryDTO));
}
@Override
public List<CcdiProjectUnsubmittedAccountFlowExcel> exportUnsubmittedAccountFlows(Long projectId) {
CcdiProjectUnsubmittedAccountQueryDTO queryDTO = new CcdiProjectUnsubmittedAccountQueryDTO();
queryDTO.setProjectId(projectId);
return exportUnsubmittedAccountFlows(queryDTO);
}
@Override
public List<CcdiProjectUnsubmittedAccountFlowExcel> exportUnsubmittedAccountFlows(
CcdiProjectUnsubmittedAccountQueryDTO queryDTO
) {
ensureProjectExists(queryDTO.getProjectId());
return defaultList(overviewMapper.selectUnsubmittedAccountFlowList(queryDTO)).stream()
.map(this::buildUnsubmittedAccountFlowExcelRow)
.toList();
}
@Override @Override
public void exportRiskDetails(HttpServletResponse response, Long projectId) { public void exportRiskDetails(HttpServletResponse response, Long projectId) {
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO(); CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
queryDTO.setProjectId(projectId); queryDTO.setProjectId(projectId);
queryDTO.setSuspiciousType("ALL"); queryDTO.setSuspiciousType(SUSPICIOUS_TYPE_ALL);
exportRiskDetails(response, queryDTO);
}
@Override
public void exportRiskDetails(
HttpServletResponse response,
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
) {
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows = exportSuspiciousTransactions(queryDTO); List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows = exportSuspiciousTransactions(queryDTO);
Long projectId = queryDTO.getProjectId();
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows = exportEmployeeCreditNegative(projectId); List<CcdiProjectEmployeeCreditNegativeExcel> creditRows = exportEmployeeCreditNegative(projectId);
List<CcdiProjectAbnormalAccountExcel> abnormalRows = exportAbnormalAccountPeople(projectId); List<CcdiProjectAbnormalAccountExcel> abnormalRows = exportAbnormalAccountPeople(projectId);
List<CcdiProjectUnsubmittedAccountFlowExcel> unsubmittedRows = exportUnsubmittedAccountFlows(projectId);
try { try {
workbookExporter.export(response, projectId, suspiciousRows, creditRows, abnormalRows); workbookExporter.export(response, projectId, suspiciousRows, creditRows, abnormalRows, unsubmittedRows);
} catch (IOException e) { } catch (IOException e) {
throw new ServiceException("导出风险明细失败"); throw new ServiceException("导出风险明细失败");
} }
@@ -334,16 +571,20 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
report.setUploadSubjects(defaultList(overviewMapper.selectReportUploadSubjects(projectId)).stream() report.setUploadSubjects(defaultList(overviewMapper.selectReportUploadSubjects(projectId)).stream()
.peek(item -> item.setDataPeriod(formatDataPeriod(item.getMinTrxDate(), item.getMaxTrxDate()))) .peek(item -> item.setDataPeriod(formatDataPeriod(item.getMinTrxDate(), item.getMaxTrxDate())))
.toList()); .toList());
report.setParams(buildReportParams(projectId)); report.setParams(buildReportParams(project));
report.setModelSummaries(defaultList(overviewMapper.selectReportRiskModelSummaries(projectId))); report.setModelSummaries(defaultList(overviewMapper.selectReportRiskModelSummaries(projectId)));
report.setExternalRiskSummary(getExternalRiskSummary(projectId));
report.setExternalModelSummaries(buildExternalReportModelSummaries(projectId));
report.setRiskPeople(defaultList(overviewMapper.selectReportRiskPeople(projectId)).stream() report.setRiskPeople(defaultList(overviewMapper.selectReportRiskPeople(projectId)).stream()
.peek(item -> item.setActionLabel(ACTION_LABEL)) .peek(item -> item.setActionLabel(ACTION_LABEL))
.toList()); .toList());
report.setExternalPersonWarnings(exportExternalPersonWarnings(projectId));
report.setSuspiciousTransactions(defaultList( report.setSuspiciousTransactions(defaultList(
overviewMapper.selectReportSuspiciousTransactionList(suspiciousQuery) overviewMapper.selectReportSuspiciousTransactionList(suspiciousQuery)
)); ));
report.setIllegalPeople(exportEmployeeCreditNegative(projectId)); report.setIllegalPeople(exportEmployeeCreditNegative(projectId));
report.setAbnormalAccounts(exportAbnormalAccountPeople(projectId)); report.setAbnormalAccounts(exportAbnormalAccountPeople(projectId));
report.setUnsubmittedAccountFlows(exportUnsubmittedAccountFlows(projectId));
try { try {
reportPdfExporter.export(response, report); reportPdfExporter.export(response, report);
@@ -445,10 +686,109 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
return row; return row;
} }
private CcdiProjectExternalPersonWarningExcel buildExternalPersonWarningExcelRow(
CcdiProjectExternalPersonWarningItemVO item
) {
CcdiProjectExternalPersonWarningExcel row = new CcdiProjectExternalPersonWarningExcel();
row.setName(item.getName());
row.setIdNo(item.getIdNo());
row.setSubjectType(item.getSubjectType());
row.setRiskLevel(item.getRiskLevel());
row.setModelCount(item.getModelCount());
row.setRiskPoint(item.getRiskPoint());
row.setRelatedObject(item.getRelatedObject());
row.setLatestTradeTime(item.getLatestTradeTime());
return row;
}
private List<CcdiProjectOverviewReportModelSummaryVO> buildExternalReportModelSummaries(Long projectId) {
return defaultList(overviewMapper.selectExternalRiskModelCardsByProjectId(projectId)).stream()
.map(this::buildExternalReportModelSummary)
.toList();
}
private CcdiProjectOverviewReportModelSummaryVO buildExternalReportModelSummary(CcdiProjectRiskModelCardVO card) {
CcdiProjectOverviewReportModelSummaryVO row = new CcdiProjectOverviewReportModelSummaryVO();
row.setModelCode(card.getModelCode());
row.setModelName(card.getModelName());
row.setWarningCount(card.getWarningCount());
row.setPeopleCount(card.getPeopleCount());
row.setPeopleNames("-");
return row;
}
private CcdiProjectRiskModelPeopleExcel buildRiskModelPeopleExcelRow(
CcdiProjectRiskModelPeopleItemVO item,
String subjectType
) {
CcdiProjectRiskModelPeopleExcel row = new CcdiProjectRiskModelPeopleExcel();
row.setPersonName(item.getStaffName());
row.setSubjectType(subjectType);
row.setIdNo(item.getIdNo());
row.setScopeName(item.getDepartment());
row.setModelNames(joinModelNames(item.getModelNames()));
row.setHitTags(joinHitTagNames(item.getHitTagList()));
return row;
}
private void ensureProjectExists(Long projectId) { private void ensureProjectExists(Long projectId) {
getRequiredProject(projectId); getRequiredProject(projectId);
} }
private CcdiProjectRiskExclusion buildRiskExclusion(CcdiProjectRiskExclusionSaveDTO dto) {
if (dto == null) {
throw new ServiceException("排除参数不能为空");
}
String exclusionType = normalizeExclusionType(dto.getExclusionType());
String ruleCode = normalizeRequired(dto.getRuleCode(), "规则编码不能为空").toUpperCase();
String excludeReason = normalizeRequired(dto.getExcludeReason(), "排除原因不能为空");
if (excludeReason.length() > 1000) {
throw new ServiceException("排除原因不能超过1000个字符");
}
CcdiProjectRiskExclusion exclusion = new CcdiProjectRiskExclusion();
exclusion.setProjectId(dto.getProjectId());
exclusion.setRuleCode(ruleCode);
exclusion.setExclusionType(exclusionType);
exclusion.setExcludeReason(excludeReason);
exclusion.setStaffIdCard(normalizeBlankToNull(dto.getStaffIdCard()));
exclusion.setBankStatementId(dto.getBankStatementId());
if (EXCLUSION_TYPE_STATEMENT.equals(exclusionType) && exclusion.getBankStatementId() == null) {
throw new ServiceException("流水型排除必须指定流水ID");
}
if (EXCLUSION_TYPE_OBJECT.equals(exclusionType) && (exclusion.getStaffIdCard() == null || exclusion.getStaffIdCard().isBlank())) {
throw new ServiceException("对象型排除必须指定人员证件号");
}
String operator = SecurityUtils.getUsername();
Date now = new Date();
exclusion.setCreateBy(operator);
exclusion.setCreateTime(now);
exclusion.setUpdateBy(operator);
exclusion.setUpdateTime(now);
return exclusion;
}
private String normalizeExclusionType(String exclusionType) {
String type = normalizeRequired(exclusionType, "排除类型不能为空").toUpperCase();
if (!EXCLUSION_TYPE_STATEMENT.equals(type) && !EXCLUSION_TYPE_OBJECT.equals(type)) {
throw new ServiceException("排除类型不正确");
}
return type;
}
private String normalizeRequired(String value, String message) {
if (value == null || value.isBlank()) {
throw new ServiceException(message);
}
return value.trim();
}
private String normalizeBlankToNull(String value) {
if (value == null || value.isBlank()) {
return null;
}
return value.trim();
}
private void normalizeRiskModelPeopleQuery(CcdiProjectRiskModelPeopleQueryDTO queryDTO) { private void normalizeRiskModelPeopleQuery(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
if (queryDTO.getMatchMode() == null || queryDTO.getMatchMode().isBlank()) { if (queryDTO.getMatchMode() == null || queryDTO.getMatchMode().isBlank()) {
queryDTO.setMatchMode("ANY"); queryDTO.setMatchMode("ANY");
@@ -457,14 +797,73 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase()); queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase());
} }
private void normalizeExternalRiskModelPeopleQuery(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
if (queryDTO.getMatchMode() == null || queryDTO.getMatchMode().isBlank()) {
queryDTO.setMatchMode("ANY");
return;
}
queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase());
}
private void normalizeSuspiciousTransactionQuery(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) { private void normalizeSuspiciousTransactionQuery(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
if (queryDTO.getModelCode() != null && !queryDTO.getModelCode().isBlank()) {
queryDTO.setModelCode(queryDTO.getModelCode().trim().toUpperCase());
} else {
queryDTO.setModelCode(null);
}
if (queryDTO.getSuspiciousType() == null || queryDTO.getSuspiciousType().isBlank()) { if (queryDTO.getSuspiciousType() == null || queryDTO.getSuspiciousType().isBlank()) {
queryDTO.setSuspiciousType("ALL"); queryDTO.setSuspiciousType(SUSPICIOUS_TYPE_ALL);
return; return;
} }
queryDTO.setSuspiciousType(queryDTO.getSuspiciousType().trim().toUpperCase()); queryDTO.setSuspiciousType(queryDTO.getSuspiciousType().trim().toUpperCase());
} }
private void prepareSuspiciousTransactionExternalBranch(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
if (!shouldCheckExternalPersonBranch(queryDTO)) {
queryDTO.setIncludeExternalPerson(false);
return;
}
queryDTO.setIncludeExternalPerson(hasExternalPersonSubject(queryDTO.getProjectId()));
}
private boolean shouldCheckExternalPersonBranch(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
if (SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(queryDTO.getSuspiciousType())) {
return true;
}
return SUSPICIOUS_TYPE_ALL.equals(queryDTO.getSuspiciousType())
&& (queryDTO.getModelCode() == null || isExternalModelCode(queryDTO.getModelCode()));
}
private boolean hasExternalPersonSubject(Long projectId) {
return overviewMapper.selectExternalPersonSubjectExistsByProjectId(projectId) != null;
}
private boolean isExternalScopeWithoutSubject(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
return (SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(queryDTO.getSuspiciousType())
|| isExternalModelCode(queryDTO.getModelCode()))
&& !Boolean.TRUE.equals(queryDTO.getIncludeExternalPerson());
}
private boolean isSuspiciousTransactionScopeMismatch(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
if (queryDTO.getModelCode() == null) {
return false;
}
boolean externalModel = isExternalModelCode(queryDTO.getModelCode());
return (SUSPICIOUS_TYPE_MODEL_RULE.equals(queryDTO.getSuspiciousType()) && externalModel)
|| (SUSPICIOUS_TYPE_EXTERNAL_PERSON.equals(queryDTO.getSuspiciousType()) && !externalModel);
}
private boolean isExternalModelCode(String modelCode) {
return modelCode != null && modelCode.startsWith("EXTERNAL_");
}
private CcdiProjectSuspiciousTransactionPageVO emptySuspiciousTransactionPage() {
CcdiProjectSuspiciousTransactionPageVO result = new CcdiProjectSuspiciousTransactionPageVO();
result.setRows(List.of());
result.setTotal(0L);
return result;
}
private CcdiProjectOverviewStatVO buildStat(String key, String label, Integer value) { private CcdiProjectOverviewStatVO buildStat(String key, String label, Integer value) {
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO(); CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
stat.setKey(key); stat.setKey(key);
@@ -526,15 +925,18 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
} }
private CcdiProjectSuspiciousTransactionExcel buildSuspiciousTransactionExcelRow( private CcdiProjectSuspiciousTransactionExcel buildSuspiciousTransactionExcelRow(
CcdiProjectSuspiciousTransactionItemVO item CcdiProjectOverviewReportSuspiciousTransactionVO item
) { ) {
CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel(); CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel();
row.setTrxDate(item.getTrxDate()); row.setTrxDate(item.getTrxDate());
row.setSuspiciousPersonName(item.getSuspiciousPersonName()); row.setLeAccountNo(item.getLeAccountNo());
row.setRelatedPersonName(item.getRelatedPersonName()); row.setLeAccountName(item.getLeAccountName());
row.setCustomerAccountName(item.getCustomerAccountName());
row.setCustomerAccountNo(item.getCustomerAccountNo());
row.setRelatedStaffDisplay(formatRelatedStaff(item.getRelatedStaffName(), item.getRelatedStaffCode())); row.setRelatedStaffDisplay(formatRelatedStaff(item.getRelatedStaffName(), item.getRelatedStaffCode()));
row.setRelationType(item.getRelationType()); row.setUserMemo(item.getUserMemo());
row.setSummaryAndCashType(formatSummaryAndCashType(item.getUserMemo(), item.getCashType())); row.setCashType(item.getCashType());
row.setHitTags(item.getHitTags());
row.setDisplayAmount(item.getDisplayAmount()); row.setDisplayAmount(item.getDisplayAmount());
return row; return row;
} }
@@ -563,24 +965,67 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
row.setAbnormalType(item.getAbnormalType()); row.setAbnormalType(item.getAbnormalType());
row.setAbnormalTime(item.getAbnormalTime()); row.setAbnormalTime(item.getAbnormalTime());
row.setStatus(item.getStatus()); row.setStatus(item.getStatus());
row.setReasonDetail(item.getReasonDetail());
row.setInvolvedAmount(item.getInvolvedAmount());
return row; return row;
} }
private List<CcdiProjectOverviewReportParamVO> buildReportParams(Long projectId) { private CcdiProjectUnsubmittedAccountSubjectVO buildUnsubmittedAccountSubject(
ModelParamAllVO response = modelParamService.selectAllParams(projectId); String subjectName,
return defaultList(response == null ? null : response.getModels()).stream() List<CcdiProjectUnsubmittedAccountItemVO> rows
.flatMap(model -> defaultList(model.getParams()).stream().map(param -> { ) {
CcdiProjectOverviewReportParamVO row = new CcdiProjectOverviewReportParamVO(); CcdiProjectUnsubmittedAccountSubjectVO subject = new CcdiProjectUnsubmittedAccountSubjectVO();
row.setModelName(model.getModelName()); subject.setSubjectName(subjectName);
row.setParamName(param.getParamName()); subject.setAccountCount(rows.size());
row.setParamValue(param.getParamValue()); subject.setTotalAmountCr(sumUnsubmittedAmount(rows, true));
row.setParamUnit(param.getParamUnit()); subject.setTotalAmountDr(sumUnsubmittedAmount(rows, false));
row.setParamDesc(param.getParamDesc()); subject.setNetAmount(subject.getTotalAmountCr().subtract(subject.getTotalAmountDr()));
return row; subject.setAccounts(rows);
})) return subject;
}
private BigDecimal sumUnsubmittedAmount(List<CcdiProjectUnsubmittedAccountItemVO> 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<CcdiProjectOverviewReportParamVO> buildReportParams(CcdiProject project) {
Long effectiveProjectId = "default".equals(project.getConfigType()) ? 0L : project.getProjectId();
return defaultList(modelParamMapper.selectByProjectId(effectiveProjectId)).stream()
.map(this::buildReportParamRow)
.toList(); .toList();
} }
private CcdiProjectOverviewReportParamVO buildReportParamRow(CcdiModelParam param) {
CcdiProjectOverviewReportParamVO row = new CcdiProjectOverviewReportParamVO();
row.setModelName(param.getModelName());
row.setParamName(param.getParamName());
row.setParamValue(param.getParamValue());
row.setParamUnit(param.getParamUnit());
row.setParamDesc(param.getParamDesc());
return row;
}
private String formatDataPeriod(String minTrxDate, String maxTrxDate) { private String formatDataPeriod(String minTrxDate, String maxTrxDate) {
if (minTrxDate == null || minTrxDate.isBlank() || maxTrxDate == null || maxTrxDate.isBlank()) { if (minTrxDate == null || minTrxDate.isBlank() || maxTrxDate == null || maxTrxDate.isBlank()) {
return "-"; return "-";
@@ -607,6 +1052,21 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
return safeMemo + "/" + safeCashType; return safeMemo + "/" + safeCashType;
} }
private String joinModelNames(List<String> modelNames) {
return defaultList(modelNames).stream()
.filter(item -> item != null && !item.isBlank())
.distinct()
.collect(Collectors.joining(""));
}
private String joinHitTagNames(List<CcdiProjectRiskHitTagVO> hitTags) {
return defaultList(hitTags).stream()
.map(CcdiProjectRiskHitTagVO::getRuleName)
.filter(item -> item != null && !item.isBlank())
.distinct()
.collect(Collectors.joining(""));
}
private CcdiProjectPersonAnalysisAbnormalDetailVO buildAbnormalDetail( private CcdiProjectPersonAnalysisAbnormalDetailVO buildAbnormalDetail(
List<CcdiBankStatementListVO> statementRows, List<CcdiBankStatementListVO> statementRows,
List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows
@@ -650,7 +1110,12 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
return; return;
} }
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = defaultList( Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = defaultList(
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds) bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
projectId,
bankStatementIds,
null,
null
)
).stream().filter(item -> item.getBankStatementId() != null) ).stream().filter(item -> item.getBankStatementId() != null)
.collect(Collectors.groupingBy( .collect(Collectors.groupingBy(
CcdiBankStatementHitTagVO::getBankStatementId, CcdiBankStatementHitTagVO::getBankStatementId,
@@ -662,6 +1127,39 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
))); )));
} }
private void attachSuspiciousTransactionHitTags(
List<CcdiProjectSuspiciousTransactionItemVO> rows,
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
) {
if (rows.isEmpty()) {
return;
}
List<Long> bankStatementIds = rows.stream()
.map(CcdiProjectSuspiciousTransactionItemVO::getBankStatementId)
.filter(item -> item != null)
.distinct()
.toList();
if (bankStatementIds.isEmpty()) {
return;
}
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = defaultList(
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
queryDTO.getProjectId(),
bankStatementIds,
queryDTO.getModelCode(),
queryDTO.getSuspiciousType()
)
).stream().filter(item -> item.getBankStatementId() != null)
.collect(Collectors.groupingBy(
CcdiBankStatementHitTagVO::getBankStatementId,
LinkedHashMap::new,
Collectors.toList()
));
rows.forEach(row -> row.setHitTags(new ArrayList<>(
hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList())
)));
}
private void normalizeObjectRows(List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows) { private void normalizeObjectRows(List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows) {
objectRows.forEach(row -> { objectRows.forEach(row -> {
if (row.getRiskTags() == null) { if (row.getRiskTags() == null) {

View File

@@ -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.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; 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.excel.CcdiProjectUnsubmittedAccountFlowExcel;
import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.FileUtils;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
@@ -29,7 +30,8 @@ public class CcdiProjectRiskDetailWorkbookExporter {
Long projectId, Long projectId,
List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows, List<CcdiProjectSuspiciousTransactionExcel> suspiciousRows,
List<CcdiProjectEmployeeCreditNegativeExcel> creditRows, List<CcdiProjectEmployeeCreditNegativeExcel> creditRows,
List<CcdiProjectAbnormalAccountExcel> abnormalRows List<CcdiProjectAbnormalAccountExcel> abnormalRows,
List<CcdiProjectUnsubmittedAccountFlowExcel> unsubmittedRows
) throws IOException { ) throws IOException {
response.setContentType(CONTENT_TYPE); response.setContentType(CONTENT_TYPE);
FileUtils.setAttachmentResponseHeader(response, "风险明细_" + projectId + ".xlsx"); FileUtils.setAttachmentResponseHeader(response, "风险明细_" + projectId + ".xlsx");
@@ -38,25 +40,40 @@ public class CcdiProjectRiskDetailWorkbookExporter {
writeSuspiciousSheet(workbook.createSheet("涉疑交易明细"), suspiciousRows); writeSuspiciousSheet(workbook.createSheet("涉疑交易明细"), suspiciousRows);
writeCreditSheet(workbook.createSheet("员工负面征信信息"), creditRows); writeCreditSheet(workbook.createSheet("员工负面征信信息"), creditRows);
writeAbnormalAccountSheet(workbook.createSheet("异常账户人员信息"), abnormalRows); writeAbnormalAccountSheet(workbook.createSheet("异常账户人员信息"), abnormalRows);
writeUnsubmittedAccountSheet(workbook.createSheet("未提交账户流水"), unsubmittedRows);
workbook.write(response.getOutputStream()); workbook.write(response.getOutputStream());
} }
} }
private void writeSuspiciousSheet(Sheet sheet, List<CcdiProjectSuspiciousTransactionExcel> rows) { private void writeSuspiciousSheet(Sheet sheet, List<CcdiProjectSuspiciousTransactionExcel> rows) {
Row header = sheet.createRow(0); Row header = sheet.createRow(0);
String[] headers = { "交易时间", "可疑人员", "关联人", "关联员工", "关系", "摘要/交易类型", "交易金额" }; String[] headers = {
"交易时间",
"本方账户",
"本方主体",
"对方名称",
"对方账户",
"关联员工",
"摘要",
"交易类型",
"交易金额",
"异常标签"
};
writeHeader(header, headers); writeHeader(header, headers);
for (int i = 0; i < rows.size(); i++) { for (int i = 0; i < rows.size(); i++) {
CcdiProjectSuspiciousTransactionExcel item = rows.get(i); CcdiProjectSuspiciousTransactionExcel item = rows.get(i);
Row row = sheet.createRow(i + 1); Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(safeText(item.getTrxDate())); row.createCell(0).setCellValue(safeText(item.getTrxDate()));
row.createCell(1).setCellValue(safeText(item.getSuspiciousPersonName())); row.createCell(1).setCellValue(safeText(item.getLeAccountNo()));
row.createCell(2).setCellValue(safeText(item.getRelatedPersonName())); row.createCell(2).setCellValue(safeText(item.getLeAccountName()));
row.createCell(3).setCellValue(safeText(item.getRelatedStaffDisplay())); row.createCell(3).setCellValue(safeText(item.getCustomerAccountName()));
row.createCell(4).setCellValue(safeText(item.getRelationType())); row.createCell(4).setCellValue(safeText(item.getCustomerAccountNo()));
row.createCell(5).setCellValue(safeText(item.getSummaryAndCashType())); row.createCell(5).setCellValue(safeText(item.getRelatedStaffDisplay()));
row.createCell(6).setCellValue(safeNumber(item.getDisplayAmount())); row.createCell(6).setCellValue(safeText(item.getUserMemo()));
row.createCell(7).setCellValue(safeText(item.getCashType()));
row.createCell(8).setCellValue(safeNumber(item.getDisplayAmount()));
row.createCell(9).setCellValue(safeText(item.getHitTags()));
} }
} }
@@ -92,7 +109,7 @@ public class CcdiProjectRiskDetailWorkbookExporter {
private void writeAbnormalAccountSheet(Sheet sheet, List<CcdiProjectAbnormalAccountExcel> rows) { private void writeAbnormalAccountSheet(Sheet sheet, List<CcdiProjectAbnormalAccountExcel> rows) {
Row header = sheet.createRow(0); Row header = sheet.createRow(0);
String[] headers = { "账号", "开户人", "银行", "异常类型", "异常发生时间", "状态" }; String[] headers = { "账号", "开户人", "银行", "异常类型", "异常发生时间", "状态", "命中原因", "涉及金额" };
writeHeader(header, headers); writeHeader(header, headers);
for (int i = 0; i < rows.size(); i++) { for (int i = 0; i < rows.size(); i++) {
@@ -104,6 +121,42 @@ public class CcdiProjectRiskDetailWorkbookExporter {
row.createCell(3).setCellValue(safeText(item.getAbnormalType())); row.createCell(3).setCellValue(safeText(item.getAbnormalType()));
row.createCell(4).setCellValue(safeText(item.getAbnormalTime())); row.createCell(4).setCellValue(safeText(item.getAbnormalTime()));
row.createCell(5).setCellValue(safeText(item.getStatus())); row.createCell(5).setCellValue(safeText(item.getStatus()));
row.createCell(6).setCellValue(safeText(item.getReasonDetail()));
row.createCell(7).setCellValue(safeNumber(item.getInvolvedAmount()));
}
}
private void writeUnsubmittedAccountSheet(Sheet sheet, List<CcdiProjectUnsubmittedAccountFlowExcel> 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()));
} }
} }

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants; import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.ProjectAccessScope;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
@@ -14,6 +15,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper; import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiProjectService; import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.lsfx.client.LsfxAnalysisClient; import com.ruoyi.lsfx.client.LsfxAnalysisClient;
@@ -54,6 +56,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Resource @Resource
private ApplicationEventPublisher applicationEventPublisher; private ApplicationEventPublisher applicationEventPublisher;
@Resource
private CcdiProjectAccessService projectAccessService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) { public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) {
@@ -82,7 +87,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
// 5. 返回VO // 5. 返回VO
CcdiProjectVO vo = new CcdiProjectVO(); CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(project, vo); BeanUtils.copyProperties(project, vo);
fillLatestTagFailure(project, vo); fillProjectExtraFields(project, vo);
return vo; return vo;
} }
@@ -96,6 +101,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
if (existingProject == null) { if (existingProject == null) {
throw new ServiceException("项目不存在"); throw new ServiceException("项目不存在");
} }
projectAccessService.assertCanOperate(dto.getProjectId());
// 只更新允许修改的字段 // 只更新允许修改的字段
existingProject.setProjectName(dto.getProjectName()); existingProject.setProjectName(dto.getProjectName());
@@ -106,39 +112,59 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
CcdiProjectVO vo = new CcdiProjectVO(); CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(existingProject, vo); BeanUtils.copyProperties(existingProject, vo);
fillProjectExtraFields(existingProject, vo);
return vo; return vo;
} }
@Override @Override
public boolean deleteProject(Long projectId) { public boolean deleteProject(Long projectId, String operator) {
return projectMapper.deleteById(projectId) > 0; projectAccessService.assertCanDelete(projectId);
return projectMapper.markProjectDeleted(projectId, resolveOperator(operator)) > 0;
}
@Override
public void restoreProject(Long projectId, String operator) {
projectAccessService.assertCanManageDeletedProjects();
int rows = projectMapper.restoreDeletedProject(projectId, resolveOperator(operator));
if (rows <= 0) {
throw new ServiceException("仅已删除项目允许恢复");
}
log.info("【项目】项目状态变更: projectId={}, oldStatus={}, oldStatusLabel={}, newStatus={}, newStatusLabel={}, operator={}",
projectId, CcdiProjectStatusConstants.DELETED, resolveStatusLabel(CcdiProjectStatusConstants.DELETED),
CcdiProjectStatusConstants.COMPLETED, resolveStatusLabel(CcdiProjectStatusConstants.COMPLETED),
resolveOperator(operator));
} }
@Override @Override
public CcdiProjectVO getProjectById(Long projectId) { public CcdiProjectVO getProjectById(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProject project = projectMapper.selectById(projectId); CcdiProject project = projectMapper.selectById(projectId);
if (project == null) { if (project == null) {
return null; return null;
} }
CcdiProjectVO vo = new CcdiProjectVO(); CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(project, vo); BeanUtils.copyProperties(project, vo);
fillLatestTagFailure(project, vo); fillProjectExtraFields(project, vo);
return vo; return vo;
} }
@Override @Override
public Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO) { public Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO) {
return projectMapper.selectProjectPage(page, queryDTO); ProjectAccessScope scope = projectAccessService.buildCurrentScope();
Page<CcdiProjectVO> result = projectMapper.selectProjectPage(page, queryDTO, scope);
fillProjectExtraFields(result.getRecords());
return result;
} }
@Override @Override
public List<CcdiProjectHistoryListItemVO> listHistoryProjects(CcdiProjectQueryDTO queryDTO) { public List<CcdiProjectHistoryListItemVO> listHistoryProjects(CcdiProjectQueryDTO queryDTO) {
return projectMapper.selectHistoryProjects(queryDTO); return projectMapper.selectHistoryProjects(queryDTO, projectAccessService.buildCurrentScope());
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator) { public CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator) {
projectAccessService.assertSourceProjectsReadable(dto.getSourceProjectIds());
CcdiProjectSaveDTO saveDTO = new CcdiProjectSaveDTO(); CcdiProjectSaveDTO saveDTO = new CcdiProjectSaveDTO();
saveDTO.setProjectName(dto.getProjectName()); saveDTO.setProjectName(dto.getProjectName());
saveDTO.setDescription(dto.getDescription()); saveDTO.setDescription(dto.getDescription());
@@ -158,50 +184,49 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Override @Override
public CcdiProjectStatusCountsVO getStatusCounts() { public CcdiProjectStatusCountsVO getStatusCounts() {
CcdiProjectStatusCountsVO vo = new CcdiProjectStatusCountsVO(); CcdiProjectStatusCountsVO vo = new CcdiProjectStatusCountsVO();
ProjectAccessScope scope = projectAccessService.buildCurrentScope();
// 统计全部项目 // 统计全部项目
Long totalCount = projectMapper.selectCount(null); LambdaQueryWrapper<CcdiProject> baseWrapper = buildScopeWrapper(scope);
Long totalCount = projectMapper.selectCount(baseWrapper);
vo.setAll(totalCount); vo.setAll(totalCount);
// 统计进行中项目状态0 // 统计进行中项目状态0
Long status0Count = projectMapper.selectCount( Long status0Count = projectMapper.selectCount(buildScopeWrapper(scope)
new LambdaQueryWrapper<CcdiProject>() .eq(CcdiProject::getStatus, CcdiProjectStatusConstants.PROCESSING));
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.PROCESSING)
);
vo.setStatus0(status0Count); vo.setStatus0(status0Count);
// 统计已完成项目状态1 // 统计已完成项目状态1
Long status1Count = projectMapper.selectCount( Long status1Count = projectMapper.selectCount(buildScopeWrapper(scope)
new LambdaQueryWrapper<CcdiProject>() .eq(CcdiProject::getStatus, CcdiProjectStatusConstants.COMPLETED));
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.COMPLETED)
);
vo.setStatus1(status1Count); vo.setStatus1(status1Count);
// 统计已归档项目状态2 // 统计已归档项目状态2
Long status2Count = projectMapper.selectCount( Long status2Count = projectMapper.selectCount(buildScopeWrapper(scope)
new LambdaQueryWrapper<CcdiProject>() .eq(CcdiProject::getStatus, CcdiProjectStatusConstants.ARCHIVED));
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.ARCHIVED)
);
vo.setStatus2(status2Count); vo.setStatus2(status2Count);
Long status3Count = projectMapper.selectCount( Long status3Count = projectMapper.selectCount(buildScopeWrapper(scope)
new LambdaQueryWrapper<CcdiProject>() .eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAGGING));
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAGGING)
);
vo.setStatus3(status3Count); vo.setStatus3(status3Count);
Long status4Count = projectMapper.selectCount( Long status4Count = projectMapper.selectCount(buildScopeWrapper(scope)
new LambdaQueryWrapper<CcdiProject>() .eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAG_FAILED));
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAG_FAILED)
);
vo.setStatus4(status4Count); vo.setStatus4(status4Count);
Long status5Count = 0L;
if (isProjectAdmin(scope)) {
status5Count = projectMapper.selectDeletedProjectCount(scope);
}
vo.setStatus5(status5Count);
return vo; return vo;
} }
@Override @Override
public void archiveProject(Long projectId, String operator) { public void archiveProject(Long projectId, String operator) {
CcdiProject project = getRequiredProject(projectId); CcdiProject project = getRequiredProject(projectId);
projectAccessService.assertCanOperate(projectId);
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) { if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
throw new ServiceException("项目已归档,无需重复操作"); throw new ServiceException("项目已归档,无需重复操作");
} }
@@ -223,6 +248,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
public void updateProjectStatus(Long projectId, String status, String operator) { public void updateProjectStatus(Long projectId, String status, String operator) {
CcdiProject project = getRequiredProject(projectId); CcdiProject project = getRequiredProject(projectId);
String oldStatus = project.getStatus(); String oldStatus = project.getStatus();
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus())) {
throw new ServiceException("已删除项目不允许更新状态");
}
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus()) if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())
&& !CcdiProjectStatusConstants.ARCHIVED.equals(status)) { && !CcdiProjectStatusConstants.ARCHIVED.equals(status)) {
throw new ServiceException("已归档项目不允许重新进入打标流程"); throw new ServiceException("已归档项目不允许重新进入打标流程");
@@ -241,6 +269,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Override @Override
public void ensureProjectCanStartTagging(Long projectId) { public void ensureProjectCanStartTagging(Long projectId) {
CcdiProject project = getRequiredProject(projectId); CcdiProject project = getRequiredProject(projectId);
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus())) {
throw new ServiceException("已删除项目不允许重新进入打标流程");
}
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) { if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
throw new ServiceException("已归档项目不允许重新进入打标流程"); throw new ServiceException("已归档项目不允许重新进入打标流程");
} }
@@ -249,6 +280,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Override @Override
public void ensureProjectNotArchived(Long projectId, String message) { public void ensureProjectNotArchived(Long projectId, String message) {
CcdiProject project = getRequiredProject(projectId); CcdiProject project = getRequiredProject(projectId);
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus())) {
throw new ServiceException("已删除项目暂不允许操作");
}
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) { if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
throw new ServiceException(message); throw new ServiceException(message);
} }
@@ -257,6 +291,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Override @Override
public void ensureProjectWritable(Long projectId, String message) { public void ensureProjectWritable(Long projectId, String message) {
CcdiProject project = getRequiredProject(projectId); CcdiProject project = getRequiredProject(projectId);
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus())) {
throw new ServiceException("已删除项目暂不允许操作");
}
if (CcdiProjectStatusConstants.TAGGING.equals(project.getStatus())) { if (CcdiProjectStatusConstants.TAGGING.equals(project.getStatus())) {
throw new ServiceException(message); throw new ServiceException(message);
} }
@@ -277,6 +314,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
case CcdiProjectStatusConstants.ARCHIVED -> "已归档"; case CcdiProjectStatusConstants.ARCHIVED -> "已归档";
case CcdiProjectStatusConstants.TAGGING -> "打标中"; case CcdiProjectStatusConstants.TAGGING -> "打标中";
case CcdiProjectStatusConstants.TAG_FAILED -> "打标失败"; case CcdiProjectStatusConstants.TAG_FAILED -> "打标失败";
case CcdiProjectStatusConstants.DELETED -> "已删除";
default -> "未知"; default -> "未知";
}; };
} }
@@ -293,6 +331,44 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
vo.setLatestTagTaskEndTime(latestFailedTask.getEndTime()); vo.setLatestTagTaskEndTime(latestFailedTask.getEndTime());
} }
private LambdaQueryWrapper<CcdiProject> buildScopeWrapper(ProjectAccessScope scope) {
LambdaQueryWrapper<CcdiProject> wrapper = new LambdaQueryWrapper<>();
wrapper.ne(CcdiProject::getStatus, CcdiProjectStatusConstants.DELETED);
if (scope != null && !scope.isViewAllProjects()) {
wrapper.eq(CcdiProject::getCreateBy, scope.getUsername());
}
return wrapper;
}
private boolean isProjectAdmin(ProjectAccessScope scope) {
return scope != null && (scope.isSuperAdmin() || scope.isProjectManager());
}
private void fillProjectExtraFields(List<CcdiProjectVO> records) {
if (records == null || records.isEmpty()) {
return;
}
ProjectAccessScope scope = projectAccessService.buildCurrentScope();
for (CcdiProjectVO vo : records) {
fillProjectAccessFields(vo, scope);
}
}
private void fillProjectExtraFields(CcdiProject project, CcdiProjectVO vo) {
fillLatestTagFailure(project, vo);
fillProjectAccessFields(vo, projectAccessService.buildCurrentScope());
}
private void fillProjectAccessFields(CcdiProjectVO vo, ProjectAccessScope scope) {
if (vo == null || scope == null) {
return;
}
boolean ownedByCurrentUser = Objects.equals(scope.getUsername(), vo.getCreateBy());
vo.setOwnedByCurrentUser(ownedByCurrentUser);
vo.setCanOperate(scope.isSuperAdmin() || ownedByCurrentUser);
vo.setCanDelete(isProjectAdmin(scope) || ownedByCurrentUser);
}
private String resolveOperator(String operator) { private String resolveOperator(String operator) {
return StringUtils.hasText(operator) ? operator : "system"; return StringUtils.hasText(operator) ? operator : "system";
} }

View File

@@ -10,6 +10,8 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryD
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectIncreaseLendingQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectIncreaseLendingExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
@@ -25,10 +27,13 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListItemV
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyDebtDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyDebtDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyIncomeDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyIncomeDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectIncreaseLendingListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectIncreaseLendingListVO;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectSpecialCheckMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectSpecialCheckMapper;
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService; import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
@@ -178,6 +183,37 @@ public class CcdiProjectSpecialCheckServiceImpl implements ICcdiProjectSpecialCh
return detail; return detail;
} }
@Override
public CcdiProjectIncreaseLendingListVO getIncreaseLendingList(CcdiProjectIncreaseLendingQueryDTO queryDTO) {
validateIncreaseLendingQuery(queryDTO);
Page<CcdiProjectIncreaseLendingListItemVO> page = new Page<>(
defaultPageNum(queryDTO.getPageNum()),
defaultPageSize(queryDTO.getPageSize())
);
Page<CcdiProjectIncreaseLendingListItemVO> resultPage = specialCheckMapper.selectIncreaseLendingPage(page, queryDTO);
CcdiProjectIncreaseLendingListVO result = new CcdiProjectIncreaseLendingListVO();
result.setRows(resultPage == null ? List.of() : defaultList(resultPage.getRecords()));
result.setTotal(resultPage == null ? 0L : resultPage.getTotal());
return result;
}
@Override
public List<CcdiProjectIncreaseLendingExcel> exportIncreaseLendingList(CcdiProjectIncreaseLendingQueryDTO queryDTO) {
validateIncreaseLendingQuery(queryDTO);
return defaultList(specialCheckMapper.selectIncreaseLendingExportList(queryDTO));
}
private void validateIncreaseLendingQuery(CcdiProjectIncreaseLendingQueryDTO queryDTO) {
ensureProjectExists(queryDTO.getProjectId());
if (StringUtils.isBlank(queryDTO.getStaffId())
&& StringUtils.isBlank(queryDTO.getStaffIdCard())
&& StringUtils.isBlank(queryDTO.getApprover())) {
throw new ServiceException("柜员号、员工身份证和审核人柜员号至少填写一项");
}
}
private void ensureProjectExists(Long projectId) { private void ensureProjectExists(Long projectId) {
CcdiProject project = projectMapper.selectById(projectId); CcdiProject project = projectMapper.selectById(projectId);
if (project == null) { if (project == null) {
@@ -191,6 +227,7 @@ public class CcdiProjectSpecialCheckServiceImpl implements ICcdiProjectSpecialCh
incomeDetail.setSelfIncome(BigDecimal.ZERO); incomeDetail.setSelfIncome(BigDecimal.ZERO);
incomeDetail.setSpouseIncome(BigDecimal.ZERO); incomeDetail.setSpouseIncome(BigDecimal.ZERO);
incomeDetail.setTotalIncome(BigDecimal.ZERO); incomeDetail.setTotalIncome(BigDecimal.ZERO);
incomeDetail.setExplainableIncome(BigDecimal.ZERO);
detail.setIncomeDetail(incomeDetail); detail.setIncomeDetail(incomeDetail);
} }

View File

@@ -72,6 +72,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="displayAmount" column="displayAmount"/> <result property="displayAmount" column="displayAmount"/>
</resultMap> </resultMap>
<resultMap id="CcdiBankStatementCounterpartyVOResultMap"
type="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementCounterpartyVO">
<result property="customerAccountName" column="customerAccountName"/>
<result property="nameEmpty" column="nameEmpty"/>
<result property="accountCount" column="accountCount"/>
<result property="inAmount" column="inAmount"/>
<result property="inCount" column="inCount"/>
<result property="outAmount" column="outAmount"/>
<result property="outCount" column="outCount"/>
<result property="totalAmount" column="totalAmount"/>
<result property="totalCount" column="totalCount"/>
<result property="latestTrxDate" column="latestTrxDate"/>
</resultMap>
<resultMap id="CcdiBankStatementDetailVOResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO"> <resultMap id="CcdiBankStatementDetailVOResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO">
<id property="bankStatementId" column="bankStatementId"/> <id property="bankStatementId" column="bankStatementId"/>
<result property="projectId" column="projectId"/> <result property="projectId" column="projectId"/>
@@ -215,6 +229,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{item} #{item}
</foreach> </foreach>
</if> </if>
<if test="query.ourCertNos != null and query.ourCertNos.size() > 0">
AND bs.cret_no IN
<foreach collection="query.ourCertNos" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
<if test="query.ourBanks != null and query.ourBanks.size() > 0"> <if test="query.ourBanks != null and query.ourBanks.size() > 0">
AND bs.BANK IN AND bs.BANK IN
<foreach collection="query.ourBanks" item="item" open="(" separator="," close=")"> <foreach collection="query.ourBanks" item="item" open="(" separator="," close=")">
@@ -278,6 +298,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</choose> </choose>
</sql> </sql>
<sql id="counterpartyNameExpr">
TRIM(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''))
</sql>
<sql id="counterpartyOrderBy">
<choose>
<when test="query.orderBy == 'inAmount' and query.orderDirection == 'asc'">
ORDER BY inAmount ASC, totalCount DESC, customerAccountName ASC
</when>
<when test="query.orderBy == 'inAmount' and query.orderDirection == 'desc'">
ORDER BY inAmount DESC, totalCount DESC, customerAccountName ASC
</when>
<when test="query.orderBy == 'outAmount' and query.orderDirection == 'asc'">
ORDER BY outAmount ASC, totalCount DESC, customerAccountName ASC
</when>
<when test="query.orderBy == 'outAmount' and query.orderDirection == 'desc'">
ORDER BY outAmount DESC, totalCount DESC, customerAccountName ASC
</when>
<when test="query.orderBy == 'totalCount' and query.orderDirection == 'asc'">
ORDER BY totalCount ASC, totalAmount DESC, customerAccountName ASC
</when>
<when test="query.orderBy == 'totalCount' and query.orderDirection == 'desc'">
ORDER BY totalCount DESC, totalAmount DESC, customerAccountName ASC
</when>
<when test="query.orderBy == 'latestTrxDate' and query.orderDirection == 'asc'">
ORDER BY latestTrxDate ASC, totalAmount DESC, customerAccountName ASC
</when>
<when test="query.orderBy == 'latestTrxDate' and query.orderDirection == 'desc'">
ORDER BY latestTrxDate DESC, totalAmount DESC, customerAccountName ASC
</when>
<when test="query.orderBy == 'totalAmount' and query.orderDirection == 'asc'">
ORDER BY totalAmount ASC, totalCount DESC, customerAccountName ASC
</when>
<otherwise>
ORDER BY totalAmount DESC, totalCount DESC, customerAccountName ASC
</otherwise>
</choose>
</sql>
<select id="selectStatementPage" resultMap="CcdiBankStatementListVOResultMap"> <select id="selectStatementPage" resultMap="CcdiBankStatementListVOResultMap">
SELECT SELECT
<include refid="statementListColumns"/> <include refid="statementListColumns"/>
@@ -288,6 +347,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="statementOrderBy"/> <include refid="statementOrderBy"/>
</select> </select>
<select id="selectCounterpartyPage" resultMap="CcdiBankStatementCounterpartyVOResultMap">
SELECT
grouped.customerAccountName,
CASE WHEN grouped.customerAccountName = '' THEN 1 ELSE 0 END AS nameEmpty,
grouped.accountCount,
grouped.inAmount,
grouped.inCount,
grouped.outAmount,
grouped.outCount,
grouped.totalAmount,
grouped.totalCount,
grouped.latestTrxDate
FROM (
SELECT
<include refid="counterpartyNameExpr"/> AS customerAccountName,
COUNT(DISTINCT NULLIF(TRIM(IFNULL(bs.CUSTOMER_ACCOUNT_NO, '')), '')) AS accountCount,
SUM(IFNULL(bs.AMOUNT_CR, 0)) AS inAmount,
SUM(CASE WHEN IFNULL(bs.AMOUNT_CR, 0) > 0 THEN 1 ELSE 0 END) AS inCount,
SUM(IFNULL(bs.AMOUNT_DR, 0)) AS outAmount,
SUM(CASE WHEN IFNULL(bs.AMOUNT_DR, 0) > 0 THEN 1 ELSE 0 END) AS outCount,
SUM(IFNULL(bs.AMOUNT_CR, 0) + IFNULL(bs.AMOUNT_DR, 0)) AS totalAmount,
COUNT(1) AS totalCount,
DATE_FORMAT(MAX(<include refid="parsedTrxDateExpr"/>), '%Y-%m-%d %H:%i:%s') AS latestTrxDate
FROM ccdi_bank_statement bs
<where>
<include refid="statementFilterWhere"/>
</where>
GROUP BY <include refid="counterpartyNameExpr"/>
) grouped
<include refid="counterpartyOrderBy"/>
</select>
<select id="selectStatementListForExport" resultMap="CcdiBankStatementListVOResultMap"> <select id="selectStatementListForExport" resultMap="CcdiBankStatementListVOResultMap">
SELECT SELECT
<include refid="statementListColumns"/> <include refid="statementListColumns"/>

Some files were not shown because too many files have changed in this diff Show More