Compare commits

...

122 Commits

Author SHA1 Message Date
wjj
addea20fa1 新增项目证据库一期功能 2026-04-21 16:46:47 +08:00
wjj
d4ac165723 完善员工招聘历史工作经历功能 2026-04-20 14:52:07 +08:00
wjj
1bb24ab0a2 完成账户库管理功能开发与验收 2026-04-14 10:16:16 +08:00
wjj
9c22e8a3ce 新增账户库管理设计方案文档 2026-04-09 19:42:21 +08:00
wjj
03282c9b69 统一前端页面蓝灰换皮样式 2026-04-03 11:00:33 +08:00
wkc
8190946a87 修改后端地址 2026-03-31 10:24:06 +08:00
wkc
a8e15e16d9 收敛后端重启脚本停机范围 2026-03-30 17:28:31 +08:00
wkc
933214a495 补充项目详情风险人员导出能力 2026-03-30 15:59:06 +08:00
wkc
b96161ecf4 补充风险总览人员导出前后端实施计划 2026-03-30 15:29:38 +08:00
wkc
3f424a5b7e 补充风险总览人员列表导出设计文档 2026-03-30 15:27:02 +08:00
wkc
ea6bd5213f 完成风险明细统一导出功能 2026-03-30 14:32:41 +08:00
wkc
28b846134a 补充风险明细统一导出实施计划 2026-03-30 14:24:49 +08:00
wkc
40194c86fb 补充风险明细统一导出设计文档 2026-03-30 14:20:58 +08:00
wkc
c9838024b3 更新NAS环境数据库端口配置 2026-03-30 13:53:00 +08:00
wkc
d582a65978 修复历史项目导入解环与流水查询SQL 2026-03-30 13:46:04 +08:00
wkc
0a3c03dcf9 完成项目详情风险人员分页改造 2026-03-29 18:44:07 +08:00
wkc
dd3aa5bbae 补充风险总览员工列表分页实施计划 2026-03-29 11:47:08 +08:00
wkc
865d8f823b 补充风险总览员工列表分页设计文档 2026-03-29 11:41:49 +08:00
wkc
9df1b956b3 完成历史项目导入前端闭环 2026-03-29 10:01:42 +08:00
wkc
0889ee4533 收敛历史导入文件前端只读展示 2026-03-29 09:59:56 +08:00
wkc
f2cc9e2700 重构历史项目导入弹窗 2026-03-29 09:58:55 +08:00
wkc
a019abb950 接通历史项目导入前端提交链路 2026-03-29 09:56:46 +08:00
wkc
d6457491e8 接通历史项目导入后端闭环 2026-03-29 09:56:01 +08:00
wkc
46d190aa74 实现历史项目流水复制后端逻辑 2026-03-29 09:54:05 +08:00
wkc
b098d4eed1 实现历史项目导入任务提交流程 2026-03-29 09:49:44 +08:00
wkc
eb0d896114 补充历史项目导入接口契约 2026-03-29 09:47:43 +08:00
wkc
36576fab78 补充历史导入文件记录字段 2026-03-29 09:45:54 +08:00
wkc
c1d56cc153 新增历史项目导入实施计划 2026-03-29 09:40:44 +08:00
wkc
26f77bf458 新增历史项目导入设计文档 2026-03-29 09:37:36 +08:00
wkc
65f25a9258 移除顶部导航源码和文档入口 2026-03-28 17:22:18 +08:00
wkc
2cb4481c3b 实现风险明细员工负面征信功能 2026-03-28 16:13:24 +08:00
wkc
559572da8c 新增风险明细员工负面征信实施计划 2026-03-28 16:06:57 +08:00
wkc
c1018fea0c 新增风险明细员工负面征信设计文档 2026-03-28 16:03:17 +08:00
wkc
cf36b5f05a 新增涉疑交易明细查询导出并补充对手方证件信息 2026-03-27 17:31:11 +08:00
wkc
5e968c8716 对齐结果总览涉疑交易明细样式 2026-03-27 17:26:47 +08:00
wkc
ed1d07ad05 新增开发风险明细涉疑交易明细实施计划 2026-03-27 16:33:10 +08:00
wkc
dc578762b3 新增开发风险明细涉疑交易明细设计文档 2026-03-27 16:27:25 +08:00
wkc
e44e632bb6 切换后端与lsfx-mock默认数据库到MySQL8 2026-03-27 15:29:40 +08:00
wkc
3f98d59741 完成结果总览卡片结构合并实现 2026-03-27 15:25:23 +08:00
wkc
e7ad46edaf 优化结果总览标题层级与人员区标题 2026-03-27 15:05:59 +08:00
wkc
966754e8c6 补充结果总览卡片合并前端实施记录 2026-03-27 14:56:10 +08:00
wkc
7cffdc9e2b 统一结果总览模型与明细区块标题 2026-03-27 14:53:40 +08:00
wkc
a76806acfc 合并结果总览顶部风险总览卡片 2026-03-27 14:52:12 +08:00
wkc
55c8f1c29c 锁定结果总览卡片合并结构断言 2026-03-27 14:49:56 +08:00
wkc
d914c93e93 补充结果总览卡片结构合并实施计划 2026-03-27 14:44:10 +08:00
wkc
3ea227141d 补充结果总览卡片结构合并设计文档 2026-03-27 14:37:58 +08:00
wkc
3a2e4d86e3 补充项目列表重新分析前端实施记录 2026-03-27 14:29:15 +08:00
wkc
9e0efe8010 补充项目列表重新分析确认交互 2026-03-27 14:28:56 +08:00
wkc
2c793eaed6 补充项目列表重新分析前后端实施计划 2026-03-27 14:14:49 +08:00
wkc
d931ac185d 补充项目列表重新分析确认刷新设计 2026-03-27 14:09:07 +08:00
wkc
0e593a9202 补充OpenClaw特权模式设计与实施记录 2026-03-26 09:50:50 +08:00
wkc
762af9de90 补充项目分析弹窗前端修复记录 2026-03-26 09:15:59 +08:00
wkc
b5733486fd 收口项目分析弹窗右侧主区节奏 2026-03-26 09:15:13 +08:00
wkc
d4421862a8 修正项目分析弹窗侧栏档案展示 2026-03-26 09:13:30 +08:00
wkc
21b8b7bf41 收紧项目分析弹窗头带与滚动布局 2026-03-26 09:12:07 +08:00
wkc
2b701602ff 补充项目分析弹窗展示修正实施计划 2026-03-26 09:08:35 +08:00
wkc
cda1028c48 补充项目分析弹窗展示优化设计 2026-03-25 19:44:11 +08:00
wkc
60f935da27 重构家庭资产负债详情展示 2026-03-25 19:28:54 +08:00
wkc
17a6c389d1 补充结果总览详情弹窗优化验证记录 2026-03-25 19:13:42 +08:00
wkc
1e3ea8d4c9 统一结果总览详情弹窗主区视觉 2026-03-25 19:12:30 +08:00
wkc
3fb02f1391 改造结果总览详情弹窗侧栏档案面板 2026-03-25 19:10:49 +08:00
wkc
04381dc434 重构结果总览详情弹窗外层骨架 2026-03-25 19:09:16 +08:00
wkc
2866767503 补充结果总览详情弹窗展示优化实施计划 2026-03-25 19:04:43 +08:00
wkc
d1bfeb8e63 补充结果总览详情窗口展示优化设计文档 2026-03-25 19:00:49 +08:00
wkc
255a41c936 修复结果总览标签展示 2026-03-25 18:47:27 +08:00
wkc
ed427f7a42 合并结果总览详情外层卡片 2026-03-25 18:44:57 +08:00
wkc
7fb1543c4c 取消结果总览详情左侧固定 2026-03-25 18:32:53 +08:00
wkc
0746a44b32 调整结果总览详情侧栏固定布局 2026-03-25 17:26:50 +08:00
wkc
d174dc739f 调整结果总览详情弹窗占比 2026-03-25 17:21:34 +08:00
wkc
54cd982603 调整异常对象逐卡展示口径 2026-03-25 17:16:15 +08:00
wkc
e957cdcc81 调整异常对象卡片单列展示 2026-03-25 17:03:14 +08:00
wkc
9442a4116c 补充异常对象原因快照展示 2026-03-25 16:58:55 +08:00
wkc
be3448eb44 调整结果总览详情弹窗分页与模型摘要 2026-03-25 16:02:46 +08:00
wkc
8e0274df88 补充专项核查展开区改版实施计划 2026-03-25 15:34:13 +08:00
wkc
5867cd5057 补充专项核查展开区改版设计文档 2026-03-25 15:29:44 +08:00
wkc
78ae93330c 实现结果总览详情弹窗前端接线 2026-03-25 15:26:03 +08:00
wkc
a52fb35bd3 实现结果总览详情弹窗后端接口 2026-03-25 15:15:07 +08:00
wkc
717f836190 搭建结果总览详情服务骨架 2026-03-25 15:07:43 +08:00
wkc
8df9dbacd8 补充结果总览详情接口契约 2026-03-25 15:03:24 +08:00
wkc
155da36e78 补充结果总览详情弹窗实施计划 2026-03-25 14:57:07 +08:00
wkc
13769da668 补充结果总览详情弹窗真实数据设计 2026-03-25 14:51:32 +08:00
wkc
e521169a7c 调整上传数据页列表工具栏布局 2026-03-25 14:26:58 +08:00
wkc
ad4e115787 补充结果总览项目分析弹窗前端记录 2026-03-25 14:17:35 +08:00
wkc
ed54b01d26 调整上传数据页轻改版前端实现 2026-03-25 14:11:54 +08:00
wkc
85f4e7bc61 实现结果总览项目分析弹窗主视图 2026-03-25 14:09:47 +08:00
wkc
a13c73f9a8 搭建结果总览项目分析弹窗骨架 2026-03-25 14:05:30 +08:00
wkc
137d6630fe 新增上传数据页轻改版实施计划 2026-03-25 14:03:44 +08:00
wkc
b14eef8482 打通结果总览项目分析弹窗入口 2026-03-25 14:02:45 +08:00
wkc
2793cf437c 新增结果总览项目分析弹窗实施计划 2026-03-25 13:59:24 +08:00
wkc
ad8099889c 新增上传数据页轻改版设计文档 2026-03-25 13:58:13 +08:00
wkc
05ac43f26b 新增结果总览项目分析弹窗设计文档 2026-03-25 13:56:21 +08:00
wkc
071c02192d 修复all模式月固定收入规则命中隔离问题 2026-03-25 10:28:08 +08:00
wkc
5eea3c66ff 实现lsfx-mock全命中SQL对齐 2026-03-25 10:05:30 +08:00
wkc
f217d59f09 新增lsfx-mock全命中实施计划 2026-03-25 09:56:59 +08:00
wkc
3e8e44ae30 新增lsfx-mock全命中SQL对齐设计文档 2026-03-25 09:41:57 +08:00
wkc
98430b4c8d 修正专项核查拓展查询项目参数绑定 2026-03-24 23:38:06 +08:00
wkc
1770d304e5 补充专项核查拓展查询前端验证记录 2026-03-24 23:16:16 +08:00
wkc
d745481eeb 补充专项核查拓展查询详情弹窗 2026-03-24 23:15:35 +08:00
wkc
0fc61aa3cb 修正专项核查调动详情主键链路 2026-03-24 23:15:28 +08:00
wkc
04c9cfc42e 实现专项核查拓展查询主题切换 2026-03-24 23:07:28 +08:00
wkc
5ba70789d4 挂载专项核查拓展查询卡片 2026-03-24 23:04:54 +08:00
wkc
0b80c18838 补充专项核查拓展查询前端接口 2026-03-24 23:03:07 +08:00
wkc
3dc639778e 补充专项核查拓展查询后端验证记录 2026-03-24 23:01:58 +08:00
wkc
8a6b844509 完成专项核查拓展查询服务组装 2026-03-24 23:01:21 +08:00
wkc
0dbf5c5ca4 补充专项核查招聘调动拓展查询SQL 2026-03-24 22:58:26 +08:00
wkc
c1a588b3fd 补充专项核查采购拓展查询SQL 2026-03-24 22:55:59 +08:00
wkc
1d013dc6df 定义专项核查拓展查询接口契约 2026-03-24 22:54:36 +08:00
wkc
dd93798cb9 新增专项核查拓展查询前后端实施计划 2026-03-24 22:41:06 +08:00
wkc
eb4988f80e 修正专项核查拓展查询设计文档口径 2026-03-24 22:16:21 +08:00
wkc
5f8c5a9ec5 新增专项核查拓展查询设计文档 2026-03-24 22:14:26 +08:00
wkc
805bef4099 修复归档项目详情页签地址回写 2026-03-24 22:00:42 +08:00
wkc
294164a504 实现项目归档功能 2026-03-24 21:45:55 +08:00
wkc
bb49d78a3a 新增项目归档前后端实施计划 2026-03-24 21:35:59 +08:00
wkc
adb6b00ed0 调整专项核查详情展示并补充家庭资产负债测试数据 2026-03-24 21:34:38 +08:00
wkc
eaea112831 新增项目归档功能设计文档 2026-03-24 21:29:15 +08:00
wkc
c1b4514806 完成专项核查家庭资产负债实现 2026-03-24 20:42:56 +08:00
wkc
979b905682 新增专项核查家庭资产负债实施计划 2026-03-24 17:15:20 +08:00
wkc
bb8bd7df43 新增专项核查页家庭资产负债专项核查设计文档 2026-03-24 16:57:14 +08:00
wkc
9dfa722c8d 新增项目详情专项排查设计与实施计划 2026-03-24 15:46:25 +08:00
wkc
fa9673c8c4 调整征信维护为征信对象直接入库 2026-03-24 15:05:02 +08:00
wkc
6b0c83024e 新增征信维护直接入库实施计划 2026-03-24 14:49:45 +08:00
wkc
a95258fe81 新增征信维护直接入库设计文档 2026-03-24 14:42:38 +08:00
465 changed files with 44887 additions and 1609 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -27,7 +27,7 @@
- 前端开发直接在当前分支进行,不需要额外创建 git worktree
- 测试结束后,自动关闭测试过程中启动的前后端进程
- 遇到 MCP 数据库操作时,使用项目配置文件中的数据库连接信息
- 执行包含中文内容的 MySQL SQL 脚本时,禁止直接手写 `mysql -e` 或普通重定向执行;必须优先使用 `bin/mysql_utf8_exec.sh <sql-file>`,确保会话字符集为 `utf8mb4`,避免写入乱码
- 执行包含中文内容的 MySQL SQL 脚本或数据库导入时,禁止直接手写 `mysql -e` 或普通重定向执行;必须优先使用 `bin/mysql_utf8_exec.sh <sql-file>`,确保会话字符集为 `utf8mb4`,避免导入或写入乱码
- 银行流水打标相关规则与参数编码需要统一使用全大写;新增或修改 `rule_code``indicator_code``param_code` 时,禁止混用大小写风格
---
@@ -164,7 +164,7 @@ return AjaxResult.success(result);
- 非业务字段如 `create_by``create_time` 由后端自动维护
- 前端表单不要暴露通用审计字段
- 新增菜单、字典、初始化数据时,同步补充 SQL 脚本
- 执行数据库脚本前,需确认客户端会话字符集为 `utf8mb4`;涉及中文插入、更新时默认使用 `bin/mysql_utf8_exec.sh`
- 执行数据库脚本或导入数据库前,需确认客户端会话字符集为 `utf8mb4`;涉及中文插入、更新、导入时默认使用 `bin/mysql_utf8_exec.sh`
### 前端规范

BIN
assets/专项核查.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

@@ -0,0 +1,13 @@
# 在专项核查页面开发员工家庭资产负债专项核查功能
- 范围为项目内的员工
- 家庭总年收入与家庭总负债之和,对比家庭总资产之和
- 家庭总年收入为员工本人年收入 + 员工配偶年收入(如有)
- 家庭总资产之和为员工与配偶关联的资产总和
- 家庭总负债为来源于员工和配偶的征信中的贷款与负债总和
- 总年收入+ 总负债 < 总资产 * 1.5 时,为正常
- 总资产 * 3 > 总年收入+ 总负债 > 总资产 * 1.5 时,提示存在风险
- 总年收入+ 总负债 > 总资产 * 3 时,提示高风险
- 在专项核查页面添加一个卡片,标题为员工家庭资产负债专项核查
- 卡片内展示项目内员工核查列表,展示每个员工家庭的总收入 总资产,总负债和风险情况。点开详情展示所有数据细项。
- 展示风格与结果总览其他组件的展示效果统一
- 使用frontend-design设计前端展示效果

BIN
assets/员工账户.xlsx Normal file

Binary file not shown.

View File

@@ -11,8 +11,8 @@ TARGET_DIR="$ROOT_DIR/ruoyi-admin/target"
JAR_NAME="ruoyi-admin.jar"
SERVER_PORT=62318
STOP_WAIT_SECONDS=30
APP_KEYWORD="$JAR_NAME"
JAVA_OPTS="-Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
APP_MARKER="-Dccdi.backend.root=$ROOT_DIR"
JAVA_OPTS="$APP_MARKER -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
timestamp() {
date "+%Y-%m-%d %H:%M:%S"
@@ -42,24 +42,54 @@ ensure_command() {
fi
}
is_managed_backend_pid() {
pid="$1"
if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then
return 1
fi
args=$(ps -o args= -p "$pid" 2>/dev/null || true)
if [ -z "${args:-}" ]; then
return 1
fi
case "$args" in
*"$APP_MARKER"*"$JAR_NAME"*|*"$JAR_NAME"*"$APP_MARKER"*)
return 0
;;
esac
if [ -f "$PID_FILE" ]; then
file_pid=$(cat "$PID_FILE" 2>/dev/null || true)
if [ "${file_pid:-}" = "$pid" ]; then
case "$args" in
*"java"*"-jar"*"$JAR_NAME"*)
return 0
;;
esac
fi
fi
return 1
}
collect_pids() {
all_pids=""
if [ -f "$PID_FILE" ]; then
file_pid=$(cat "$PID_FILE" 2>/dev/null || true)
if [ -n "${file_pid:-}" ] && kill -0 "$file_pid" 2>/dev/null; then
if [ -n "${file_pid:-}" ] && is_managed_backend_pid "$file_pid"; then
all_pids="$all_pids $file_pid"
fi
fi
port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true)
if [ -n "${port_pids:-}" ]; then
all_pids="$all_pids $port_pids"
fi
app_pids=$(pgrep -f "$APP_KEYWORD" 2>/dev/null || true)
if [ -n "${app_pids:-}" ]; then
all_pids="$all_pids $app_pids"
marker_pids=$(pgrep -f "$APP_MARKER" 2>/dev/null || true)
if [ -n "${marker_pids:-}" ]; then
for pid in $marker_pids; do
if is_managed_backend_pid "$pid"; then
all_pids="$all_pids $pid"
fi
done
fi
unique_pids=""
@@ -155,6 +185,12 @@ status_backend() {
pids=$(collect_pids)
if [ -n "${pids:-}" ]; then
log_info "后端正在运行,进程: $pids"
return 0
fi
port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true)
if [ -n "${port_pids:-}" ]; then
log_info "未发现脚本托管的后端进程,但端口 $SERVER_PORT 被其他进程占用: $port_pids"
else
log_info "后端未运行"
fi
@@ -190,6 +226,7 @@ main() {
ensure_command mvn
ensure_command lsof
ensure_command pgrep
ensure_command ps
ensure_command tail
action="${1:-restart}"

View File

@@ -0,0 +1,181 @@
package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiAccountInfoExcel;
import com.ruoyi.info.collection.domain.vo.AccountInfoImportFailureVO;
import com.ruoyi.info.collection.domain.vo.CcdiAccountInfoVO;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.service.ICcdiAccountInfoService;
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 账户库Controller
*
* @author ruoyi
* @date 2026-04-13
*/
@Tag(name = "账户库管理")
@RestController
@RequestMapping("/ccdi/accountInfo")
public class CcdiAccountInfoController extends BaseController {
@Resource
private ICcdiAccountInfoService accountInfoService;
@Resource
private ICcdiBaseStaffService baseStaffService;
/**
* 查询账户库列表
*/
@Operation(summary = "查询账户库列表")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:list')")
@GetMapping("/list")
public TableDataInfo list(CcdiAccountInfoQueryDTO queryDTO) {
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiAccountInfoVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiAccountInfoVO> result = accountInfoService.selectAccountInfoPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
/**
* 查询账户库详情
*/
@Operation(summary = "查询账户库详情")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
return success(accountInfoService.selectAccountInfoById(id));
}
/**
* 导出账户库列表
*/
@Operation(summary = "导出账户库列表")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:export')")
@Log(title = "账户库管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiAccountInfoQueryDTO queryDTO) {
List<CcdiAccountInfoExcel> list = accountInfoService.selectAccountInfoListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiAccountInfoExcel.class, "账户库管理");
}
/**
* 新增账户
*/
@Operation(summary = "新增账户")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:add')")
@Log(title = "账户库管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiAccountInfoAddDTO addDTO) {
return toAjax(accountInfoService.insertAccountInfo(addDTO));
}
/**
* 修改账户
*/
@Operation(summary = "修改账户")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:edit')")
@Log(title = "账户库管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiAccountInfoEditDTO editDTO) {
return toAjax(accountInfoService.updateAccountInfo(editDTO));
}
/**
* 删除账户
*/
@Operation(summary = "删除账户")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:remove')")
@Log(title = "账户库管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(accountInfoService.deleteAccountInfoByIds(ids));
}
/**
* 查询账户归属员工下拉
*/
@Operation(summary = "查询账户归属员工下拉")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:list')")
@GetMapping("/staffOptions")
public AjaxResult getStaffOptions(@RequestParam(required = false) String query) {
List<CcdiBaseStaffOptionVO> list = baseStaffService.selectStaffOptions(query);
return success(list);
}
/**
* 查询关系人下拉
*/
@Operation(summary = "查询关系人下拉")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:list')")
@GetMapping("/relationOptions")
public AjaxResult getRelationOptions(@RequestParam Long staffId) {
return success(accountInfoService.selectRelationOptionsByStaffId(staffId));
}
/**
* 下载导入模板
*/
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateExcel(response, CcdiAccountInfoExcel.class, "账户库管理");
}
/**
* 导入账户库信息
*/
@Operation(summary = "导入账户库信息")
@PreAuthorize("@ss.hasPermi('ccdi:accountInfo:import')")
@Log(title = "账户库管理", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
List<CcdiAccountInfoExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiAccountInfoExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
ImportResult result = accountInfoService.importAccountInfo(list, updateSupport);
List<AccountInfoImportFailureVO> failures = accountInfoService.getLatestImportFailures();
Map<String, Object> data = new HashMap<>(4);
data.put("totalCount", result.getTotalCount());
data.put("successCount", result.getSuccessCount());
data.put("failureCount", result.getFailureCount());
data.put("failures", failures);
String message = "导入完成,共 " + result.getTotalCount() + " 条,成功 " + result.getSuccessCount()
+ " 条,失败 " + result.getFailureCount() + "";
return AjaxResult.success(message, data);
}
}

View File

@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
@@ -128,6 +129,15 @@ public class CcdiStaffRecruitmentController extends BaseController {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffRecruitmentExcel.class, "员工招聘信息");
}
/**
* 下载历史工作经历导入模板
*/
@Operation(summary = "下载历史工作经历导入模板")
@PostMapping("/workImportTemplate")
public void workImportTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffRecruitmentWorkExcel.class, "历史工作经历");
}
/**
* 异步导入招聘信息
*/
@@ -155,6 +165,31 @@ public class CcdiStaffRecruitmentController extends BaseController {
return AjaxResult.success("导入任务已提交,正在后台处理", result);
}
/**
* 异步导入历史工作经历
*/
@Operation(summary = "异步导入历史工作经历")
@Parameter(name = "file", description = "导入文件", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:import')")
@Log(title = "员工招聘历史工作经历", businessType = BusinessType.IMPORT)
@PostMapping("/importWorkData")
public AjaxResult importWorkData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiStaffRecruitmentWorkExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffRecruitmentWorkExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
String taskId = recruitmentService.importRecruitmentWork(list);
ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId);
result.setStatus("PROCESSING");
result.setMessage("历史工作经历导入任务已提交,正在后台处理");
return AjaxResult.success("历史工作经历导入任务已提交,正在后台处理", result);
}
/**
* 查询导入状态
*/

View File

@@ -0,0 +1,83 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
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;
/**
* 账户基础信息对象 ccdi_account_info
*
* @author ruoyi
* @date 2026-04-13
*/
@Data
@TableName("ccdi_account_info")
public class CcdiAccountInfo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(value = "account_id", type = IdType.AUTO)
private Long id;
/** 所属人类型EMPLOYEE/RELATION/INTERMEDIARY/EXTERNAL */
private String ownerType;
/** 所属人标识 */
private String ownerId;
/** 账户号码 */
private String accountNo;
/** 账户类型 */
private String accountType;
/** 账户范围INTERNAL/EXTERNAL */
private String bankScope;
/** 账户姓名 */
private String accountName;
/** 开户机构 */
@TableField("bank")
private String openBank;
/** 银行代码 */
private String bankCode;
/** 币种 */
private String currency;
/** 状态1-正常 2-已销户 */
private Integer status;
/** 生效日期 */
private Date effectiveDate;
/** 失效日期 */
private Date invalidDate;
/** 创建者 */
@TableField(fill = FieldFill.INSERT)
private String createBy;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新者 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

View File

@@ -0,0 +1,86 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
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.math.BigDecimal;
import java.util.Date;
/**
* 账户分析结果对象 ccdi_account_result
*
* @author ruoyi
* @date 2026-04-13
*/
@Data
@TableName("ccdi_account_result")
public class CcdiAccountResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(value = "result_id", type = IdType.AUTO)
private Long resultId;
/** 账户号码 */
private String accountNo;
/** 是否实控账户0-否 1-是 */
@TableField("is_self_account")
private Integer isActualControl;
/** 月均交易笔数 */
@TableField("monthly_avg_trans_count")
private Integer avgMonthTxnCount;
/** 月均交易金额 */
@TableField("monthly_avg_trans_amount")
private BigDecimal avgMonthTxnAmount;
/** 交易频率等级 */
@TableField("trans_freq_type")
private String txnFrequencyLevel;
/** 借方单笔最高额 */
@TableField("dr_max_single_amount")
private BigDecimal debitSingleMaxAmount;
/** 贷方单笔最高额 */
@TableField("cr_max_single_amount")
private BigDecimal creditSingleMaxAmount;
/** 借方日累计最高额 */
@TableField("dr_max_daily_amount")
private BigDecimal debitDailyMaxAmount;
/** 贷方日累计最高额 */
@TableField("cr_max_daily_amount")
private BigDecimal creditDailyMaxAmount;
/** 风险等级 */
@TableField("trans_risk_level")
private String txnRiskLevel;
/** 创建者 */
@TableField(fill = FieldFill.INSERT)
private String createBy;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新者 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

View File

@@ -22,7 +22,7 @@ public class CcdiStaffRecruitment implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 招聘项目编号 */
/** 招聘记录编号 */
@TableId(type = IdType.INPUT)
private String recruitId;
@@ -41,6 +41,9 @@ public class CcdiStaffRecruitment implements Serializable {
/** 应聘人员姓名 */
private String candName;
/** 招聘类型SOCIAL-社招CAMPUS-校招 */
private String recruitType;
/** 应聘人员学历 */
private String candEdu;

View File

@@ -0,0 +1,76 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
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;
/**
* 招聘记录历史工作经历对象 ccdi_staff_recruitment_work
*
* @author ruoyi
* @date 2026-04-15
*/
@Data
@TableName("ccdi_staff_recruitment_work")
public class CcdiStaffRecruitmentWork implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键 */
@TableId(type = IdType.AUTO)
private Long id;
/** 关联招聘记录编号 */
private String recruitId;
/** 排序号 */
private Integer sortOrder;
/** 工作单位 */
private String companyName;
/** 所属部门 */
private String departmentName;
/** 岗位名称 */
private String positionName;
/** 入职年月 */
private String jobStartMonth;
/** 离职年月 */
private String jobEndMonth;
/** 离职原因 */
private String departureReason;
/** 主要工作内容 */
private String workContent;
/** 备注 */
private String remark;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

View File

@@ -0,0 +1,138 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 账户库新增DTO
*
* @author ruoyi
* @date 2026-04-13
*/
@Data
@Schema(description = "账户库新增")
public class CcdiAccountInfoAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 所属人类型 */
@NotBlank(message = "所属人类型不能为空")
@Schema(description = "所属人类型")
private String ownerType;
/** 所属人标识 */
@Schema(description = "所属人标识")
private String ownerId;
/** 账户号码 */
@NotBlank(message = "账户号码不能为空")
@Size(max = 240, message = "账户号码长度不能超过240个字符")
@Schema(description = "账户号码")
private String accountNo;
/** 账户类型 */
@NotBlank(message = "账户类型不能为空")
@Size(max = 30, message = "账户类型长度不能超过30个字符")
@Schema(description = "账户类型")
private String accountType;
/** 账户范围 */
@NotBlank(message = "账户范围不能为空")
@Size(max = 20, message = "账户范围长度不能超过20个字符")
@Schema(description = "账户范围")
private String bankScope;
/** 账户姓名 */
@NotBlank(message = "账户姓名不能为空")
@Size(max = 100, message = "账户姓名长度不能超过100个字符")
@Schema(description = "账户姓名")
private String accountName;
/** 开户机构 */
@NotBlank(message = "开户机构不能为空")
@Size(max = 100, message = "开户机构长度不能超过100个字符")
@Schema(description = "开户机构")
private String openBank;
/** 银行代码 */
@Size(max = 20, message = "银行代码长度不能超过20个字符")
@Schema(description = "银行代码")
private String bankCode;
/** 币种 */
@Size(max = 3, message = "币种长度不能超过3个字符")
@Schema(description = "币种")
private String currency;
/** 状态 */
@NotNull(message = "状态不能为空")
@Schema(description = "状态")
private Integer status;
/** 生效日期 */
@NotNull(message = "生效日期不能为空")
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 是否实控账户 */
@Schema(description = "是否实控账户")
private Integer isActualControl;
/** 月均交易笔数 */
@Min(value = 0, message = "月均交易笔数不能小于0")
@Schema(description = "月均交易笔数")
private Integer avgMonthTxnCount;
/** 月均交易金额 */
@DecimalMin(value = "0", message = "月均交易金额不能小于0")
@Schema(description = "月均交易金额")
private BigDecimal avgMonthTxnAmount;
/** 频率等级 */
@Size(max = 20, message = "频率等级长度不能超过20个字符")
@Schema(description = "频率等级")
private String txnFrequencyLevel;
/** 借方单笔最高额 */
@DecimalMin(value = "0", message = "借方单笔最高额不能小于0")
@Schema(description = "借方单笔最高额")
private BigDecimal debitSingleMaxAmount;
/** 贷方单笔最高额 */
@DecimalMin(value = "0", message = "贷方单笔最高额不能小于0")
@Schema(description = "贷方单笔最高额")
private BigDecimal creditSingleMaxAmount;
/** 借方日累计最高额 */
@DecimalMin(value = "0", message = "借方日累计最高额不能小于0")
@Schema(description = "借方日累计最高额")
private BigDecimal debitDailyMaxAmount;
/** 贷方日累计最高额 */
@DecimalMin(value = "0", message = "贷方日累计最高额不能小于0")
@Schema(description = "贷方日累计最高额")
private BigDecimal creditDailyMaxAmount;
/** 风险等级 */
@Size(max = 10, message = "风险等级长度不能超过10个字符")
@Schema(description = "风险等级")
private String txnRiskLevel;
}

View File

@@ -0,0 +1,143 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 账户库编辑DTO
*
* @author ruoyi
* @date 2026-04-13
*/
@Data
@Schema(description = "账户库编辑")
public class CcdiAccountInfoEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@NotNull(message = "主键ID不能为空")
@Schema(description = "主键ID")
private Long id;
/** 所属人类型 */
@NotBlank(message = "所属人类型不能为空")
@Schema(description = "所属人类型")
private String ownerType;
/** 所属人标识 */
@Schema(description = "所属人标识")
private String ownerId;
/** 账户号码 */
@NotBlank(message = "账户号码不能为空")
@Size(max = 240, message = "账户号码长度不能超过240个字符")
@Schema(description = "账户号码")
private String accountNo;
/** 账户类型 */
@NotBlank(message = "账户类型不能为空")
@Size(max = 30, message = "账户类型长度不能超过30个字符")
@Schema(description = "账户类型")
private String accountType;
/** 账户范围 */
@NotBlank(message = "账户范围不能为空")
@Size(max = 20, message = "账户范围长度不能超过20个字符")
@Schema(description = "账户范围")
private String bankScope;
/** 账户姓名 */
@NotBlank(message = "账户姓名不能为空")
@Size(max = 100, message = "账户姓名长度不能超过100个字符")
@Schema(description = "账户姓名")
private String accountName;
/** 开户机构 */
@NotBlank(message = "开户机构不能为空")
@Size(max = 100, message = "开户机构长度不能超过100个字符")
@Schema(description = "开户机构")
private String openBank;
/** 银行代码 */
@Size(max = 20, message = "银行代码长度不能超过20个字符")
@Schema(description = "银行代码")
private String bankCode;
/** 币种 */
@Size(max = 3, message = "币种长度不能超过3个字符")
@Schema(description = "币种")
private String currency;
/** 状态 */
@NotNull(message = "状态不能为空")
@Schema(description = "状态")
private Integer status;
/** 生效日期 */
@NotNull(message = "生效日期不能为空")
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 是否实控账户 */
@Schema(description = "是否实控账户")
private Integer isActualControl;
/** 月均交易笔数 */
@Min(value = 0, message = "月均交易笔数不能小于0")
@Schema(description = "月均交易笔数")
private Integer avgMonthTxnCount;
/** 月均交易金额 */
@DecimalMin(value = "0", message = "月均交易金额不能小于0")
@Schema(description = "月均交易金额")
private BigDecimal avgMonthTxnAmount;
/** 频率等级 */
@Size(max = 20, message = "频率等级长度不能超过20个字符")
@Schema(description = "频率等级")
private String txnFrequencyLevel;
/** 借方单笔最高额 */
@DecimalMin(value = "0", message = "借方单笔最高额不能小于0")
@Schema(description = "借方单笔最高额")
private BigDecimal debitSingleMaxAmount;
/** 贷方单笔最高额 */
@DecimalMin(value = "0", message = "贷方单笔最高额不能小于0")
@Schema(description = "贷方单笔最高额")
private BigDecimal creditSingleMaxAmount;
/** 借方日累计最高额 */
@DecimalMin(value = "0", message = "借方日累计最高额不能小于0")
@Schema(description = "借方日累计最高额")
private BigDecimal debitDailyMaxAmount;
/** 贷方日累计最高额 */
@DecimalMin(value = "0", message = "贷方日累计最高额不能小于0")
@Schema(description = "贷方日累计最高额")
private BigDecimal creditDailyMaxAmount;
/** 风险等级 */
@Size(max = 10, message = "风险等级长度不能超过10个字符")
@Schema(description = "风险等级")
private String txnRiskLevel;
}

View File

@@ -0,0 +1,57 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 账户库查询DTO
*
* @author ruoyi
* @date 2026-04-13
*/
@Data
@Schema(description = "账户库查询条件")
public class CcdiAccountInfoQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工姓名 */
@Schema(description = "员工姓名")
private String staffName;
/** 所属人类型 */
@Schema(description = "所属人类型")
private String ownerType;
/** 账户范围 */
@Schema(description = "账户范围")
private String bankScope;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 账户姓名 */
@Schema(description = "账户姓名")
private String accountName;
/** 账户类型 */
@Schema(description = "账户类型")
private String accountType;
/** 是否实控账户 */
@Schema(description = "是否实控账户")
private Integer isActualControl;
/** 风险等级 */
@Schema(description = "风险等级")
private String riskLevel;
/** 状态 */
@Schema(description = "状态")
private Integer status;
}

View File

@@ -15,7 +15,5 @@ public class CcdiCreditInfoQueryDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String staffId;
private String idCard;
private String maintained;
}

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto;
import com.ruoyi.info.collection.annotation.EnumValid;
import com.ruoyi.info.collection.enums.AdmitStatus;
import com.ruoyi.info.collection.enums.RecruitType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
@@ -22,9 +23,9 @@ public class CcdiStaffRecruitmentAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 招聘项目编号 */
@NotBlank(message = "招聘项目编号不能为空")
@Size(max = 32, message = "招聘项目编号长度不能超过32个字符")
/** 招聘记录编号 */
@NotBlank(message = "招聘记录编号不能为空")
@Size(max = 32, message = "招聘记录编号长度不能超过32个字符")
private String recruitId;
/** 招聘项目名称 */
@@ -51,6 +52,11 @@ public class CcdiStaffRecruitmentAddDTO implements Serializable {
@Size(max = 20, message = "应聘人员姓名长度不能超过20个字符")
private String candName;
/** 招聘类型 */
@NotBlank(message = "招聘类型不能为空")
@EnumValid(enumClass = RecruitType.class, message = "招聘类型状态值不合法")
private String recruitType;
/** 应聘人员学历 */
@NotBlank(message = "应聘人员学历不能为空")
@Size(max = 20, message = "应聘人员学历长度不能超过20个字符")

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto;
import com.ruoyi.info.collection.annotation.EnumValid;
import com.ruoyi.info.collection.enums.AdmitStatus;
import com.ruoyi.info.collection.enums.RecruitType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
@@ -23,8 +24,8 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 招聘项目编号 */
@NotNull(message = "招聘项目编号不能为空")
/** 招聘记录编号 */
@NotNull(message = "招聘记录编号不能为空")
private String recruitId;
/** 招聘项目名称 */
@@ -46,6 +47,10 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable {
@Size(max = 20, message = "应聘人员姓名长度不能超过20个字符")
private String candName;
/** 招聘类型 */
@EnumValid(enumClass = RecruitType.class, message = "招聘类型状态值不合法")
private String recruitType;
/** 应聘人员学历 */
@Size(max = 20, message = "应聘人员学历长度不能超过20个字符")
private String candEdu;

View File

@@ -26,6 +26,9 @@ public class CcdiStaffRecruitmentQueryDTO implements Serializable {
/** 候选人姓名(模糊查询) */
private String candName;
/** 招聘类型(精确查询) */
private String recruitType;
/** 证件号码(精确查询) */
private String candId;

View File

@@ -0,0 +1,105 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 账户库导入导出对象
*
* @author ruoyi
* @date 2026-04-14
*/
@Data
public class CcdiAccountInfoExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "所属人类型*", index = 0)
@ColumnWidth(16)
private String ownerType;
@ExcelProperty(value = "证件号*", index = 1)
@ColumnWidth(24)
private String ownerId;
@ExcelProperty(value = "账户姓名*", index = 2)
@ColumnWidth(18)
private String accountName;
@ExcelProperty(value = "账户号码*", index = 3)
@ColumnWidth(28)
private String accountNo;
@ExcelProperty(value = "账户类型*", index = 4)
@ColumnWidth(16)
private String accountType;
@ExcelProperty(value = "账户范围*", index = 5)
@ColumnWidth(14)
private String bankScope;
@ExcelProperty(value = "开户机构*", index = 6)
@ColumnWidth(28)
private String openBank;
@ExcelProperty(value = "银行代码", index = 7)
@ColumnWidth(16)
private String bankCode;
@ExcelProperty(value = "币种", index = 8)
@ColumnWidth(10)
private String currency;
@ExcelProperty(value = "状态*", index = 9)
@ColumnWidth(12)
private String status;
@ExcelProperty(value = "生效日期*(yyyy-MM-dd)", index = 10)
@ColumnWidth(18)
private String effectiveDate;
@ExcelProperty(value = "失效日期(yyyy-MM-dd)", index = 11)
@ColumnWidth(18)
private String invalidDate;
@ExcelProperty(value = "是否实控账户", index = 12)
@ColumnWidth(14)
private String isActualControl;
@ExcelProperty(value = "月均交易笔数", index = 13)
@ColumnWidth(14)
private String avgMonthTxnCount;
@ExcelProperty(value = "月均交易金额", index = 14)
@ColumnWidth(16)
private String avgMonthTxnAmount;
@ExcelProperty(value = "频率等级", index = 15)
@ColumnWidth(12)
private String txnFrequencyLevel;
@ExcelProperty(value = "借方单笔最高额", index = 16)
@ColumnWidth(16)
private String debitSingleMaxAmount;
@ExcelProperty(value = "贷方单笔最高额", index = 17)
@ColumnWidth(16)
private String creditSingleMaxAmount;
@ExcelProperty(value = "借方日累计最高额", index = 18)
@ColumnWidth(16)
private String debitDailyMaxAmount;
@ExcelProperty(value = "贷方日累计最高额", index = 19)
@ColumnWidth(16)
private String creditDailyMaxAmount;
@ExcelProperty(value = "风险等级", index = 20)
@ColumnWidth(12)
private String txnRiskLevel;
}

View File

@@ -0,0 +1,95 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.Required;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 招聘记录历史工作经历Excel导入对象
*
* @author ruoyi
* @date 2026-04-20
*/
@Data
public class CcdiStaffRecruitmentWorkExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 招聘记录编号 */
@ExcelProperty(value = "招聘记录编号", index = 0)
@ColumnWidth(20)
@Required
private String recruitId;
/** 候选人姓名 */
@ExcelProperty(value = "候选人姓名", index = 1)
@ColumnWidth(15)
@Required
private String candName;
/** 招聘项目名称 */
@ExcelProperty(value = "招聘项目名称", index = 2)
@ColumnWidth(25)
@Required
private String recruitName;
/** 职位名称 */
@ExcelProperty(value = "职位名称", index = 3)
@ColumnWidth(20)
@Required
private String posName;
/** 排序号 */
@ExcelProperty(value = "排序号", index = 4)
@ColumnWidth(10)
@Required
private Integer sortOrder;
/** 工作单位 */
@ExcelProperty(value = "工作单位", index = 5)
@ColumnWidth(25)
@Required
private String companyName;
/** 所属部门 */
@ExcelProperty(value = "所属部门", index = 6)
@ColumnWidth(18)
private String departmentName;
/** 岗位 */
@ExcelProperty(value = "岗位", index = 7)
@ColumnWidth(20)
@Required
private String positionName;
/** 入职年月 */
@ExcelProperty(value = "入职年月", index = 8)
@ColumnWidth(12)
@Required
private String jobStartMonth;
/** 离职年月 */
@ExcelProperty(value = "离职年月", index = 9)
@ColumnWidth(12)
private String jobEndMonth;
/** 离职原因 */
@ExcelProperty(value = "离职原因", index = 10)
@ColumnWidth(30)
private String departureReason;
/** 工作内容 */
@ExcelProperty(value = "工作内容", index = 11)
@ColumnWidth(35)
private String workContent;
/** 备注 */
@ExcelProperty(value = "备注", index = 12)
@ColumnWidth(25)
private String remark;
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 账户库导入失败记录
*
* @author ruoyi
* @date 2026-04-14
*/
@Data
@Schema(description = "账户库导入失败记录")
public class AccountInfoImportFailureVO {
@Schema(description = "行号")
private Integer rowNum;
@Schema(description = "所属人类型")
private String ownerType;
@Schema(description = "证件号")
private String ownerId;
@Schema(description = "账户号码")
private String accountNo;
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -0,0 +1,156 @@
package com.ruoyi.info.collection.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 账户库VO
*
* @author ruoyi
* @date 2026-04-13
*/
@Data
@Schema(description = "账户库信息")
public class CcdiAccountInfoVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@Schema(description = "主键ID")
private Long id;
/** 所属人类型 */
@Schema(description = "所属人类型")
private String ownerType;
/** 所属人标识 */
@Schema(description = "所属人标识")
private String ownerId;
/** 员工工号 */
@Schema(description = "员工工号")
private Long staffId;
/** 员工姓名 */
@Schema(description = "员工姓名")
private String staffName;
/** 关系人ID */
@Schema(description = "关系人ID")
private Long relationId;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@Schema(description = "关系人姓名")
private String relationName;
/** 关系人证件号 */
@Schema(description = "关系人证件号")
private String relationCertNo;
/** 账户号码 */
@Schema(description = "账户号码")
private String accountNo;
/** 账户类型 */
@Schema(description = "账户类型")
private String accountType;
/** 账户范围 */
@Schema(description = "账户范围")
private String bankScope;
/** 账户姓名 */
@Schema(description = "账户姓名")
private String accountName;
/** 开户机构 */
@Schema(description = "开户机构")
private String openBank;
/** 银行代码 */
@Schema(description = "银行代码")
private String bankCode;
/** 币种 */
@Schema(description = "币种")
private String currency;
/** 状态 */
@Schema(description = "状态")
private Integer status;
/** 生效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 是否实控账户 */
@Schema(description = "是否实控账户")
private Integer isActualControl;
/** 月均交易笔数 */
@Schema(description = "月均交易笔数")
private Integer avgMonthTxnCount;
/** 月均交易金额 */
@Schema(description = "月均交易金额")
private BigDecimal avgMonthTxnAmount;
/** 频率等级 */
@Schema(description = "频率等级")
private String txnFrequencyLevel;
/** 借方单笔最高额 */
@Schema(description = "借方单笔最高额")
private BigDecimal debitSingleMaxAmount;
/** 贷方单笔最高额 */
@Schema(description = "贷方单笔最高额")
private BigDecimal creditSingleMaxAmount;
/** 借方日累计最高额 */
@Schema(description = "借方日累计最高额")
private BigDecimal debitDailyMaxAmount;
/** 贷方日累计最高额 */
@Schema(description = "贷方日累计最高额")
private BigDecimal creditDailyMaxAmount;
/** 风险等级 */
@Schema(description = "风险等级")
private String txnRiskLevel;
/** 创建者 */
@Schema(description = "创建者")
private String createBy;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private Date createTime;
/** 更新者 */
@Schema(description = "更新者")
private String updateBy;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
private Date updateTime;
}

View File

@@ -0,0 +1,37 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 账户库关系人下拉VO
*
* @author ruoyi
* @date 2026-04-13
*/
@Data
@Schema(description = "账户库关系人下拉选项")
public class CcdiAccountRelationOptionVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 关系人ID */
@Schema(description = "关系人ID")
private Long id;
/** 关系人姓名 */
@Schema(description = "关系人姓名")
private String relationName;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 关系人证件号 */
@Schema(description = "关系人证件号")
private String relationCertNo;
}

View File

@@ -26,6 +26,11 @@ public class CcdiBaseStaffOptionVO {
*/
private Long deptId;
/**
* 身份证号
*/
private String idCard;
/**
* 部门名称
*/

View File

@@ -5,6 +5,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 员工招聘信息VO
@@ -18,7 +19,7 @@ public class CcdiStaffRecruitmentVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 招聘项目编号 */
/** 招聘记录编号 */
private String recruitId;
/** 招聘项目名称 */
@@ -36,6 +37,9 @@ public class CcdiStaffRecruitmentVO implements Serializable {
/** 应聘人员姓名 */
private String candName;
/** 招聘类型 */
private String recruitType;
/** 应聘人员学历 */
private String candEdu;
@@ -57,6 +61,12 @@ public class CcdiStaffRecruitmentVO implements Serializable {
/** 录用情况描述 */
private String admitStatusDesc;
/** 历史工作经历条数 */
private Long workExperienceCount;
/** 历史工作经历列表 */
private List<CcdiStaffRecruitmentWorkVO> workExperienceList;
/** 面试官1姓名 */
private String interviewerName1;

View File

@@ -0,0 +1,46 @@
package com.ruoyi.info.collection.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 招聘记录历史工作经历VO
*
* @author ruoyi
* @date 2026-04-15
*/
@Data
public class CcdiStaffRecruitmentWorkVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 排序号 */
private Integer sortOrder;
/** 工作单位 */
private String companyName;
/** 所属部门 */
private String departmentName;
/** 岗位名称 */
private String positionName;
/** 入职年月 */
private String jobStartMonth;
/** 离职年月 */
private String jobEndMonth;
/** 离职原因 */
private String departureReason;
/** 主要工作内容 */
private String workContent;
/** 备注 */
private String remark;
}

View File

@@ -16,10 +16,8 @@ public class CreditInfoListVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long staffId;
private String name;
private String idCard;
private String deptName;
private Date queryDate;
private Long debtCount;
private BigDecimal debtTotalAmount;

View File

@@ -19,6 +19,9 @@ public class RecruitmentImportFailureVO {
@Schema(description = "招聘项目名称")
private String recruitName;
@Schema(description = "职位名称")
private String posName;
@Schema(description = "应聘人员姓名")
private String candName;
@@ -28,6 +31,12 @@ public class RecruitmentImportFailureVO {
@Schema(description = "录用情况")
private String admitStatus;
@Schema(description = "工作单位")
private String companyName;
@Schema(description = "岗位")
private String positionName;
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -0,0 +1,49 @@
package com.ruoyi.info.collection.enums;
import com.ruoyi.common.utils.StringUtils;
/**
* 招聘类型枚举
*
* @author ruoyi
*/
public enum RecruitType {
/** 社招 */
SOCIAL("SOCIAL", "社招"),
/** 校招 */
CAMPUS("CAMPUS", "校招");
private final String code;
private final String desc;
RecruitType(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
public static String getDescByCode(String code) {
for (RecruitType type : values()) {
if (type.code.equals(code)) {
return type.desc;
}
}
return null;
}
public static String inferCode(String recruitName) {
if (StringUtils.isNotEmpty(recruitName) && recruitName.contains("校园")) {
return CAMPUS.code;
}
return SOCIAL.code;
}
}

View File

@@ -11,26 +11,26 @@ public enum RelationType {
/** 配偶 */
SPOUSE("配偶", "配偶"),
/** 父 */
FATHER_SON("", ""),
/** 父 */
FATHER("", ""),
/** 母 */
MOTHER_DAUGHTER("", ""),
/** 母 */
MOTHER("", ""),
/** 兄弟 */
BROTHER("兄弟", "兄弟"),
/** 子女 */
CHILDREN("子女", "子女"),
/** 姐妹 */
SISTER("姐妹", "姐妹"),
/** 亲属 */
RELATIVE("亲属", "亲属"),
/** 兄弟姐妹 */
SIBLINGS("兄弟姐妹", "兄弟姐妹"),
/** 朋友 */
FRIEND("朋友", "朋友"),
/** 同事 */
COLLEAGUE("同事", "同事");
COLLEAGUE("同事", "同事"),
/** 其他 */
OTHER("其他", "其他");
private final String code;
private final String desc;

View File

@@ -0,0 +1,54 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiAccountInfo;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoQueryDTO;
import com.ruoyi.info.collection.domain.vo.CcdiAccountInfoVO;
import com.ruoyi.info.collection.domain.vo.CcdiAccountRelationOptionVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 账户库数据层
*
* @author ruoyi
* @date 2026-04-13
*/
public interface CcdiAccountInfoMapper extends BaseMapper<CcdiAccountInfo> {
/**
* 分页查询账户库
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 账户库分页结果
*/
Page<CcdiAccountInfoVO> selectAccountInfoPage(@Param("page") Page<CcdiAccountInfoVO> page,
@Param("query") CcdiAccountInfoQueryDTO queryDTO);
/**
* 查询账户库详情
*
* @param id 主键ID
* @return 账户库详情
*/
CcdiAccountInfoVO selectAccountInfoById(@Param("id") Long id);
/**
* 导出账户库列表
*
* @param queryDTO 查询条件
* @return 导出列表
*/
List<CcdiAccountInfoVO> selectAccountInfoListForExport(@Param("query") CcdiAccountInfoQueryDTO queryDTO);
/**
* 查询关系人下拉选项
*
* @param staffId 员工工号
* @return 关系人下拉
*/
List<CcdiAccountRelationOptionVO> selectRelationOptionsByStaffId(@Param("staffId") Long staffId);
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.info.collection.domain.CcdiAccountResult;
/**
* 账户分析结果数据层
*
* @author ruoyi
* @date 2026-04-13
*/
public interface CcdiAccountResultMapper extends BaseMapper<CcdiAccountResult> {
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork;
/**
* 招聘记录历史工作经历 数据层
*
* @author ruoyi
* @date 2026-04-15
*/
public interface CcdiStaffRecruitmentWorkMapper extends BaseMapper<CcdiStaffRecruitmentWork> {
}

View File

@@ -0,0 +1,95 @@
package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiAccountInfoExcel;
import com.ruoyi.info.collection.domain.vo.AccountInfoImportFailureVO;
import com.ruoyi.info.collection.domain.vo.CcdiAccountInfoVO;
import com.ruoyi.info.collection.domain.vo.CcdiAccountRelationOptionVO;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import java.util.List;
/**
* 账户库服务层
*
* @author ruoyi
* @date 2026-04-13
*/
public interface ICcdiAccountInfoService {
/**
* 分页查询账户库
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 分页结果
*/
Page<CcdiAccountInfoVO> selectAccountInfoPage(Page<CcdiAccountInfoVO> page, CcdiAccountInfoQueryDTO queryDTO);
/**
* 查询账户库详情
*
* @param id 主键ID
* @return 账户库详情
*/
CcdiAccountInfoVO selectAccountInfoById(Long id);
/**
* 新增账户
*
* @param addDTO 新增DTO
* @return 影响行数
*/
int insertAccountInfo(CcdiAccountInfoAddDTO addDTO);
/**
* 修改账户
*
* @param editDTO 编辑DTO
* @return 影响行数
*/
int updateAccountInfo(CcdiAccountInfoEditDTO editDTO);
/**
* 批量删除账户
*
* @param ids 主键ID数组
* @return 影响行数
*/
int deleteAccountInfoByIds(Long[] ids);
/**
* 查询关系人下拉
*
* @param staffId 员工工号
* @return 下拉列表
*/
List<CcdiAccountRelationOptionVO> selectRelationOptionsByStaffId(Long staffId);
/**
* 导出账户库列表
*
* @param queryDTO 查询条件
* @return 导出列表
*/
List<CcdiAccountInfoExcel> selectAccountInfoListForExport(CcdiAccountInfoQueryDTO queryDTO);
/**
* 导入账户库信息
*
* @param excelList Excel数据
* @param updateSupport 是否更新已存在账户
* @return 导入结果
*/
ImportResult importAccountInfo(List<CcdiAccountInfoExcel> excelList, boolean updateSupport);
/**
* 获取导入失败记录
*
* @return 失败记录
*/
List<AccountInfoImportFailureVO> getLatestImportFailures();
}

View File

@@ -1,6 +1,7 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO;
@@ -25,6 +26,17 @@ public interface ICcdiStaffRecruitmentImportService {
String taskId,
String userName);
/**
* 异步导入招聘记录历史工作经历数据
*
* @param excelList Excel数据列表
* @param taskId 任务ID
* @param userName 用户名
*/
void importRecruitmentWorkAsync(List<CcdiStaffRecruitmentWorkExcel> excelList,
String taskId,
String userName);
/**
* 查询导入状态
*

View File

@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO;
import java.util.List;
@@ -81,4 +82,12 @@ public interface ICcdiStaffRecruitmentService {
* @return 结果
*/
String importRecruitment(List<CcdiStaffRecruitmentExcel> excelList);
/**
* 导入招聘记录历史工作经历数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
String importRecruitmentWork(List<CcdiStaffRecruitmentWorkExcel> excelList);
}

View File

@@ -0,0 +1,618 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.info.collection.domain.CcdiAccountInfo;
import com.ruoyi.info.collection.domain.CcdiAccountResult;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiAccountInfoExcel;
import com.ruoyi.info.collection.domain.vo.AccountInfoImportFailureVO;
import com.ruoyi.info.collection.domain.vo.CcdiAccountInfoVO;
import com.ruoyi.info.collection.domain.vo.CcdiAccountRelationOptionVO;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiAccountResultMapper;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.service.ICcdiAccountInfoService;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 账户库服务实现
*
* @author ruoyi
* @date 2026-04-13
*/
@Service
public class CcdiAccountInfoServiceImpl implements ICcdiAccountInfoService {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
private static final Set<String> OWNER_TYPES = Set.of("EMPLOYEE", "RELATION", "INTERMEDIARY", "EXTERNAL");
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> LEVELS = Set.of("LOW", "MEDIUM", "HIGH");
private final List<AccountInfoImportFailureVO> latestImportFailures = new CopyOnWriteArrayList<>();
@Resource
private CcdiAccountInfoMapper accountInfoMapper;
@Resource
private CcdiAccountResultMapper accountResultMapper;
@Resource
private CcdiBaseStaffMapper baseStaffMapper;
@Resource
private CcdiStaffFmyRelationMapper staffFmyRelationMapper;
@Override
public Page<CcdiAccountInfoVO> selectAccountInfoPage(Page<CcdiAccountInfoVO> page, CcdiAccountInfoQueryDTO queryDTO) {
return accountInfoMapper.selectAccountInfoPage(page, queryDTO);
}
@Override
public CcdiAccountInfoVO selectAccountInfoById(Long id) {
return accountInfoMapper.selectAccountInfoById(id);
}
@Override
@Transactional
public int insertAccountInfo(CcdiAccountInfoAddDTO addDTO) {
normalizeAddDto(addDTO);
validateDto(addDTO.getOwnerType(), addDTO.getOwnerId(), addDTO.getAccountType(), addDTO.getBankScope(),
addDTO.getStatus(), addDTO.getEffectiveDate(), addDTO.getInvalidDate(), addDTO.getTxnFrequencyLevel(),
addDTO.getTxnRiskLevel(), addDTO.getAvgMonthTxnAmount(), addDTO.getDebitSingleMaxAmount(),
addDTO.getCreditSingleMaxAmount(), addDTO.getDebitDailyMaxAmount(), addDTO.getCreditDailyMaxAmount());
validateDuplicateAccountNo(addDTO.getAccountNo(), null);
CcdiAccountInfo accountInfo = new CcdiAccountInfo();
BeanUtils.copyProperties(addDTO, accountInfo);
int result = accountInfoMapper.insert(accountInfo);
syncAccountResult(accountInfo.getBankScope(), null, accountInfo.getAccountNo(), addDTO);
return result;
}
@Override
@Transactional
public int updateAccountInfo(CcdiAccountInfoEditDTO editDTO) {
normalizeEditDto(editDTO);
validateDto(editDTO.getOwnerType(), editDTO.getOwnerId(), editDTO.getAccountType(), editDTO.getBankScope(),
editDTO.getStatus(), editDTO.getEffectiveDate(), editDTO.getInvalidDate(), editDTO.getTxnFrequencyLevel(),
editDTO.getTxnRiskLevel(), editDTO.getAvgMonthTxnAmount(), editDTO.getDebitSingleMaxAmount(),
editDTO.getCreditSingleMaxAmount(), editDTO.getDebitDailyMaxAmount(), editDTO.getCreditDailyMaxAmount());
CcdiAccountInfo existing = accountInfoMapper.selectById(editDTO.getId());
if (existing == null) {
throw new RuntimeException("账户不存在");
}
validateDuplicateAccountNo(editDTO.getAccountNo(), editDTO.getId());
CcdiAccountInfo accountInfo = new CcdiAccountInfo();
BeanUtils.copyProperties(editDTO, accountInfo);
int result = accountInfoMapper.updateById(accountInfo);
syncAccountResult(accountInfo.getBankScope(), existing, accountInfo.getAccountNo(), editDTO);
return result;
}
@Override
@Transactional
public int deleteAccountInfoByIds(Long[] ids) {
List<CcdiAccountInfo> accountList = accountInfoMapper.selectBatchIds(Arrays.asList(ids));
if (!accountList.isEmpty()) {
List<String> accountNos = accountList.stream()
.map(CcdiAccountInfo::getAccountNo)
.filter(StringUtils::isNotEmpty)
.toList();
if (!accountNos.isEmpty()) {
LambdaQueryWrapper<CcdiAccountResult> resultWrapper = new LambdaQueryWrapper<>();
resultWrapper.in(CcdiAccountResult::getAccountNo, accountNos);
accountResultMapper.delete(resultWrapper);
}
}
return accountInfoMapper.deleteBatchIds(Arrays.asList(ids));
}
@Override
public List<CcdiAccountRelationOptionVO> selectRelationOptionsByStaffId(Long staffId) {
if (staffId == null) {
return List.of();
}
return accountInfoMapper.selectRelationOptionsByStaffId(staffId);
}
@Override
public List<CcdiAccountInfoExcel> selectAccountInfoListForExport(CcdiAccountInfoQueryDTO queryDTO) {
return accountInfoMapper.selectAccountInfoListForExport(queryDTO).stream().map(this::toExcel).toList();
}
@Override
@Transactional
public ImportResult importAccountInfo(List<CcdiAccountInfoExcel> excelList, boolean updateSupport) {
latestImportFailures.clear();
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
int successCount = 0;
int rowNum = 1;
for (CcdiAccountInfoExcel excel : excelList) {
rowNum++;
try {
importSingleRow(excel, updateSupport);
successCount++;
} catch (Exception e) {
latestImportFailures.add(buildFailure(rowNum, excel, e.getMessage()));
}
}
result.setSuccessCount(successCount);
result.setFailureCount(latestImportFailures.size());
return result;
}
@Override
public List<AccountInfoImportFailureVO> getLatestImportFailures() {
return new ArrayList<>(latestImportFailures);
}
private void validateDto(String ownerType, String ownerId, String accountType, String bankScope, Integer status,
java.util.Date effectiveDate, java.util.Date invalidDate, String txnFrequencyLevel,
String txnRiskLevel, BigDecimal avgMonthTxnAmount, BigDecimal debitSingleMaxAmount,
BigDecimal creditSingleMaxAmount, BigDecimal debitDailyMaxAmount,
BigDecimal creditDailyMaxAmount) {
if (!OWNER_TYPES.contains(ownerType)) {
throw new RuntimeException("所属人类型不合法");
}
if (!ACCOUNT_TYPES.contains(accountType)) {
throw new RuntimeException("账户类型不合法");
}
if (!BANK_SCOPES.contains(bankScope)) {
throw new RuntimeException("账户范围不合法");
}
if (status == null || (status != 1 && status != 2)) {
throw new RuntimeException("状态不合法");
}
if (effectiveDate == null) {
throw new RuntimeException("生效日期不能为空");
}
if (invalidDate != null && invalidDate.before(effectiveDate)) {
throw new RuntimeException("失效日期不能早于生效日期");
}
if (StringUtils.isNotEmpty(txnFrequencyLevel) && !LEVELS.contains(txnFrequencyLevel)) {
throw new RuntimeException("频率等级不合法");
}
if (StringUtils.isNotEmpty(txnRiskLevel) && !LEVELS.contains(txnRiskLevel)) {
throw new RuntimeException("风险等级不合法");
}
validateAmount(avgMonthTxnAmount, "月均交易金额");
validateAmount(debitSingleMaxAmount, "借方单笔最高额");
validateAmount(creditSingleMaxAmount, "贷方单笔最高额");
validateAmount(debitDailyMaxAmount, "借方日累计最高额");
validateAmount(creditDailyMaxAmount, "贷方日累计最高额");
validateOwner(ownerType, ownerId);
}
private void validateOwner(String ownerType, String ownerId) {
if (StringUtils.isEmpty(ownerId)) {
if ("EXTERNAL".equals(ownerType) || "INTERMEDIARY".equals(ownerType)) {
throw new RuntimeException("证件号不能为空");
}
throw new RuntimeException("所属人不能为空");
}
if ("EXTERNAL".equals(ownerType) || "INTERMEDIARY".equals(ownerType)) {
return;
}
if ("EMPLOYEE".equals(ownerType)) {
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBaseStaff::getIdCard, ownerId);
CcdiBaseStaff staff = baseStaffMapper.selectOne(wrapper);
if (staff == null) {
throw new RuntimeException("员工不存在");
}
return;
}
LambdaQueryWrapper<CcdiStaffFmyRelation> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiStaffFmyRelation::getRelationCertNo, ownerId);
CcdiStaffFmyRelation relation = staffFmyRelationMapper.selectOne(wrapper);
if (relation == null) {
throw new RuntimeException("关系人不存在");
}
}
private void validateDuplicateAccountNo(String accountNo, Long excludeId) {
LambdaQueryWrapper<CcdiAccountInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiAccountInfo::getAccountNo, accountNo);
if (excludeId != null) {
wrapper.ne(CcdiAccountInfo::getId, excludeId);
}
if (accountInfoMapper.selectCount(wrapper) > 0) {
throw new RuntimeException("账户号码已存在");
}
}
private void syncAccountResult(String newBankScope, CcdiAccountInfo existing, String accountNo, Object dto) {
String oldBankScope = existing == null ? null : existing.getBankScope();
String oldAccountNo = existing == null ? null : existing.getAccountNo();
if (existing != null && "EXTERNAL".equals(oldBankScope)
&& (!"EXTERNAL".equals(newBankScope) || !StringUtils.equals(oldAccountNo, accountNo))) {
LambdaQueryWrapper<CcdiAccountResult> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(CcdiAccountResult::getAccountNo, oldAccountNo);
accountResultMapper.delete(deleteWrapper);
}
if (!"EXTERNAL".equals(newBankScope)) {
return;
}
LambdaQueryWrapper<CcdiAccountResult> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiAccountResult::getAccountNo, accountNo);
CcdiAccountResult existingResult = accountResultMapper.selectOne(wrapper);
CcdiAccountResult accountResult = new CcdiAccountResult();
BeanUtils.copyProperties(dto, accountResult);
accountResult.setAccountNo(accountNo);
if (accountResult.getIsActualControl() == null) {
accountResult.setIsActualControl(1);
}
if (accountResult.getAvgMonthTxnCount() == null) {
accountResult.setAvgMonthTxnCount(0);
}
if (accountResult.getAvgMonthTxnAmount() == null) {
accountResult.setAvgMonthTxnAmount(BigDecimal.ZERO);
}
if (StringUtils.isEmpty(accountResult.getTxnFrequencyLevel())) {
accountResult.setTxnFrequencyLevel("MEDIUM");
}
if (StringUtils.isEmpty(accountResult.getTxnRiskLevel())) {
accountResult.setTxnRiskLevel("LOW");
}
if (existingResult == null) {
accountResultMapper.insert(accountResult);
return;
}
accountResult.setResultId(existingResult.getResultId());
accountResultMapper.updateById(accountResult);
}
private void validateAmount(BigDecimal amount, String fieldLabel) {
if (amount == null) {
return;
}
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException(fieldLabel + "不能为负数");
}
if (amount.scale() > 2) {
throw new RuntimeException(fieldLabel + "最多保留2位小数");
}
}
private void normalizeAddDto(CcdiAccountInfoAddDTO addDTO) {
addDTO.setOwnerType(toUpper(addDTO.getOwnerType()));
addDTO.setAccountType(toUpper(addDTO.getAccountType()));
addDTO.setBankScope(toUpper(addDTO.getBankScope()));
addDTO.setCurrency(normalizeCurrency(addDTO.getCurrency()));
addDTO.setTxnFrequencyLevel(toUpper(addDTO.getTxnFrequencyLevel()));
addDTO.setTxnRiskLevel(toUpper(addDTO.getTxnRiskLevel()));
addDTO.setOwnerId(normalizeOwnerId(addDTO.getOwnerId()));
}
private void normalizeEditDto(CcdiAccountInfoEditDTO editDTO) {
editDTO.setOwnerType(toUpper(editDTO.getOwnerType()));
editDTO.setAccountType(toUpper(editDTO.getAccountType()));
editDTO.setBankScope(toUpper(editDTO.getBankScope()));
editDTO.setCurrency(normalizeCurrency(editDTO.getCurrency()));
editDTO.setTxnFrequencyLevel(toUpper(editDTO.getTxnFrequencyLevel()));
editDTO.setTxnRiskLevel(toUpper(editDTO.getTxnRiskLevel()));
editDTO.setOwnerId(normalizeOwnerId(editDTO.getOwnerId()));
}
private String normalizeCurrency(String currency) {
if (StringUtils.isEmpty(currency)) {
return "CNY";
}
return currency.trim().toUpperCase(Locale.ROOT);
}
private String toUpper(String value) {
if (StringUtils.isEmpty(value)) {
return value;
}
return value.trim().toUpperCase(Locale.ROOT);
}
private String normalizeOwnerId(String ownerId) {
if (StringUtils.isEmpty(ownerId)) {
return ownerId;
}
return ownerId.trim();
}
private CcdiAccountInfoExcel toExcel(CcdiAccountInfoVO vo) {
CcdiAccountInfoExcel excel = new CcdiAccountInfoExcel();
excel.setOwnerType(ownerTypeLabel(vo.getOwnerType()));
excel.setOwnerId(vo.getOwnerId());
excel.setAccountName(vo.getAccountName());
excel.setAccountNo(vo.getAccountNo());
excel.setAccountType(accountTypeLabel(vo.getAccountType()));
excel.setBankScope(bankScopeLabel(vo.getBankScope()));
excel.setOpenBank(vo.getOpenBank());
excel.setBankCode(vo.getBankCode());
excel.setCurrency(vo.getCurrency());
excel.setStatus(vo.getStatus() == null ? "" : (vo.getStatus() == 1 ? "正常" : "已销户"));
excel.setEffectiveDate(formatDate(vo.getEffectiveDate()));
excel.setInvalidDate(formatDate(vo.getInvalidDate()));
excel.setIsActualControl(formatYesNo(vo.getIsActualControl()));
excel.setAvgMonthTxnCount(vo.getAvgMonthTxnCount() == null ? "" : String.valueOf(vo.getAvgMonthTxnCount()));
excel.setAvgMonthTxnAmount(formatNumber(vo.getAvgMonthTxnAmount()));
excel.setTxnFrequencyLevel(StringUtils.isEmpty(vo.getTxnFrequencyLevel()) ? "" : vo.getTxnFrequencyLevel());
excel.setDebitSingleMaxAmount(formatNumber(vo.getDebitSingleMaxAmount()));
excel.setCreditSingleMaxAmount(formatNumber(vo.getCreditSingleMaxAmount()));
excel.setDebitDailyMaxAmount(formatNumber(vo.getDebitDailyMaxAmount()));
excel.setCreditDailyMaxAmount(formatNumber(vo.getCreditDailyMaxAmount()));
excel.setTxnRiskLevel(StringUtils.isEmpty(vo.getTxnRiskLevel()) ? "" : vo.getTxnRiskLevel());
return excel;
}
private void importSingleRow(CcdiAccountInfoExcel excel, boolean updateSupport) {
CcdiAccountInfo existing = findByAccountNo(excel.getAccountNo());
if (existing != null && !updateSupport) {
throw new RuntimeException("账户号码已存在,请勾选更新已存在数据后重试");
}
if (existing != null) {
CcdiAccountInfoEditDTO editDTO = toEditDto(existing.getId(), excel);
updateAccountInfo(editDTO);
return;
}
CcdiAccountInfoAddDTO addDTO = toAddDto(excel);
insertAccountInfo(addDTO);
}
private CcdiAccountInfoAddDTO toAddDto(CcdiAccountInfoExcel excel) {
CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO();
dto.setOwnerType(parseOwnerType(excel.getOwnerType()));
dto.setOwnerId(normalizeOwnerId(excel.getOwnerId()));
dto.setAccountName(trimToNull(excel.getAccountName()));
dto.setAccountNo(trimToNull(excel.getAccountNo()));
dto.setAccountType(parseAccountType(excel.getAccountType()));
dto.setBankScope(parseBankScope(excel.getBankScope()));
dto.setOpenBank(trimToNull(excel.getOpenBank()));
dto.setBankCode(trimToNull(excel.getBankCode()));
dto.setCurrency(normalizeCurrency(excel.getCurrency()));
dto.setStatus(parseStatus(excel.getStatus()));
dto.setEffectiveDate(parseDateRequired(excel.getEffectiveDate(), "生效日期"));
dto.setInvalidDate(parseDateOptional(excel.getInvalidDate()));
dto.setIsActualControl(parseBooleanFlag(excel.getIsActualControl(), 1));
dto.setAvgMonthTxnCount(parseInteger(excel.getAvgMonthTxnCount()));
dto.setAvgMonthTxnAmount(parseDecimal(excel.getAvgMonthTxnAmount()));
dto.setTxnFrequencyLevel(parseLevel(excel.getTxnFrequencyLevel(), "频率等级"));
dto.setDebitSingleMaxAmount(parseDecimal(excel.getDebitSingleMaxAmount()));
dto.setCreditSingleMaxAmount(parseDecimal(excel.getCreditSingleMaxAmount()));
dto.setDebitDailyMaxAmount(parseDecimal(excel.getDebitDailyMaxAmount()));
dto.setCreditDailyMaxAmount(parseDecimal(excel.getCreditDailyMaxAmount()));
dto.setTxnRiskLevel(parseLevel(excel.getTxnRiskLevel(), "风险等级"));
return dto;
}
private CcdiAccountInfoEditDTO toEditDto(Long id, CcdiAccountInfoExcel excel) {
CcdiAccountInfoEditDTO dto = new CcdiAccountInfoEditDTO();
BeanUtils.copyProperties(toAddDto(excel), dto);
dto.setId(id);
return dto;
}
private CcdiAccountInfo findByAccountNo(String accountNo) {
LambdaQueryWrapper<CcdiAccountInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiAccountInfo::getAccountNo, trimToNull(accountNo));
return accountInfoMapper.selectOne(wrapper);
}
private AccountInfoImportFailureVO buildFailure(int rowNum, CcdiAccountInfoExcel excel, String errorMessage) {
AccountInfoImportFailureVO failure = new AccountInfoImportFailureVO();
failure.setRowNum(rowNum);
failure.setOwnerType(excel.getOwnerType());
failure.setOwnerId(excel.getOwnerId());
failure.setAccountNo(excel.getAccountNo());
failure.setErrorMessage(errorMessage);
return failure;
}
private String parseOwnerType(String value) {
String normalized = toUpper(value);
if ("员工".equals(value)) {
return "EMPLOYEE";
}
if ("员工关系人".equals(value)) {
return "RELATION";
}
if ("中介".equals(value)) {
return "INTERMEDIARY";
}
if ("外部人员".equals(value)) {
return "EXTERNAL";
}
return normalized;
}
private String parseAccountType(String value) {
String normalized = toUpper(value);
return switch (normalized) {
case "银行账户" -> "BANK";
case "证券账户" -> "SECURITIES";
case "支付账户" -> "PAYMENT";
case "其他" -> "OTHER";
default -> normalized;
};
}
private String parseBankScope(String value) {
String normalized = toUpper(value);
return switch (normalized) {
case "行内" -> "INTERNAL";
case "行外" -> "EXTERNAL";
default -> normalized;
};
}
private Integer parseStatus(String value) {
String normalized = trimToNull(value);
if (normalized == null) {
return null;
}
return switch (normalized) {
case "1", "正常" -> 1;
case "2", "已销户" -> 2;
default -> throw new RuntimeException("状态仅支持“正常/已销户”或“1/2”");
};
}
private Integer parseBooleanFlag(String value, Integer defaultValue) {
String normalized = trimToNull(value);
if (normalized == null) {
return defaultValue;
}
return switch (normalized) {
case "1", "", "Y", "YES", "TRUE", "true" -> 1;
case "0", "", "N", "NO", "FALSE", "false" -> 0;
default -> throw new RuntimeException("是否实控账户仅支持“是/否”或“1/0”");
};
}
private Integer parseInteger(String value) {
String normalized = trimToNull(value);
if (normalized == null) {
return null;
}
try {
return Integer.valueOf(normalized);
} catch (NumberFormatException e) {
throw new RuntimeException("月均交易笔数格式不正确");
}
}
private BigDecimal parseDecimal(String value) {
String normalized = trimToNull(value);
if (normalized == null) {
return null;
}
try {
return new BigDecimal(normalized);
} catch (NumberFormatException e) {
throw new RuntimeException("金额字段格式不正确");
}
}
private String parseLevel(String value, String fieldLabel) {
String normalized = toUpper(value);
if (StringUtils.isEmpty(normalized)) {
return null;
}
return switch (normalized) {
case "", "LOW" -> "LOW";
case "", "MEDIUM" -> "MEDIUM";
case "", "HIGH" -> "HIGH";
default -> throw new RuntimeException(fieldLabel + "仅支持 LOW/MEDIUM/HIGH");
};
}
private Date parseDateRequired(String value, String fieldLabel) {
Date date = parseDateOptional(value);
if (date == null) {
throw new RuntimeException(fieldLabel + "不能为空");
}
return date;
}
private Date parseDateOptional(String value) {
String normalized = trimToNull(value);
if (normalized == null) {
return null;
}
try {
synchronized (DATE_FORMAT) {
return DATE_FORMAT.parse(normalized);
}
} catch (ParseException e) {
throw new RuntimeException("日期格式需为 yyyy-MM-dd");
}
}
private String trimToNull(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
return value.trim();
}
private String ownerTypeLabel(String value) {
return switch (value) {
case "EMPLOYEE" -> "员工";
case "RELATION" -> "员工关系人";
case "INTERMEDIARY" -> "中介";
case "EXTERNAL" -> "外部人员";
default -> value;
};
}
private String accountTypeLabel(String value) {
return switch (value) {
case "BANK" -> "银行账户";
case "SECURITIES" -> "证券账户";
case "PAYMENT" -> "支付账户";
case "OTHER" -> "其他";
default -> value;
};
}
private String bankScopeLabel(String value) {
return switch (value) {
case "INTERNAL" -> "行内";
case "EXTERNAL" -> "行外";
default -> value;
};
}
private String formatYesNo(Integer value) {
if (value == null) {
return "";
}
return value == 1 ? "" : "";
}
private String formatDate(Date value) {
if (value == null) {
return "";
}
synchronized (DATE_FORMAT) {
return DATE_FORMAT.format(value);
}
}
private String formatNumber(Number value) {
return value == null ? "" : String.valueOf(value);
}
}

View File

@@ -1,9 +1,7 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.CcdiCreditNegativeInfo;
import com.ruoyi.info.collection.domain.CcdiDebtsInfo;
import com.ruoyi.info.collection.domain.dto.CcdiCreditInfoQueryDTO;
@@ -12,7 +10,6 @@ import com.ruoyi.info.collection.domain.vo.CreditInfoListVO;
import com.ruoyi.info.collection.domain.vo.CreditInfoNegativeVO;
import com.ruoyi.info.collection.domain.vo.CreditInfoUploadFailureVO;
import com.ruoyi.info.collection.domain.vo.CreditInfoUploadResultVO;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiCreditInfoQueryMapper;
import com.ruoyi.info.collection.mapper.CcdiCreditNegativeInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiDebtsInfoMapper;
@@ -45,9 +42,6 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
@Resource
private CreditInfoPayloadAssembler assembler;
@Resource
private CcdiBaseStaffMapper baseStaffMapper;
@Resource
private CcdiDebtsInfoMapper debtsInfoMapper;
@@ -156,7 +150,7 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
String personId = stringValue(header.get("query_cert_no"));
String personName = stringValue(header.get("query_cust_name"));
LocalDate queryDate = parseQueryDate(stringValue(header.get("report_time")));
ensureStaffExists(personId);
ensurePersonIdPresent(personId);
ensureLatestQueryDate(personId, queryDate);
List<CcdiDebtsInfo> debts = assembler.buildDebts(personId, personName, queryDate, payload);
@@ -209,15 +203,9 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
return header;
}
private void ensureStaffExists(String personId) {
private void ensurePersonIdPresent(String personId) {
if (isBlank(personId)) {
throw new RuntimeException("征信解析结果缺少员工身份证号");
}
CcdiBaseStaff staff = baseStaffMapper.selectOne(new LambdaQueryWrapper<CcdiBaseStaff>()
.eq(CcdiBaseStaff::getIdCard, personId)
.last("LIMIT 1"));
if (staff == null) {
throw new RuntimeException("未找到对应员工信息");
throw new RuntimeException("征信解析结果缺少身份证号");
}
}

View File

@@ -3,13 +3,17 @@ package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment;
import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO;
import com.ruoyi.info.collection.enums.AdmitStatus;
import com.ruoyi.info.collection.enums.RecruitType;
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper;
import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.IdCardUtil;
@@ -43,6 +47,9 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
@Resource
private CcdiStaffRecruitmentMapper recruitmentMapper;
@Resource
private CcdiStaffRecruitmentWorkMapper recruitmentWorkMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@@ -60,10 +67,10 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
List<CcdiStaffRecruitment> newRecords = new ArrayList<>();
List<RecruitmentImportFailureVO> failures = new ArrayList<>();
// 批量查询已存在的招聘项目编号
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的招聘项目编号", excelList.size());
// 批量查询已存在的招聘记录编号
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的招聘记录编号", excelList.size());
Set<String> existingRecruitIds = getExistingRecruitIds(excelList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "招聘项目编号", existingRecruitIds.size());
ImportLogUtils.logBatchQueryComplete(log, taskId, "招聘记录编号", existingRecruitIds.size());
// 用于检测Excel内部的重复ID
Set<String> excelProcessedIds = new HashSet<>();
@@ -76,19 +83,21 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
// 转换为AddDTO进行验证
CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO();
BeanUtils.copyProperties(excel, addDTO);
addDTO.setRecruitType(RecruitType.inferCode(addDTO.getRecruitName()));
// 验证数据
validateRecruitmentData(addDTO, existingRecruitIds);
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
BeanUtils.copyProperties(excel, recruitment);
recruitment.setRecruitType(addDTO.getRecruitType());
if (existingRecruitIds.contains(excel.getRecruitId())) {
// 招聘项目编号在数据库中已存在,直接报错
throw new RuntimeException(String.format("招聘项目编号[%s]已存在,请勿重复导入", excel.getRecruitId()));
// 招聘记录编号在数据库中已存在,直接报错
throw new RuntimeException(String.format("招聘记录编号[%s]已存在,请勿重复导入", excel.getRecruitId()));
} else if (excelProcessedIds.contains(excel.getRecruitId())) {
// 招聘项目编号在Excel文件内部重复
throw new RuntimeException(String.format("招聘项目编号[%s]在导入文件中重复,已跳过此条记录", excel.getRecruitId()));
// 招聘记录编号在Excel文件内部重复
throw new RuntimeException(String.format("招聘记录编号[%s]在导入文件中重复,已跳过此条记录", excel.getRecruitId()));
} else {
recruitment.setCreatedBy(userName);
recruitment.setUpdatedBy(userName);
@@ -107,7 +116,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("招聘项目编号=%s, 项目名称=%s, 应聘人员=%s",
String keyData = String.format("招聘记录编号=%s, 项目名称=%s, 应聘人员=%s",
excel.getRecruitId(), excel.getRecruitName(), excel.getCandName());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
@@ -142,7 +151,85 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
// 记录导入完成
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "招聘信息",
ImportLogUtils.logImportComplete(log, taskId, "招聘信息",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
@Override
@Async
@Transactional
public void importRecruitmentWorkAsync(List<CcdiStaffRecruitmentWorkExcel> excelList,
String taskId,
String userName) {
long startTime = System.currentTimeMillis();
ImportLogUtils.logImportStart(log, taskId, "招聘历史工作经历", excelList.size(), userName);
List<RecruitmentImportFailureVO> failures = new ArrayList<>();
List<CcdiStaffRecruitmentWork> validRecords = new ArrayList<>();
Set<String> failedRecruitIds = new HashSet<>();
Set<String> processedRecruitSortKeys = new HashSet<>();
Map<String, CcdiStaffRecruitment> recruitmentMap = getRecruitmentMap(excelList);
for (int i = 0; i < excelList.size(); i++) {
CcdiStaffRecruitmentWorkExcel excel = excelList.get(i);
try {
CcdiStaffRecruitment recruitment = recruitmentMap.get(trim(excel.getRecruitId()));
validateRecruitmentWorkData(excel, recruitment, processedRecruitSortKeys);
CcdiStaffRecruitmentWork work = new CcdiStaffRecruitmentWork();
BeanUtils.copyProperties(excel, work);
work.setRecruitId(trim(excel.getRecruitId()));
work.setCreatedBy(userName);
work.setUpdatedBy(userName);
validRecords.add(work);
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
validRecords.size(), failures.size());
} catch (Exception e) {
failedRecruitIds.add(trim(excel.getRecruitId()));
failures.add(buildWorkFailure(excel, e.getMessage()));
String keyData = String.format("招聘记录编号=%s, 候选人=%s, 工作单位=%s",
excel.getRecruitId(), excel.getCandName(), excel.getCompanyName());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
List<CcdiStaffRecruitmentWork> importRecords = validRecords.stream()
.filter(work -> !failedRecruitIds.contains(work.getRecruitId()))
.toList();
appendSkippedFailures(validRecords, failedRecruitIds, failures);
if (!importRecords.isEmpty()) {
Set<String> importRecruitIds = importRecords.stream()
.map(CcdiStaffRecruitmentWork::getRecruitId)
.collect(Collectors.toSet());
LambdaQueryWrapper<CcdiStaffRecruitmentWork> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.in(CcdiStaffRecruitmentWork::getRecruitId, importRecruitIds);
recruitmentWorkMapper.delete(deleteWrapper);
importRecords.forEach(recruitmentWorkMapper::insert);
}
if (!failures.isEmpty()) {
try {
String failuresKey = "import:recruitment:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
}
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(importRecords.size());
result.setFailureCount(failures.size());
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus(taskId, finalStatus, result);
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "招聘历史工作经历",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
@@ -184,7 +271,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
}
/**
* 批量查询已存在的招聘项目编号
* 批量查询已存在的招聘记录编号
*/
private Set<String> getExistingRecruitIds(List<CcdiStaffRecruitmentExcel> excelList) {
List<String> recruitIds = excelList.stream()
@@ -212,7 +299,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
Set<String> existingRecruitIds) {
// 验证必填字段
if (StringUtils.isEmpty(addDTO.getRecruitId())) {
throw new RuntimeException("招聘项目编号不能为空");
throw new RuntimeException("招聘记录编号不能为空");
}
if (StringUtils.isEmpty(addDTO.getRecruitName())) {
throw new RuntimeException("招聘项目名称不能为空");
@@ -247,6 +334,9 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
if (StringUtils.isEmpty(addDTO.getAdmitStatus())) {
throw new RuntimeException("录用情况不能为空");
}
if (StringUtils.isEmpty(addDTO.getRecruitType())) {
throw new RuntimeException("招聘类型不能为空");
}
// 验证证件号码格式
String idCardError = IdCardUtil.getErrorMessage(addDTO.getCandId());
@@ -263,6 +353,115 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
if (AdmitStatus.getDescByCode(addDTO.getAdmitStatus()) == null) {
throw new RuntimeException("录用情况只能填写'录用'、'未录用'或'放弃'");
}
if (RecruitType.getDescByCode(addDTO.getRecruitType()) == null) {
throw new RuntimeException("招聘类型只能填写'SOCIAL'或'CAMPUS'");
}
}
private Map<String, CcdiStaffRecruitment> getRecruitmentMap(List<CcdiStaffRecruitmentWorkExcel> excelList) {
List<String> recruitIds = excelList.stream()
.map(CcdiStaffRecruitmentWorkExcel::getRecruitId)
.map(this::trim)
.filter(StringUtils::isNotEmpty)
.distinct()
.toList();
if (recruitIds.isEmpty()) {
return Collections.emptyMap();
}
List<CcdiStaffRecruitment> recruitments = recruitmentMapper.selectBatchIds(recruitIds);
return recruitments.stream()
.collect(Collectors.toMap(CcdiStaffRecruitment::getRecruitId, item -> item));
}
private void validateRecruitmentWorkData(CcdiStaffRecruitmentWorkExcel excel,
CcdiStaffRecruitment recruitment,
Set<String> processedRecruitSortKeys) {
if (StringUtils.isEmpty(trim(excel.getRecruitId()))) {
throw new RuntimeException("招聘记录编号不能为空");
}
if (StringUtils.isEmpty(trim(excel.getCandName()))) {
throw new RuntimeException("候选人姓名不能为空");
}
if (StringUtils.isEmpty(trim(excel.getRecruitName()))) {
throw new RuntimeException("招聘项目名称不能为空");
}
if (StringUtils.isEmpty(trim(excel.getPosName()))) {
throw new RuntimeException("职位名称不能为空");
}
if (excel.getSortOrder() == null || excel.getSortOrder() <= 0) {
throw new RuntimeException("排序号不能为空且必须大于0");
}
if (StringUtils.isEmpty(trim(excel.getCompanyName()))) {
throw new RuntimeException("工作单位不能为空");
}
if (StringUtils.isEmpty(trim(excel.getPositionName()))) {
throw new RuntimeException("岗位不能为空");
}
if (StringUtils.isEmpty(trim(excel.getJobStartMonth()))) {
throw new RuntimeException("入职年月不能为空");
}
validateMonth(excel.getJobStartMonth(), "入职年月");
if (StringUtils.isNotEmpty(trim(excel.getJobEndMonth()))) {
validateMonth(excel.getJobEndMonth(), "离职年月");
}
if (recruitment == null) {
throw new RuntimeException("招聘记录编号不存在,请先维护招聘主信息");
}
if (!"SOCIAL".equals(recruitment.getRecruitType())) {
throw new RuntimeException("该招聘记录不是社招,不允许导入历史工作经历");
}
if (!sameText(excel.getCandName(), recruitment.getCandName())) {
throw new RuntimeException("招聘记录编号与候选人姓名不匹配");
}
if (!sameText(excel.getRecruitName(), recruitment.getRecruitName())) {
throw new RuntimeException("招聘记录编号与招聘项目名称不匹配");
}
if (!sameText(excel.getPosName(), recruitment.getPosName())) {
throw new RuntimeException("招聘记录编号与职位名称不匹配");
}
String duplicateKey = trim(excel.getRecruitId()) + "#" + excel.getSortOrder();
if (!processedRecruitSortKeys.add(duplicateKey)) {
throw new RuntimeException("同一招聘记录编号下排序号重复");
}
}
private void validateMonth(String value, String fieldName) {
String month = trim(value);
if (!month.matches("^((19|20)\\d{2})-(0[1-9]|1[0-2])$")) {
throw new RuntimeException(fieldName + "格式不正确应为YYYY-MM");
}
}
private boolean sameText(String first, String second) {
return Objects.equals(trim(first), trim(second));
}
private String trim(String value) {
return value == null ? null : value.trim();
}
private RecruitmentImportFailureVO buildWorkFailure(CcdiStaffRecruitmentWorkExcel excel, String errorMessage) {
RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(errorMessage);
return failure;
}
private void appendSkippedFailures(List<CcdiStaffRecruitmentWork> validRecords,
Set<String> failedRecruitIds,
List<RecruitmentImportFailureVO> failures) {
Set<String> appendedRecruitIds = new HashSet<>();
for (CcdiStaffRecruitmentWork work : validRecords) {
if (failedRecruitIds.contains(work.getRecruitId()) && appendedRecruitIds.add(work.getRecruitId())) {
RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO();
failure.setRecruitId(work.getRecruitId());
failure.setCompanyName(work.getCompanyName());
failure.setPositionName(work.getPositionName());
failure.setErrorMessage("同一招聘记录编号存在失败行,已跳过该编号下全部工作经历,避免覆盖旧数据");
failures.add(failure);
}
}
}
/**

View File

@@ -1,13 +1,18 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment;
import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentWorkVO;
import com.ruoyi.info.collection.enums.AdmitStatus;
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper;
import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService;
import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentService;
@@ -19,6 +24,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -37,6 +43,9 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
@Resource
private CcdiStaffRecruitmentMapper recruitmentMapper;
@Resource
private CcdiStaffRecruitmentWorkMapper recruitmentWorkMapper;
@Resource
private ICcdiStaffRecruitmentImportService recruitmentImportService;
@@ -96,7 +105,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
/**
* 查询招聘信息详情
*
* @param recruitId 招聘项目编号
* @param recruitId 招聘记录编号
* @return 招聘信息VO
*/
@Override
@@ -104,6 +113,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
CcdiStaffRecruitmentVO vo = recruitmentMapper.selectRecruitmentById(recruitId);
if (vo != null) {
vo.setAdmitStatusDesc(AdmitStatus.getDescByCode(vo.getAdmitStatus()));
vo.setWorkExperienceList(selectWorkExperienceList(recruitId));
}
return vo;
}
@@ -117,9 +127,9 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
@Override
@Transactional
public int insertRecruitment(CcdiStaffRecruitmentAddDTO addDTO) {
// 检查招聘项目编号唯一性
// 检查招聘记录编号唯一性
if (recruitmentMapper.selectById(addDTO.getRecruitId()) != null) {
throw new RuntimeException("该招聘项目编号已存在");
throw new RuntimeException("该招聘记录编号已存在");
}
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
@@ -148,12 +158,15 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
/**
* 批量删除招聘信息
*
* @param recruitIds 需要删除的招聘项目编号
* @param recruitIds 需要删除的招聘记录编号
* @return 结果
*/
@Override
@Transactional
public int deleteRecruitmentByIds(String[] recruitIds) {
LambdaQueryWrapper<CcdiStaffRecruitmentWork> workWrapper = new LambdaQueryWrapper<>();
workWrapper.in(CcdiStaffRecruitmentWork::getRecruitId, List.of(recruitIds));
recruitmentWorkMapper.delete(workWrapper);
return recruitmentMapper.deleteBatchIds(List.of(recruitIds));
}
@@ -197,4 +210,56 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
return taskId;
}
/**
* 导入招聘记录历史工作经历数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
@Override
@Transactional
public String importRecruitmentWork(List<CcdiStaffRecruitmentWorkExcel> excelList) {
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
throw new RuntimeException("至少需要一条数据");
}
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
String userName = SecurityUtils.getUsername();
String statusKey = "import:recruitment:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("taskId", taskId);
statusData.put("status", "PROCESSING");
statusData.put("totalCount", excelList.size());
statusData.put("successCount", 0);
statusData.put("failureCount", 0);
statusData.put("progress", 0);
statusData.put("startTime", startTime);
statusData.put("message", "正在处理历史工作经历...");
redisTemplate.opsForHash().putAll(statusKey, statusData);
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
recruitmentImportService.importRecruitmentWorkAsync(excelList, taskId, userName);
return taskId;
}
private List<CcdiStaffRecruitmentWorkVO> selectWorkExperienceList(String recruitId) {
LambdaQueryWrapper<CcdiStaffRecruitmentWork> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiStaffRecruitmentWork::getRecruitId, recruitId)
.orderByAsc(CcdiStaffRecruitmentWork::getSortOrder)
.orderByDesc(CcdiStaffRecruitmentWork::getId);
List<CcdiStaffRecruitmentWork> workList = recruitmentWorkMapper.selectList(wrapper);
if (workList == null || workList.isEmpty()) {
return new ArrayList<>();
}
return workList.stream().map(work -> {
CcdiStaffRecruitmentWorkVO vo = new CcdiStaffRecruitmentWorkVO();
BeanUtils.copyProperties(work, vo);
return vo;
}).toList();
}
}

View File

@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper">
<resultMap id="CcdiAccountInfoVOResult" type="com.ruoyi.info.collection.domain.vo.CcdiAccountInfoVO">
<id property="id" column="id"/>
<result property="ownerType" column="ownerType"/>
<result property="ownerId" column="ownerId"/>
<result property="staffId" column="staffId"/>
<result property="staffName" column="staffName"/>
<result property="relationId" column="relationId"/>
<result property="relationType" column="relationType"/>
<result property="relationName" column="relationName"/>
<result property="relationCertNo" column="relationCertNo"/>
<result property="accountNo" column="accountNo"/>
<result property="accountType" column="accountType"/>
<result property="bankScope" column="bankScope"/>
<result property="accountName" column="accountName"/>
<result property="openBank" column="openBank"/>
<result property="bankCode" column="bankCode"/>
<result property="currency" column="currency"/>
<result property="status" column="status"/>
<result property="effectiveDate" column="effectiveDate"/>
<result property="invalidDate" column="invalidDate"/>
<result property="isActualControl" column="isActualControl"/>
<result property="avgMonthTxnCount" column="avgMonthTxnCount"/>
<result property="avgMonthTxnAmount" column="avgMonthTxnAmount"/>
<result property="txnFrequencyLevel" column="txnFrequencyLevel"/>
<result property="debitSingleMaxAmount" column="debitSingleMaxAmount"/>
<result property="creditSingleMaxAmount" column="creditSingleMaxAmount"/>
<result property="debitDailyMaxAmount" column="debitDailyMaxAmount"/>
<result property="creditDailyMaxAmount" column="creditDailyMaxAmount"/>
<result property="txnRiskLevel" column="txnRiskLevel"/>
<result property="createBy" column="createBy"/>
<result property="createTime" column="createTime"/>
<result property="updateBy" column="updateBy"/>
<result property="updateTime" column="updateTime"/>
</resultMap>
<sql id="AccountInfoSelectColumns">
ai.account_id AS id,
ai.owner_type AS ownerType,
ai.owner_id AS ownerId,
CASE
WHEN ai.owner_type = 'EMPLOYEE' THEN bs.staff_id
WHEN ai.owner_type = 'RELATION' THEN bsRel.staff_id
ELSE NULL
END AS staffId,
CASE
WHEN ai.owner_type = 'EMPLOYEE' THEN bs.name
WHEN ai.owner_type = 'RELATION' THEN bsRel.name
ELSE NULL
END AS staffName,
CASE WHEN ai.owner_type = 'RELATION' THEN fr.id ELSE NULL END AS relationId,
CASE WHEN ai.owner_type = 'RELATION' THEN fr.relation_type ELSE NULL END AS relationType,
CASE WHEN ai.owner_type = 'RELATION' THEN fr.relation_name ELSE NULL END AS relationName,
CASE WHEN ai.owner_type = 'RELATION' THEN fr.relation_cert_no ELSE NULL END AS relationCertNo,
ai.account_no AS accountNo,
ai.account_type AS accountType,
ai.bank_scope AS bankScope,
ai.account_name AS accountName,
ai.bank AS openBank,
ai.bank_code AS bankCode,
ai.currency AS currency,
ai.status AS status,
ai.effective_date AS effectiveDate,
ai.invalid_date AS invalidDate,
ar.is_self_account AS isActualControl,
ar.monthly_avg_trans_count AS avgMonthTxnCount,
ar.monthly_avg_trans_amount AS avgMonthTxnAmount,
ar.trans_freq_type AS txnFrequencyLevel,
ar.dr_max_single_amount AS debitSingleMaxAmount,
ar.cr_max_single_amount AS creditSingleMaxAmount,
ar.dr_max_daily_amount AS debitDailyMaxAmount,
ar.cr_max_daily_amount AS creditDailyMaxAmount,
ar.trans_risk_level AS txnRiskLevel,
ai.create_by AS createBy,
ai.create_time AS createTime,
ai.update_by AS updateBy,
ai.update_time AS updateTime
</sql>
<sql id="AccountInfoWhereClause">
WHERE 1 = 1
<if test="query.staffName != null and query.staffName != ''">
AND (
(ai.owner_type = 'EMPLOYEE' AND bs.name LIKE CONCAT('%', #{query.staffName}, '%'))
OR
(ai.owner_type = 'RELATION' AND bsRel.name LIKE CONCAT('%', #{query.staffName}, '%'))
)
</if>
<if test="query.ownerType != null and query.ownerType != ''">
AND ai.owner_type = #{query.ownerType}
</if>
<if test="query.bankScope != null and query.bankScope != ''">
AND ai.bank_scope = #{query.bankScope}
</if>
<if test="query.relationType != null and query.relationType != ''">
AND fr.relation_type = #{query.relationType}
</if>
<if test="query.accountName != null and query.accountName != ''">
AND ai.account_name LIKE CONCAT('%', #{query.accountName}, '%')
</if>
<if test="query.accountType != null and query.accountType != ''">
AND ai.account_type = #{query.accountType}
</if>
<if test="query.isActualControl != null">
AND ar.is_self_account = #{query.isActualControl}
</if>
<if test="query.riskLevel != null and query.riskLevel != ''">
AND ar.trans_risk_level = #{query.riskLevel}
</if>
<if test="query.status != null">
AND ai.status = #{query.status}
</if>
</sql>
<select id="selectAccountInfoPage" resultMap="CcdiAccountInfoVOResult">
SELECT
<include refid="AccountInfoSelectColumns"/>
FROM ccdi_account_info ai
LEFT JOIN ccdi_account_result ar ON ai.account_no = ar.account_no
LEFT JOIN ccdi_base_staff bs ON ai.owner_type = 'EMPLOYEE' AND ai.owner_id = bs.id_card
LEFT JOIN ccdi_staff_fmy_relation fr ON ai.owner_type = 'RELATION' AND ai.owner_id = fr.relation_cert_no
LEFT JOIN ccdi_base_staff bsRel ON fr.person_id = bsRel.id_card
<include refid="AccountInfoWhereClause"/>
ORDER BY ai.update_time DESC, ai.account_id DESC
</select>
<select id="selectAccountInfoListForExport" resultMap="CcdiAccountInfoVOResult">
SELECT
<include refid="AccountInfoSelectColumns"/>
FROM ccdi_account_info ai
LEFT JOIN ccdi_account_result ar ON ai.account_no = ar.account_no
LEFT JOIN ccdi_base_staff bs ON ai.owner_type = 'EMPLOYEE' AND ai.owner_id = bs.id_card
LEFT JOIN ccdi_staff_fmy_relation fr ON ai.owner_type = 'RELATION' AND ai.owner_id = fr.relation_cert_no
LEFT JOIN ccdi_base_staff bsRel ON fr.person_id = bsRel.id_card
<include refid="AccountInfoWhereClause"/>
ORDER BY ai.update_time DESC, ai.account_id DESC
</select>
<select id="selectAccountInfoById" resultMap="CcdiAccountInfoVOResult">
SELECT
<include refid="AccountInfoSelectColumns"/>
FROM ccdi_account_info ai
LEFT JOIN ccdi_account_result ar ON ai.account_no = ar.account_no
LEFT JOIN ccdi_base_staff bs ON ai.owner_type = 'EMPLOYEE' AND ai.owner_id = bs.id_card
LEFT JOIN ccdi_staff_fmy_relation fr ON ai.owner_type = 'RELATION' AND ai.owner_id = fr.relation_cert_no
LEFT JOIN ccdi_base_staff bsRel ON fr.person_id = bsRel.id_card
WHERE ai.account_id = #{id}
</select>
<select id="selectRelationOptionsByStaffId" resultType="com.ruoyi.info.collection.domain.vo.CcdiAccountRelationOptionVO">
SELECT
fr.id,
fr.relation_name AS relationName,
fr.relation_type AS relationType,
fr.relation_cert_no AS relationCertNo
FROM ccdi_staff_fmy_relation fr
INNER JOIN ccdi_base_staff bs ON fr.person_id = bs.id_card
WHERE bs.staff_id = #{staffId}
AND fr.is_emp_family = 1
AND fr.status = 1
ORDER BY fr.id DESC
</select>
</mapper>

View File

@@ -86,6 +86,7 @@
e.staff_id,
e.name,
e.dept_id,
e.id_card,
d.dept_name
FROM ccdi_base_staff e
LEFT JOIN sys_dept d ON e.dept_id = d.dept_id

View File

@@ -6,68 +6,76 @@
<select id="selectCreditInfoPage" resultType="com.ruoyi.info.collection.domain.vo.CreditInfoListVO">
SELECT
s.staff_id,
s.name,
s.id_card,
d.dept_name,
debt_agg.query_date,
COALESCE(debt_agg.person_name, neg.person_name) AS name,
credit_keys.person_id AS id_card,
COALESCE(debt_agg.query_date, neg.query_date) AS query_date,
IFNULL(debt_agg.debt_count, 0) AS debt_count,
IFNULL(debt_agg.debt_total_amount, 0) AS debt_total_amount,
IFNULL(neg.civil_cnt, 0) AS civil_cnt,
IFNULL(neg.enforce_cnt, 0) AS enforce_cnt,
IFNULL(neg.adm_cnt, 0) AS adm_cnt
FROM ccdi_base_staff s
LEFT JOIN sys_dept d ON s.dept_id = d.dept_id
FROM (
SELECT person_id
FROM ccdi_debts_info
GROUP BY person_id
UNION
SELECT person_id
FROM ccdi_credit_negative_info
GROUP BY person_id
) credit_keys
LEFT JOIN (
SELECT
person_id,
MAX(person_name) AS person_name,
MAX(query_date) AS query_date,
COUNT(*) AS debt_count,
SUM(debt_total_amount) AS debt_total_amount
FROM ccdi_debts_info
GROUP BY person_id
) debt_agg ON debt_agg.person_id = s.id_card
LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = s.id_card
) debt_agg ON debt_agg.person_id = credit_keys.person_id
LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = credit_keys.person_id
<where>
AND (debt_agg.person_id IS NOT NULL OR neg.person_id IS NOT NULL)
<if test="query != null and query.name != null and query.name != ''">
AND s.name LIKE CONCAT('%', #{query.name}, '%')
</if>
<if test="query != null and query.staffId != null and query.staffId != ''">
AND CAST(s.staff_id AS CHAR) = #{query.staffId}
AND COALESCE(debt_agg.person_name, neg.person_name) LIKE CONCAT('%', #{query.name}, '%')
</if>
<if test="query != null and query.idCard != null and query.idCard != ''">
AND s.id_card LIKE CONCAT('%', #{query.idCard}, '%')
AND credit_keys.person_id LIKE CONCAT('%', #{query.idCard}, '%')
</if>
</where>
ORDER BY debt_agg.query_date DESC, s.staff_id DESC
ORDER BY COALESCE(debt_agg.query_date, neg.query_date) DESC, credit_keys.person_id DESC
</select>
<select id="selectCreditInfoSummaryByPersonId" resultType="com.ruoyi.info.collection.domain.vo.CreditInfoListVO">
SELECT
s.staff_id,
s.name,
s.id_card,
d.dept_name,
debt_agg.query_date,
COALESCE(debt_agg.person_name, neg.person_name) AS name,
credit_keys.person_id AS id_card,
COALESCE(debt_agg.query_date, neg.query_date) AS query_date,
IFNULL(debt_agg.debt_count, 0) AS debt_count,
IFNULL(debt_agg.debt_total_amount, 0) AS debt_total_amount,
IFNULL(neg.civil_cnt, 0) AS civil_cnt,
IFNULL(neg.enforce_cnt, 0) AS enforce_cnt,
IFNULL(neg.adm_cnt, 0) AS adm_cnt
FROM ccdi_base_staff s
LEFT JOIN sys_dept d ON s.dept_id = d.dept_id
FROM (
SELECT person_id
FROM ccdi_debts_info
GROUP BY person_id
UNION
SELECT person_id
FROM ccdi_credit_negative_info
GROUP BY person_id
) credit_keys
LEFT JOIN (
SELECT
person_id,
MAX(person_name) AS person_name,
MAX(query_date) AS query_date,
COUNT(*) AS debt_count,
SUM(debt_total_amount) AS debt_total_amount
FROM ccdi_debts_info
GROUP BY person_id
) debt_agg ON debt_agg.person_id = s.id_card
LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = s.id_card
WHERE s.id_card = #{personId}
) debt_agg ON debt_agg.person_id = credit_keys.person_id
LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = credit_keys.person_id
WHERE credit_keys.person_id = #{personId}
LIMIT 1
</select>

View File

@@ -12,12 +12,14 @@
<result property="posCategory" column="pos_category"/>
<result property="posDesc" column="pos_desc"/>
<result property="candName" column="cand_name"/>
<result property="recruitType" column="recruit_type"/>
<result property="candEdu" column="cand_edu"/>
<result property="candId" column="cand_id"/>
<result property="candSchool" column="cand_school"/>
<result property="candMajor" column="cand_major"/>
<result property="candGrad" column="cand_grad"/>
<result property="admitStatus" column="admit_status"/>
<result property="workExperienceCount" column="work_experience_count"/>
<result property="interviewerName1" column="interviewer_name1"/>
<result property="interviewerId1" column="interviewer_id1"/>
<result property="interviewerName2" column="interviewer_name2"/>
@@ -31,44 +33,53 @@
<!-- 分页查询招聘信息列表 -->
<select id="selectRecruitmentPage" resultMap="CcdiStaffRecruitmentVOResult">
SELECT
recruit_id, recruit_name, pos_name, pos_category, pos_desc,
cand_name, cand_edu, cand_id, cand_school, cand_major, cand_grad,
admit_status, interviewer_name1, interviewer_id1, interviewer_name2, interviewer_id2,
created_by, create_time, updated_by, update_time
FROM ccdi_staff_recruitment
r.recruit_id, r.recruit_name, r.pos_name, r.pos_category, r.pos_desc,
r.cand_name, r.recruit_type, r.cand_edu, r.cand_id, r.cand_school, r.cand_major, r.cand_grad,
r.admit_status, COALESCE(w.work_experience_count, 0) AS work_experience_count,
r.interviewer_name1, r.interviewer_id1, r.interviewer_name2, r.interviewer_id2,
r.created_by, r.create_time, r.updated_by, r.update_time
FROM ccdi_staff_recruitment r
LEFT JOIN (
SELECT recruit_id, COUNT(1) AS work_experience_count
FROM ccdi_staff_recruitment_work
GROUP BY recruit_id
) w ON w.recruit_id = r.recruit_id
<where>
<if test="query.recruitName != null and query.recruitName != ''">
AND recruit_name LIKE CONCAT('%', #{query.recruitName}, '%')
AND r.recruit_name LIKE CONCAT('%', #{query.recruitName}, '%')
</if>
<if test="query.posName != null and query.posName != ''">
AND pos_name LIKE CONCAT('%', #{query.posName}, '%')
AND r.pos_name LIKE CONCAT('%', #{query.posName}, '%')
</if>
<if test="query.candName != null and query.candName != ''">
AND cand_name LIKE CONCAT('%', #{query.candName}, '%')
AND r.cand_name LIKE CONCAT('%', #{query.candName}, '%')
</if>
<if test="query.recruitType != null and query.recruitType != ''">
AND r.recruit_type = #{query.recruitType}
</if>
<if test="query.candId != null and query.candId != ''">
AND cand_id = #{query.candId}
AND r.cand_id = #{query.candId}
</if>
<if test="query.admitStatus != null and query.admitStatus != ''">
AND admit_status = #{query.admitStatus}
AND r.admit_status = #{query.admitStatus}
</if>
<if test="query.interviewerName != null and query.interviewerName != ''">
AND (interviewer_name1 LIKE CONCAT('%', #{query.interviewerName}, '%')
OR interviewer_name2 LIKE CONCAT('%', #{query.interviewerName}, '%'))
AND (r.interviewer_name1 LIKE CONCAT('%', #{query.interviewerName}, '%')
OR r.interviewer_name2 LIKE CONCAT('%', #{query.interviewerName}, '%'))
</if>
<if test="query.interviewerId != null and query.interviewerId != ''">
AND (interviewer_id1 = #{query.interviewerId}
OR interviewer_id2 = #{query.interviewerId})
AND (r.interviewer_id1 = #{query.interviewerId}
OR r.interviewer_id2 = #{query.interviewerId})
</if>
</where>
ORDER BY create_time DESC
ORDER BY r.create_time DESC
</select>
<!-- 查询招聘信息详情 -->
<select id="selectRecruitmentById" resultMap="CcdiStaffRecruitmentVOResult">
SELECT
recruit_id, recruit_name, pos_name, pos_category, pos_desc,
cand_name, cand_edu, cand_id, cand_school, cand_major, cand_grad,
cand_name, recruit_type, cand_edu, cand_id, cand_school, cand_major, cand_grad,
admit_status, interviewer_name1, interviewer_id1, interviewer_name2, interviewer_id2,
created_by, create_time, updated_by, update_time
FROM ccdi_staff_recruitment
@@ -79,13 +90,13 @@
<insert id="insertBatch">
INSERT INTO ccdi_staff_recruitment
(recruit_id, recruit_name, pos_name, pos_category, pos_desc,
cand_name, cand_edu, cand_id, cand_school, cand_major, cand_grad,
cand_name, recruit_type, cand_edu, cand_id, cand_school, cand_major, cand_grad,
admit_status, interviewer_name1, interviewer_id1, interviewer_name2, interviewer_id2,
created_by, create_time, updated_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.recruitId}, #{item.recruitName}, #{item.posName}, #{item.posCategory}, #{item.posDesc},
#{item.candName}, #{item.candEdu}, #{item.candId}, #{item.candSchool}, #{item.candMajor}, #{item.candGrad},
#{item.candName}, #{item.recruitType}, #{item.candEdu}, #{item.candId}, #{item.candSchool}, #{item.candMajor}, #{item.candGrad},
#{item.admitStatus}, #{item.interviewerName1}, #{item.interviewerId1}, #{item.interviewerName2}, #{item.interviewerId2},
#{item.createdBy}, NOW(), #{item.updatedBy}, NOW())
</foreach>
@@ -100,6 +111,7 @@
pos_category = #{item.posCategory},
pos_desc = #{item.posDesc},
cand_name = #{item.candName},
recruit_type = #{item.recruitType},
cand_edu = #{item.candEdu},
cand_id = #{item.candId},
cand_school = #{item.candSchool},

View File

@@ -34,12 +34,15 @@ class CcdiCreditInfoControllerTest {
request.addParameter("pageNum", "1");
request.addParameter("pageSize", "10");
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
when(service.selectCreditInfoPage(any(), any())).thenReturn(new Page<CreditInfoListVO>(1, 10, 0));
try {
when(service.selectCreditInfoPage(any(), any())).thenReturn(new Page<CreditInfoListVO>(1, 10, 0));
TableDataInfo result = controller.list(new CcdiCreditInfoQueryDTO());
TableDataInfo result = controller.list(new CcdiCreditInfoQueryDTO());
assertEquals(0L, result.getTotal());
RequestContextHolder.resetRequestAttributes();
assertEquals(0L, result.getTotal());
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
@Test

View File

@@ -12,13 +12,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class CcdiCreditInfoQueryMapperXmlTest {
@Test
void selectCreditInfoPage_shouldOnlyQueryMaintainedCreditInfo() throws Exception {
void selectCreditInfoPage_shouldAggregateByCreditObject() throws Exception {
Path xmlPath = Path.of("src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml");
String source = Files.readString(xmlPath, StandardCharsets.UTF_8);
assertTrue(source.contains("AND (debt_agg.person_id IS NOT NULL OR neg.person_id IS NOT NULL)"),
"征信维护列表必须默认只返回已维护征信的员工");
assertFalse(source.contains("query.maintained == '0'"),
"征信维护列表不应再支持未维护员工列表");
assertFalse(source.contains("FROM ccdi_base_staff"),
"征信维护列表不应再从员工表驱动查询");
assertFalse(source.contains("query.staffId"),
"征信维护列表不应再接收柜员号筛选");
assertFalse(source.contains("query.maintained"),
"征信维护列表不应再接收是否已维护筛选");
assertFalse(source.contains(") keys"),
"征信维护列表不应使用 MySQL 保留字作为派生表别名");
assertTrue(source.contains("person_id"),
"征信维护列表必须按征信对象证件号聚合");
}
}

View File

@@ -1,10 +1,8 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.CcdiCreditNegativeInfo;
import com.ruoyi.info.collection.domain.CcdiDebtsInfo;
import com.ruoyi.info.collection.domain.vo.CreditInfoUploadResultVO;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiCreditInfoQueryMapper;
import com.ruoyi.info.collection.mapper.CcdiCreditNegativeInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiDebtsInfoMapper;
@@ -24,7 +22,6 @@ import java.io.File;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -47,9 +44,6 @@ class CcdiCreditInfoServiceImplTest {
@Mock
private CreditInfoPayloadAssembler assembler;
@Mock
private CcdiBaseStaffMapper baseStaffMapper;
@Mock
private CcdiDebtsInfoMapper debtsInfoMapper;
@@ -60,28 +54,23 @@ class CcdiCreditInfoServiceImplTest {
private CcdiCreditInfoQueryMapper queryMapper;
@Test
void uploadHtmlFiles_shouldIsolateFailuresPerEmployee() {
MockMultipartFile successFile = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8));
MockMultipartFile failFile = new MockMultipartFile("files", "b.html", "text/html", "<html>b</html>".getBytes(StandardCharsets.UTF_8));
void uploadHtmlFiles_shouldStoreCreditObjectWithoutStaffBinding() {
MockMultipartFile file = new MockMultipartFile(
"files", "family.html", "text/html", "<html>ok</html>".getBytes(StandardCharsets.UTF_8));
when(creditParseClient.parse(anyString(), anyString(), any(File.class)))
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"))
.thenThrow(new RuntimeException("征信解析失败"));
when(baseStaffMapper.selectOne(any())).thenReturn(baseStaff("330101199001010011", "张三"));
.thenReturn(successResponse("330101199202020022", "李四", "2026-03-24"));
when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
.thenReturn(List.of(buildDebt()));
.thenReturn(List.of(buildDebt("330101199202020022")));
when(assembler.buildNegative(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
.thenReturn(buildNegative());
.thenReturn(buildNegative("330101199202020022"));
CreditInfoUploadResultVO result = service.upload(Arrays.asList(successFile, failFile));
CreditInfoUploadResultVO result = service.upload(List.of(file));
assertEquals(1, result.getSuccessCount());
assertEquals(1, result.getFailureCount());
assertEquals("征信解析失败", result.getFailures().get(0).getReason());
verify(debtsInfoMapper).deleteByPersonId("330101199001010011");
verify(negativeInfoMapper).deleteByPersonId("330101199001010011");
verify(debtsInfoMapper).insertBatch(any());
verify(negativeInfoMapper).insert(any(CcdiCreditNegativeInfo.class));
assertEquals(0, result.getFailureCount());
verify(debtsInfoMapper).deleteByPersonId("330101199202020022");
verify(negativeInfoMapper).deleteByPersonId("330101199202020022");
}
@Test
@@ -90,7 +79,6 @@ class CcdiCreditInfoServiceImplTest {
when(creditParseClient.parse(anyString(), anyString(), any(File.class)))
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"));
when(baseStaffMapper.selectOne(any())).thenReturn(baseStaff("330101199001010011", "张三"));
when(queryMapper.selectLatestQueryDate("330101199001010011"))
.thenReturn(LocalDate.parse("2026-03-05"));
@@ -116,23 +104,16 @@ class CcdiCreditInfoServiceImplTest {
return response;
}
private CcdiBaseStaff baseStaff(String idCard, String name) {
CcdiBaseStaff staff = new CcdiBaseStaff();
staff.setIdCard(idCard);
staff.setName(name);
return staff;
}
private CcdiDebtsInfo buildDebt() {
private CcdiDebtsInfo buildDebt(String personId) {
CcdiDebtsInfo debt = new CcdiDebtsInfo();
debt.setPersonId("330101199001010011");
debt.setPersonId(personId);
debt.setDebtTotalAmount(new BigDecimal("100"));
return debt;
}
private CcdiCreditNegativeInfo buildNegative() {
private CcdiCreditNegativeInfo buildNegative(String personId) {
CcdiCreditNegativeInfo info = new CcdiCreditNegativeInfo();
info.setPersonId("330101199001010011");
info.setPersonId(personId);
return info;
}
}

View File

@@ -110,6 +110,12 @@ public class GetBankStatementResponse {
/** 对手方备注 */
private String customerReference;
/** 交易对手方证件号 */
private String customerCertNo;
/** 交易对手方统一社会信用代码 */
private String customerSocialCreditCode;
// ===== 摘要和备注 =====
/** 用户交易摘要 */

View File

@@ -0,0 +1,69 @@
package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceSaveDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiEvidenceVO;
import com.ruoyi.ccdi.project.service.ICcdiEvidenceService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.SecurityUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
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.PathVariable;
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.RestController;
import java.util.List;
/**
* 项目证据Controller
*
* @author ruoyi
*/
@RestController
@RequestMapping("/ccdi/evidence")
@Tag(name = "项目证据")
public class CcdiEvidenceController extends BaseController {
@Resource
private ICcdiEvidenceService evidenceService;
/**
* 保存证据
*/
@PostMapping
@Operation(summary = "保存证据")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult saveEvidence(@Validated @RequestBody CcdiEvidenceSaveDTO dto) {
CcdiEvidenceVO vo = evidenceService.saveEvidence(dto, SecurityUtils.getUsername());
return AjaxResult.success("证据入库成功", vo);
}
/**
* 查询项目证据列表
*/
@GetMapping("/list")
@Operation(summary = "查询项目证据列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult listEvidence(CcdiEvidenceQueryDTO queryDTO) {
List<CcdiEvidenceVO> list = evidenceService.listEvidence(queryDTO);
return AjaxResult.success(list);
}
/**
* 查询证据详情
*/
@GetMapping("/{evidenceId}")
@Operation(summary = "查询证据详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getEvidence(@PathVariable Long evidenceId) {
CcdiEvidenceVO vo = evidenceService.getEvidence(evidenceId);
return AjaxResult.success(vo);
}
}

View File

@@ -6,18 +6,23 @@ import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import com.ruoyi.common.utils.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 纪检初核项目管理Controller
*
@@ -53,6 +58,17 @@ public class CcdiProjectController extends BaseController {
return AjaxResult.success("项目更新成功", vo);
}
/**
* 归档项目
*/
@PostMapping("/{projectId}/archive")
@Operation(summary = "归档项目")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult archiveProject(@PathVariable Long projectId) {
projectService.archiveProject(projectId, SecurityUtils.getUsername());
return AjaxResult.success("项目归档成功");
}
/**
* 删除项目
*/
@@ -88,6 +104,28 @@ public class CcdiProjectController extends BaseController {
return getDataTable(result.getRecords(), result.getTotal());
}
/**
* 查询历史项目列表
*/
@GetMapping("/history")
@Operation(summary = "查询历史项目列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public AjaxResult listHistoryProjects(CcdiProjectQueryDTO queryDTO) {
List<CcdiProjectHistoryListItemVO> result = projectService.listHistoryProjects(queryDTO);
return AjaxResult.success(result);
}
/**
* 从历史项目导入
*/
@PostMapping("/import")
@Operation(summary = "导入历史项目")
@PreAuthorize("@ss.hasPermi('ccdi:project:add')")
public AjaxResult importFromHistory(@Validated @RequestBody CcdiProjectImportHistoryDTO dto) {
CcdiProjectVO vo = projectService.importFromHistory(dto, SecurityUtils.getUsername());
return AjaxResult.success("项目创建成功", vo);
}
/**
* 查询项目状态统计
*/

View File

@@ -1,22 +1,36 @@
package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 结果总览控制器
*/
@@ -45,8 +59,8 @@ public class CcdiProjectOverviewController extends BaseController {
@GetMapping("/risk-people")
@Operation(summary = "查询风险人员总览")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getRiskPeople(Long projectId) {
CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(projectId);
public AjaxResult getRiskPeople(CcdiProjectRiskPeopleQueryDTO queryDTO) {
CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(queryDTO);
return AjaxResult.success(overview);
}
@@ -82,4 +96,76 @@ public class CcdiProjectOverviewController extends BaseController {
CcdiProjectRiskModelPeopleVO people = overviewService.getRiskModelPeople(queryDTO);
return AjaxResult.success(people);
}
/**
* 查询项目分析详情
*/
@GetMapping("/person-analysis/detail")
@Operation(summary = "查询项目分析详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getPersonAnalysisDetail(CcdiProjectPersonAnalysisDetailQueryDTO queryDTO) {
CcdiProjectPersonAnalysisDetailVO detail = overviewService.getPersonAnalysisDetail(queryDTO);
return AjaxResult.success(detail);
}
/**
* 查询涉疑交易明细
*/
@GetMapping("/suspicious-transactions")
@Operation(summary = "查询涉疑交易明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getSuspiciousTransactions(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
CcdiProjectSuspiciousTransactionPageVO pageVO = overviewService.getSuspiciousTransactions(queryDTO);
return AjaxResult.success(pageVO);
}
/**
* 查询项目员工负面征信
*/
@GetMapping("/employee-credit-negative")
@Operation(summary = "查询项目员工负面征信")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getEmployeeCreditNegative(CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO) {
CcdiProjectEmployeeCreditNegativePageVO pageVO = overviewService.getEmployeeCreditNegative(queryDTO);
return AjaxResult.success(pageVO);
}
/**
* 导出涉疑交易明细
*/
@PostMapping("/suspicious-transactions/export")
@Operation(summary = "导出涉疑交易明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportSuspiciousTransactions(
HttpServletResponse response,
CcdiProjectSuspiciousTransactionQueryDTO queryDTO
) {
List<CcdiProjectSuspiciousTransactionExcel> rows = overviewService.exportSuspiciousTransactions(queryDTO);
ExcelUtil<CcdiProjectSuspiciousTransactionExcel> util =
new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class);
util.exportExcel(response, rows, "涉疑交易明细");
}
/**
* 导出风险人员总览
*/
@PostMapping("/risk-people/export")
@Operation(summary = "导出风险人员总览")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskPeople(HttpServletResponse response, Long projectId) {
List<CcdiProjectRiskPeopleOverviewExcel> rows = overviewService.exportRiskPeopleOverview(projectId);
ExcelUtil<CcdiProjectRiskPeopleOverviewExcel> util =
new ExcelUtil<>(CcdiProjectRiskPeopleOverviewExcel.class);
util.exportExcel(response, rows, "风险人员总览");
}
/**
* 导出风险明细
*/
@PostMapping("/risk-details/export")
@Operation(summary = "导出风险明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
overviewService.exportRiskDetails(response, projectId);
}
}

View File

@@ -0,0 +1,129 @@
package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedPurchaseQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectFamilyAssetLiabilityListQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 项目专项核查控制器
*/
@RestController
@RequestMapping("/ccdi/project/special-check")
@Tag(name = "项目专项核查")
public class CcdiProjectSpecialCheckController extends BaseController {
@Resource
private ICcdiProjectSpecialCheckService specialCheckService;
/**
* 查询员工家庭资产负债列表
*/
@GetMapping("/family-asset-liability/list")
@Operation(summary = "查询员工家庭资产负债列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getFamilyAssetLiabilityList(@Validated CcdiProjectFamilyAssetLiabilityListQueryDTO queryDTO) {
CcdiProjectFamilyAssetLiabilityListVO result = specialCheckService.getFamilyAssetLiabilityList(queryDTO);
return AjaxResult.success(result);
}
/**
* 查询员工家庭资产负债详情
*/
@GetMapping("/family-asset-liability/detail")
@Operation(summary = "查询员工家庭资产负债详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getFamilyAssetLiabilityDetail(@Validated CcdiProjectFamilyAssetLiabilityDetailQueryDTO queryDTO) {
CcdiProjectFamilyAssetLiabilityDetailVO result = specialCheckService.getFamilyAssetLiabilityDetail(queryDTO);
return AjaxResult.success(result);
}
/**
* 查询采购拓展列表
*/
@GetMapping("/extended-query/purchase/list")
@Operation(summary = "查询采购拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedPurchaseList(@Validated CcdiProjectExtendedPurchaseQueryDTO queryDTO) {
CcdiProjectExtendedPurchaseListVO result = specialCheckService.getExtendedPurchaseList(queryDTO);
return AjaxResult.success(result);
}
/**
* 查询采购拓展详情
*/
@GetMapping("/extended-query/purchase/detail")
@Operation(summary = "查询采购拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedPurchaseDetail(@Validated CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO) {
CcdiProjectExtendedPurchaseDetailVO result = specialCheckService.getExtendedPurchaseDetail(queryDTO);
return AjaxResult.success(result);
}
/**
* 查询招聘拓展列表
*/
@GetMapping("/extended-query/recruitment/list")
@Operation(summary = "查询招聘拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedRecruitmentList(@Validated CcdiProjectExtendedRecruitmentQueryDTO queryDTO) {
CcdiProjectExtendedRecruitmentListVO result = specialCheckService.getExtendedRecruitmentList(queryDTO);
return AjaxResult.success(result);
}
/**
* 查询招聘拓展详情
*/
@GetMapping("/extended-query/recruitment/detail")
@Operation(summary = "查询招聘拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedRecruitmentDetail(@Validated CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO) {
CcdiProjectExtendedRecruitmentDetailVO result = specialCheckService.getExtendedRecruitmentDetail(queryDTO);
return AjaxResult.success(result);
}
/**
* 查询调动拓展列表
*/
@GetMapping("/extended-query/transfer/list")
@Operation(summary = "查询调动拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedTransferList(@Validated CcdiProjectExtendedTransferQueryDTO queryDTO) {
CcdiProjectExtendedTransferListVO result = specialCheckService.getExtendedTransferList(queryDTO);
return AjaxResult.success(result);
}
/**
* 查询调动拓展详情
*/
@GetMapping("/extended-query/transfer/detail")
@Operation(summary = "查询调动拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedTransferDetail(@Validated CcdiProjectExtendedTransferDetailQueryDTO queryDTO) {
CcdiProjectExtendedTransferDetailVO result = specialCheckService.getExtendedTransferDetail(queryDTO);
return AjaxResult.success(result);
}
}

View File

@@ -0,0 +1,21 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 项目证据查询入参
*
* @author ruoyi
*/
@Data
public class CcdiEvidenceQueryDTO {
/** 项目ID */
private Long projectId;
/** 证据类型FLOW/MODEL/ASSET */
private String evidenceType;
/** 关键词:姓名、标题、摘要、证据编号 */
private String keyword;
}

View File

@@ -0,0 +1,54 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 保存项目证据入参
*
* @author ruoyi
*/
@Data
public class CcdiEvidenceSaveDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 证据类型FLOW/MODEL/ASSET */
@NotBlank(message = "证据类型不能为空")
private String evidenceType;
/** 关联人员姓名 */
@NotBlank(message = "关联人员不能为空")
private String relatedPersonName;
/** 关联人员标识 */
private String relatedPersonId;
/** 证据标题 */
@NotBlank(message = "证据标题不能为空")
private String evidenceTitle;
/** 证据摘要 */
@NotBlank(message = "证据摘要不能为空")
private String evidenceSummary;
/** 来源类型 */
@NotBlank(message = "来源类型不能为空")
private String sourceType;
/** 来源记录ID */
private String sourceRecordId;
/** 来源页面名称 */
private String sourcePage;
/** 证据快照JSON */
private String snapshotJson;
/** 确认理由/备注 */
@NotBlank(message = "确认理由/备注不能为空")
private String confirmReason;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 项目员工负面征信查询 DTO
*/
@Data
public class CcdiProjectEmployeeCreditNegativeQueryDTO {
private Long projectId;
private Integer pageNum;
private Integer pageSize;
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 专项核查采购拓展查询详情入参
*/
@Data
public class CcdiProjectExtendedPurchaseDetailQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 采购事项ID */
@NotBlank(message = "采购事项ID不能为空")
private String purchaseId;
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 专项核查采购拓展查询列表入参
*/
@Data
public class CcdiProjectExtendedPurchaseQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 申请人姓名 */
private String applicantName;
/** 申请日期开始 */
private String applyDateStart;
/** 申请日期结束 */
private String applyDateEnd;
/** 页码 */
private Integer pageNum = 1;
/** 每页条数 */
private Integer pageSize = 10;
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 专项核查招聘拓展查询详情入参
*/
@Data
public class CcdiProjectExtendedRecruitmentDetailQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 招聘项目编号 */
@NotBlank(message = "招聘项目编号不能为空")
private String recruitId;
}

View File

@@ -0,0 +1,24 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 专项核查招聘拓展查询列表入参
*/
@Data
public class CcdiProjectExtendedRecruitmentQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 面试官姓名 */
private String interviewerName;
/** 页码 */
private Integer pageNum = 1;
/** 每页条数 */
private Integer pageSize = 10;
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 专项核查调动拓展查询详情入参
*/
@Data
public class CcdiProjectExtendedTransferDetailQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 主键ID */
@NotNull(message = "主键ID不能为空")
private Long id;
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 专项核查调动拓展查询列表入参
*/
@Data
public class CcdiProjectExtendedTransferQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 员工姓名 */
private String staffName;
/** 调动日期开始 */
private String transferDateStart;
/** 调动日期结束 */
private String transferDateEnd;
/** 页码 */
private Integer pageNum = 1;
/** 每页条数 */
private Integer pageSize = 10;
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 员工家庭资产负债详情查询DTO
*/
@Data
public class CcdiProjectFamilyAssetLiabilityDetailQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
/** 员工身份证号 */
@NotBlank(message = "员工身份证号不能为空")
private String staffIdCard;
}

View File

@@ -0,0 +1,15 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 员工家庭资产负债列表查询DTO
*/
@Data
public class CcdiProjectFamilyAssetLiabilityListQueryDTO {
/** 项目ID */
@NotNull(message = "项目ID不能为空")
private Long projectId;
}

View File

@@ -0,0 +1,36 @@
package com.ruoyi.ccdi.project.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import java.util.List;
/**
* 历史项目导入DTO
*
* @author ruoyi
*/
@Data
public class CcdiProjectImportHistoryDTO {
/** 新项目名称 */
@NotBlank(message = "项目名称不能为空")
@Length(max = 200, message = "项目名称长度不能超过200个字符")
private String projectName;
/** 项目描述 */
@Length(max = 500, message = "项目描述长度不能超过500个字符")
private String description;
/** 来源项目ID列表 */
@NotEmpty(message = "来源项目不能为空")
private List<Long> sourceProjectIds;
/** 流水起始日期 */
private String startDate;
/** 流水结束日期 */
private String endDate;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 项目分析详情查询DTO
*/
@Data
public class CcdiProjectPersonAnalysisDetailQueryDTO {
/** 项目ID */
private Long projectId;
/** 员工身份证号 */
private String staffIdCard;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 风险人员总览查询 DTO
*/
@Data
public class CcdiProjectRiskPeopleQueryDTO {
private Long projectId;
private Integer pageNum;
private Integer pageSize;
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 涉疑交易明细查询DTO
*/
@Data
public class CcdiProjectSuspiciousTransactionQueryDTO {
/** 项目ID */
private Long projectId;
/** 涉疑类型 */
private String suspiciousType;
/** 页码 */
private Integer pageNum;
/** 每页数量 */
private Integer pageSize;
}

View File

@@ -112,6 +112,12 @@ public class CcdiBankStatement implements Serializable {
/** 对手方备注 */
private String customerReference;
/** 交易对手方证件号 */
private String customerCertNo;
/** 交易对手方统一社会信用代码 */
private String customerSocialCreditCode;
// ===== 摘要和备注 =====
/** 用户交易摘要 */
@@ -199,6 +205,8 @@ public class CcdiBankStatement implements Serializable {
entity.setCustomerLeId(item.getCustomerId());
entity.setCustomerAccountName(item.getCustomerName());
entity.setBatchSequence(item.getUploadSequnceNumber());
entity.setCustomerCertNo(item.getCustomerCertNo());
entity.setCustomerSocialCreditCode(item.getCustomerSocialCreditCode());
// 5. 特殊字段处理
entity.setMetaJson(null); // 根据文档要求强制设为 null

View File

@@ -0,0 +1,84 @@
package com.ruoyi.ccdi.project.domain.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
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;
/**
* 项目证据对象 ccdi_evidence
*
* @author ruoyi
*/
@Data
@TableName("ccdi_evidence")
public class CcdiEvidence implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 证据ID */
@TableId(type = IdType.AUTO)
private Long evidenceId;
/** 项目ID */
private Long projectId;
/** 证据类型FLOW/MODEL/ASSET */
private String evidenceType;
/** 关联人员姓名 */
private String relatedPersonName;
/** 关联人员标识,优先存身份证号或员工号 */
private String relatedPersonId;
/** 证据标题 */
private String evidenceTitle;
/** 证据摘要 */
private String evidenceSummary;
/** 来源类型BANK_STATEMENT/MODEL_DETAIL/ASSET_DETAIL */
private String sourceType;
/** 来源记录ID */
private String sourceRecordId;
/** 来源页面名称 */
private String sourcePage;
/** 证据快照JSON */
private String snapshotJson;
/** 确认理由/备注 */
private String confirmReason;
/** 确认人 */
private String confirmBy;
/** 确认时间 */
private Date confirmTime;
/** 创建者 */
@TableField(fill = FieldFill.INSERT)
private String createBy;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新者 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

View File

@@ -44,6 +44,15 @@ public class CcdiFileUploadRecord implements Serializable {
/** 文件状态uploading-上传中parsing-解析中parsed_success-解析成功parsed_failed-解析失败 */
private String fileStatus;
/** 来源类型 */
private String sourceType;
/** 来源项目ID */
private Long sourceProjectId;
/** 来源项目名称 */
private String sourceProjectName;
/** 主体名称(多个用逗号分隔) */
private String enterpriseNames;

View File

@@ -0,0 +1,38 @@
package com.ruoyi.ccdi.project.domain.event;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
/**
* 历史项目导入提交事件
*/
public class CcdiProjectHistoryImportSubmittedEvent {
private final Long targetProjectId;
private final Integer targetLsfxProjectId;
private final CcdiProjectImportHistoryDTO dto;
private final String operator;
public CcdiProjectHistoryImportSubmittedEvent(Long targetProjectId, Integer targetLsfxProjectId,
CcdiProjectImportHistoryDTO dto, String operator) {
this.targetProjectId = targetProjectId;
this.targetLsfxProjectId = targetLsfxProjectId;
this.dto = dto;
this.operator = operator;
}
public Long getTargetProjectId() {
return targetProjectId;
}
public Integer getTargetLsfxProjectId() {
return targetLsfxProjectId;
}
public CcdiProjectImportHistoryDTO getDto() {
return dto;
}
public String getOperator() {
return operator;
}
}

View File

@@ -0,0 +1,40 @@
package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
import java.math.BigDecimal;
/**
* 项目员工负面征信导出对象
*/
@Data
public class CcdiProjectEmployeeCreditNegativeExcel {
@Excel(name = "员工姓名")
private String personName;
@Excel(name = "身份证号")
private String personId;
@Excel(name = "最近征信查询日期")
private String queryDate;
@Excel(name = "民事案件笔数")
private Integer civilCnt;
@Excel(name = "民事案件金额")
private BigDecimal civilLmt;
@Excel(name = "强制执行笔数")
private Integer enforceCnt;
@Excel(name = "强制执行金额")
private BigDecimal enforceLmt;
@Excel(name = "行政处罚笔数")
private Integer admCnt;
@Excel(name = "行政处罚金额")
private BigDecimal admLmt;
}

View File

@@ -0,0 +1,32 @@
package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
/**
* 风险人员总览导出对象
*/
@Data
public class CcdiProjectRiskPeopleOverviewExcel {
@Excel(name = "姓名")
private String name;
@Excel(name = "身份证号")
private String idNo;
@Excel(name = "所属部门")
private String department;
@Excel(name = "疑似违规数")
private Integer riskCount;
@Excel(name = "风险等级")
private String riskLevel;
@Excel(name = "命中模型数")
private Integer modelCount;
@Excel(name = "核心异常点")
private String riskPoint;
}

View File

@@ -0,0 +1,34 @@
package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
import java.math.BigDecimal;
/**
* 涉疑交易导出对象
*/
@Data
public class CcdiProjectSuspiciousTransactionExcel {
@Excel(name = "交易时间")
private String trxDate;
@Excel(name = "可疑人员")
private String suspiciousPersonName;
@Excel(name = "关联人")
private String relatedPersonName;
@Excel(name = "关联员工")
private String relatedStaffDisplay;
@Excel(name = "关系")
private String relationType;
@Excel(name = "摘要/交易类型")
private String summaryAndCashType;
@Excel(name = "交易金额")
private BigDecimal displayAmount;
}

View File

@@ -0,0 +1,56 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.util.Date;
/**
* 项目证据返回对象
*
* @author ruoyi
*/
@Data
public class CcdiEvidenceVO {
/** 证据ID */
private Long evidenceId;
/** 项目ID */
private Long projectId;
/** 证据类型FLOW/MODEL/ASSET */
private String evidenceType;
/** 关联人员姓名 */
private String relatedPersonName;
/** 关联人员标识 */
private String relatedPersonId;
/** 证据标题 */
private String evidenceTitle;
/** 证据摘要 */
private String evidenceSummary;
/** 来源类型 */
private String sourceType;
/** 来源记录ID */
private String sourceRecordId;
/** 来源页面名称 */
private String sourcePage;
/** 证据快照JSON */
private String snapshotJson;
/** 确认理由/备注 */
private String confirmReason;
/** 确认人 */
private String confirmBy;
/** 确认时间 */
private Date confirmTime;
}

View File

@@ -0,0 +1,29 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import lombok.Data;
/**
* 项目员工负面征信行
*/
@Data
public class CcdiProjectEmployeeCreditNegativeItemVO {
private String personName;
private String personId;
private String queryDate;
private Integer civilCnt;
private BigDecimal civilLmt;
private Integer enforceCnt;
private BigDecimal enforceLmt;
private Integer admCnt;
private BigDecimal admLmt;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
/**
* 项目员工负面征信分页结果
*/
@Data
public class CcdiProjectEmployeeCreditNegativePageVO {
private List<CcdiProjectEmployeeCreditNegativeItemVO> rows = new ArrayList<>();
private Long total = 0L;
}

View File

@@ -0,0 +1,83 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import lombok.Data;
/**
* 专项核查采购拓展查询详情
*/
@Data
public class CcdiProjectExtendedPurchaseDetailVO {
private String purchaseId;
private String purchaseCategory;
private String projectName;
private String subjectName;
private String subjectDesc;
private BigDecimal purchaseQty;
private BigDecimal budgetAmount;
private BigDecimal bidAmount;
private BigDecimal actualAmount;
private BigDecimal contractAmount;
private BigDecimal settlementAmount;
private String purchaseMethod;
private String supplierName;
private String contactPerson;
private String contactPhone;
private String supplierUscc;
private String supplierBankAccount;
private String applyDate;
private String planApproveDate;
private String announceDate;
private String bidOpenDate;
private String contractSignDate;
private String expectedDeliveryDate;
private String actualDeliveryDate;
private String acceptanceDate;
private String settlementDate;
private String applicantId;
private String applicantName;
private String applyDepartment;
private String purchaseLeaderId;
private String purchaseLeaderName;
private String purchaseDepartment;
private String createdBy;
private String createTime;
private String updatedBy;
private String updateTime;
}

View File

@@ -0,0 +1,25 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 专项核查采购拓展查询列表项
*/
@Data
public class CcdiProjectExtendedPurchaseListItemVO {
/** 采购事项ID */
private String purchaseId;
/** 项目名称 */
private String projectName;
/** 标的物名称 */
private String subjectName;
/** 申请人姓名 */
private String applicantName;
/** 申请日期 */
private String applyDate;
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 专项核查采购拓展查询列表结果
*/
@Data
public class CcdiProjectExtendedPurchaseListVO {
/** 列表数据 */
private List<CcdiProjectExtendedPurchaseListItemVO> rows;
/** 总数 */
private Long total;
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.Date;
import lombok.Data;
/**
* 专项核查招聘拓展查询详情
*/
@Data
public class CcdiProjectExtendedRecruitmentDetailVO {
private String recruitId;
private String recruitName;
private String posName;
private String posCategory;
private String posDesc;
private String candName;
private String candEdu;
private String candId;
private String candSchool;
private String candMajor;
private String candGrad;
private String admitStatus;
private String interviewerName1;
private String interviewerId1;
private String interviewerName2;
private String interviewerId2;
private String createdBy;
private Date createTime;
private String updatedBy;
private Date updateTime;
}

View File

@@ -0,0 +1,25 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 专项核查招聘拓展查询列表项
*/
@Data
public class CcdiProjectExtendedRecruitmentListItemVO {
/** 招聘项目编号 */
private String recruitId;
/** 招聘项目名称 */
private String recruitName;
/** 职位名称 */
private String posName;
/** 面试官摘要 */
private String interviewerNameSummary;
/** 录用情况 */
private String admitStatus;
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 专项核查招聘拓展查询列表结果
*/
@Data
public class CcdiProjectExtendedRecruitmentListVO {
/** 列表数据 */
private List<CcdiProjectExtendedRecruitmentListItemVO> rows;
/** 总数 */
private Long total;
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.Date;
import lombok.Data;
/**
* 专项核查调动拓展查询详情
*/
@Data
public class CcdiProjectExtendedTransferDetailVO {
private Long id;
private Long staffId;
private String staffName;
private String transferType;
private String transferSubType;
private Long deptIdBefore;
private String deptNameBefore;
private String gradeBefore;
private String positionBefore;
private String salaryLevelBefore;
private Long deptIdAfter;
private String deptNameAfter;
private String gradeAfter;
private String positionAfter;
private String salaryLevelAfter;
private Date transferDate;
private String createdBy;
private Date createTime;
private String updatedBy;
private Date updateTime;
}

View File

@@ -0,0 +1,28 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 专项核查调动拓展查询列表项
*/
@Data
public class CcdiProjectExtendedTransferListItemVO {
/** 主键ID */
private Long id;
/** 员工姓名 */
private String staffName;
/** 调动类型 */
private String transferType;
/** 调动前部门 */
private String deptNameBefore;
/** 调动后部门 */
private String deptNameAfter;
/** 调动日期 */
private String transferDate;
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 专项核查调动拓展查询列表结果
*/
@Data
public class CcdiProjectExtendedTransferListVO {
/** 列表数据 */
private List<CcdiProjectExtendedTransferListItemVO> rows;
/** 总数 */
private Long total;
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import java.util.List;
import lombok.Data;
/**
* 员工家庭资产明细VO
*/
@Data
public class CcdiProjectFamilyAssetDetailVO {
/** 本人是否缺少资产信息 */
private Boolean missingSelfAssetInfo;
/** 本人资产小计 */
private BigDecimal selfTotalAsset;
/** 配偶资产小计 */
private BigDecimal spouseTotalAsset;
/** 家庭总资产 */
private BigDecimal totalAsset;
/** 资产明细 */
private List<CcdiProjectFamilyAssetItemVO> items;
}

View File

@@ -0,0 +1,33 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
/**
* 员工家庭资产明细项VO
*/
@Data
public class CcdiProjectFamilyAssetItemVO {
/** 资产名称 */
private String assetName;
/** 资产大类 */
private String assetMainType;
/** 资产小类 */
private String assetSubType;
/** 持有人姓名 */
private String holderName;
/** 持有人证件号 */
private String holderIdCard;
/** 当前估值 */
private BigDecimal currentValue;
/** 估值日期 */
private Date valuationDate;
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 员工家庭资产负债详情VO
*/
@Data
public class CcdiProjectFamilyAssetLiabilityDetailVO {
/** 收入明细 */
private CcdiProjectFamilyIncomeDetailVO incomeDetail;
/** 资产明细 */
private CcdiProjectFamilyAssetDetailVO assetDetail;
/** 负债明细 */
private CcdiProjectFamilyDebtDetailVO debtDetail;
/** 汇总信息 */
private CcdiProjectFamilyAssetLiabilityListItemVO summary;
}

View File

@@ -0,0 +1,41 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import lombok.Data;
/**
* 员工家庭资产负债列表项VO
*/
@Data
public class CcdiProjectFamilyAssetLiabilityListItemVO {
/** 员工身份证号 */
private String staffIdCard;
/** 员工工号 */
private String staffCode;
/** 员工姓名 */
private String staffName;
/** 所属部门 */
private String deptName;
/** 家庭总年收入 */
private BigDecimal totalIncome;
/** 家庭总资产 */
private BigDecimal totalAsset;
/** 家庭总负债 */
private BigDecimal totalDebt;
/** 收入负债对比金额 */
private BigDecimal comparisonAmount;
/** 风险等级编码 */
private String riskLevelCode;
/** 风险等级名称 */
private String riskLevelName;
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 员工家庭资产负债列表VO
*/
@Data
public class CcdiProjectFamilyAssetLiabilityListVO {
/** 列表数据 */
private List<CcdiProjectFamilyAssetLiabilityListItemVO> rows;
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import java.util.List;
import lombok.Data;
/**
* 员工家庭负债明细VO
*/
@Data
public class CcdiProjectFamilyDebtDetailVO {
/** 本人是否缺少负债信息 */
private Boolean missingSelfDebtInfo;
/** 本人负债小计 */
private BigDecimal selfTotalDebt;
/** 配偶负债小计 */
private BigDecimal spouseTotalDebt;
/** 家庭总负债 */
private BigDecimal totalDebt;
/** 负债明细 */
private List<CcdiProjectFamilyDebtItemVO> items;
}

View File

@@ -0,0 +1,36 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
/**
* 员工家庭负债明细项VO
*/
@Data
public class CcdiProjectFamilyDebtItemVO {
/** 负债名称 */
private String debtName;
/** 负债大类 */
private String debtMainType;
/** 负债小类 */
private String debtSubType;
/** 债权人类型 */
private String creditorType;
/** 归属人姓名 */
private String ownerName;
/** 归属人证件号 */
private String ownerIdCard;
/** 本金余额 */
private BigDecimal principalBalance;
/** 查询日期 */
private Date queryDate;
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.math.BigDecimal;
import lombok.Data;
/**
* 员工家庭收入明细VO
*/
@Data
public class CcdiProjectFamilyIncomeDetailVO {
/** 本人年收入 */
private BigDecimal selfIncome;
/** 配偶年收入 */
private BigDecimal spouseIncome;
/** 家庭总年收入 */
private BigDecimal totalIncome;
}

View File

@@ -0,0 +1,32 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.util.Date;
/**
* 历史项目列表项VO
*
* @author ruoyi
*/
@Data
public class CcdiProjectHistoryListItemVO {
/** 项目ID */
private Long projectId;
/** 项目名称 */
private String projectName;
/** 项目描述 */
private String description;
/** 项目状态 */
private String status;
/** 是否归档 */
private Integer isArchived;
/** 创建时间 */
private Date createTime;
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 项目分析异常明细
*/
@Data
public class CcdiProjectPersonAnalysisAbnormalDetailVO {
private List<CcdiProjectPersonAnalysisAbnormalGroupVO> groups;
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 项目分析异常分组
*/
@Data
public class CcdiProjectPersonAnalysisAbnormalGroupVO {
private String groupCode;
private String groupName;
private String groupType;
private List<?> records;
}

View File

@@ -0,0 +1,24 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 项目分析人员基础信息
*/
@Data
public class CcdiProjectPersonAnalysisBasicInfoVO {
private String name;
private String idNo;
private String staffCode;
private String department;
private String phone;
private String riskLevel;
private String projectName;
}

View File

@@ -0,0 +1,14 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 项目分析详情
*/
@Data
public class CcdiProjectPersonAnalysisDetailVO {
private CcdiProjectPersonAnalysisBasicInfoVO basicInfo;
private CcdiProjectPersonAnalysisAbnormalDetailVO abnormalDetail;
}

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