Compare commits

62 Commits

Author SHA1 Message Date
wkc
fc6af5234d 修复 Redis 断连重连并更新中介导入文案 2026-04-22 09:52:32 +08:00
wkc
624b51292f 补充中介导入测试文件 2026-04-22 09:52:32 +08:00
wkc
6385778e4c 完成中介库导入改造 2026-04-22 09:52:32 +08:00
wkc
60a7906eb3 更新开发文档并调整开发环境配置 2026-04-22 09:52:32 +08:00
wkc
49118a4418 新增中介库导入实施计划 2026-04-22 09:52:32 +08:00
wkc
d2d36d75a7 补充中介库导入设计约束 2026-04-22 09:52:32 +08:00
wkc
bc2a885abf 完善中介库导入设计文档 2026-04-22 09:52:32 +08:00
wkc
018b085447 新增中介库导入改造设计文档 2026-04-22 09:52:32 +08:00
wkc
55f6eb9129 Merge pull request 'dev-ui' (#1) from dev-ui into dev
Reviewed-on: #1
2026-04-22 01:50:37 +00:00
wjj
addea20fa1 新增项目证据库一期功能 2026-04-21 16:46:47 +08:00
wjj
d4ac165723 完善员工招聘历史工作经历功能 2026-04-20 14:52:07 +08:00
wkc
2b321a8621 修复中介库 2026-04-20 11:24:18 +08:00
wkc
c278d11390 修复 2026-04-20 11:22:10 +08:00
wkc
e0629f22e5 docs: 新增中介库主从结构改造实施计划 2026-04-17 15:56:37 +08:00
wkc
03ecbbd204 docs: 新增中介库主从结构改造设计文档 2026-04-17 15:50:28 +08:00
wkc
eabd38fa58 新增实体库管理设计文档 2026-04-17 12:00:59 +08:00
wkc
03a4acb63a 新增员工党员字段 2026-04-17 11:04:52 +08:00
wkc
3286795f98 Integrate TongWeb into backend 2026-04-17 10:31:05 +08:00
wkc
4c6ca52e7e 合并账户库为单表 2026-04-17 10:18:13 +08:00
wkc
cc1a4538af Merge branch 'dev-ui' into dev 2026-04-16 14:42:01 +08:00
wjj
1bb24ab0a2 完成账户库管理功能开发与验收 2026-04-14 10:16:16 +08:00
wjj
9c22e8a3ce 新增账户库管理设计方案文档 2026-04-09 19:42:21 +08:00
wkc
5aaf6c83be Merge remote-tracking branch 'origin/dev-ui' into dev 2026-04-07 09:39:17 +08:00
wkc
cb3265e796 修复记录 2026-04-07 09:37:41 +08:00
wjj
03282c9b69 统一前端页面蓝灰换皮样式 2026-04-03 11:00:33 +08:00
wkc
8798aa9230 调整lsfx-mock默认数据库配置并更新NAS部署环境 2026-03-31 23:03:14 +08:00
wkc
2fdf5f1546 记录异常账户基线同步后端实施 2026-03-31 22:18:06 +08:00
wkc
a32be65bf1 锁定异常账户流水与账户事实一致性 2026-03-31 22:16:48 +08:00
wkc
51810a325e 新增异常账户基线写库服务 2026-03-31 22:15:41 +08:00
wkc
6b24e02ba9 接入异常账户基线同步触发点 2026-03-31 22:14:03 +08:00
wkc
d831edcaa4 补充异常账户基线同步实施计划 2026-03-31 22:11:21 +08:00
wkc
af63607069 补充异常账户基线同步设计文档 2026-03-31 22:07:05 +08:00
wkc
0abc84c571 记录异常账户人员信息前端实施 2026-03-31 21:09:14 +08:00
wkc
7dafabf7cb 调整异常账户人员信息前端展示列 2026-03-31 21:08:04 +08:00
wkc
4dca2b2b63 补充异常账户人员前端查询状态 2026-03-31 21:07:24 +08:00
wkc
001597d5e8 Merge branch 'codex/project-detail-risk-details-abnormal-account-backend' into dev 2026-03-31 21:05:32 +08:00
wkc
4b5ac7388c 记录异常账户人员信息后端实施 2026-03-31 21:04:06 +08:00
wkc
1e0813a84c 补充风险明细异常账户统一导出 2026-03-31 21:03:13 +08:00
wkc
c8d45416cf 补充异常账户人员服务映射 2026-03-31 21:02:00 +08:00
wkc
09119a2365 补充异常账户人员查询SQL 2026-03-31 21:00:24 +08:00
wkc
5de46eabc5 修正异常账户流水返回账号覆盖 2026-03-31 20:58:44 +08:00
wkc
bcb2e39099 补充异常账户人员查询接口契约 2026-03-31 20:57:20 +08:00
wkc
09b4cfe3c4 Merge branch 'codex/lsfx-mock-server-abnormal-account-backend' into dev 2026-03-31 20:54:05 +08:00
wkc
c5a00f26ad 补充风险明细异常账户实施计划 2026-03-31 20:53:32 +08:00
wkc
d4dc66a514 完成异常账户Mock服务后端实施记录 2026-03-31 20:49:27 +08:00
wkc
2877e26fa5 接入异常账户命中流水主链路 2026-03-31 20:45:25 +08:00
wkc
1a19dcbc13 补充风险明细异常账户人员信息设计文档 2026-03-31 20:43:55 +08:00
wkc
f981dc9906 补充异常账户规则样本生成器 2026-03-31 20:42:22 +08:00
wkc
f0e2595a2b 补充异常账户命中计划与账户事实 2026-03-31 20:40:38 +08:00
wkc
37e0c231a7 补充异常账户Mock造数实施计划 2026-03-31 20:33:22 +08:00
wkc
1397f12057 补充异常账户Mock造数设计文档 2026-03-31 20:30:55 +08:00
wkc
46e476e35b 完成异常账户模型前端实施记录 2026-03-31 16:46:20 +08:00
wkc
bfac1f10d2 修正异常账户规则金额口径并补充后端验证记录 2026-03-31 16:46:05 +08:00
wkc
d01362cc72 补充异常账户规则SQL校验记录 2026-03-31 16:37:17 +08:00
wkc
2aee9ff76e 补充异常账户规则测试数据 2026-03-31 16:34:45 +08:00
wkc
5b91cee935 实现休眠账户大额启用打标规则 2026-03-31 16:32:52 +08:00
wkc
a3f49dc176 实现突然销户打标规则 2026-03-31 16:31:58 +08:00
wkc
127a59bf78 补充异常账户模型建表和规则元数据 2026-03-31 16:29:48 +08:00
wkc
988c2d3572 补充异常账户模型规则骨架 2026-03-31 16:28:37 +08:00
wkc
f4a72a6110 补充异常账户模型实施计划 2026-03-31 16:18:20 +08:00
wkc
3741ef5fe4 补充异常账户模型打标设计文档 2026-03-31 16:12:45 +08:00
wkc
8190946a87 修改后端地址 2026-03-31 10:24:06 +08:00
328 changed files with 29072 additions and 2313 deletions

BIN
.DS_Store vendored

Binary file not shown.

12
.gitignore vendored
View File

@@ -79,4 +79,14 @@ output/
logs/
.DS_Store
.DS_Store
ruoyi-ui/vue.config.js
*/src/test/
.pytest_cache/
tests/
tongweb_62318.properties

View File

@@ -1,17 +0,0 @@
{
"mcpServers": {
"mysql": {
"command": "node",
"args": [
"C:/Users/wkc/.codex/mcp-tools/mysql-server/node_modules/@fhuang/mcp-mysql-server/build/index.js"
],
"env": {
"MYSQL_DATABASE": "ccdi",
"MYSQL_HOST": "116.62.17.81",
"MYSQL_PASSWORD": "Kfcx@1234",
"MYSQL_PORT": "3306",
"MYSQL_USER": "root"
}
}
}
}

View File

@@ -1,24 +0,0 @@
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"oh-my-opencode@latest"
],
"agent": {
"Sisyphus-Junior": {
"mode": "subagent",
"model": "glm/glm-5"
},
"oracle": {
"mode": "subagent",
"model": "gmn/gpt-5.3-codex"
},
"Metis (Plan Consultant)": {
"mode": "subagent",
"model": "gmn/gpt-5.3-codex"
},
"Momus (Plan Critic)": {
"mode": "subagent",
"model": "gmn/gpt-5.3-codex"
}
}
}

View File

@@ -15,19 +15,58 @@
---
## 协作约定
## 高优先级规则
- 使用简体中文进行思考和对话
- Git 提交说明使用中文
- Git 提交前必须检查暂存区,仅允许包含本次任务相关文件
- 若暂存区存在无关文件,必须先移出暂存或与用户确认,禁止顺带提交
- 根据设计文档产出实施计划时,默认输出两份文档:
- 后端实施计划放 `docs/plans/backend/`
- 前端实施计划放 `docs/plans/frontend/`
- 前端开发直接在当前分支进行,不需要额外创建 git worktree
- Git 提交说明必须使用中文
- 忽略 `.DS_Store` 文件,不将其视为本次任务需要处理或提交的有效变更
- 仅当用户明确声明调用 `using-superpowers` 时才允许启用;未明确声明时按普通流程直接处理需求
- Git 提交前必须检查暂存区,仅允许包含本次任务相关文件;若存在无关文件,必须先移出暂存或与用户确认
- 每一次改动都需要留下实施文档,记录修改内容、影响范围与验证情况
- 功能设计同时涉及前端和后端改动时,必须分别输出后端与前端两份实施计划;若仅涉及单侧,则只输出对应实施计划
- 新增或修改设计文档、实施计划、实施记录前,必须先确认保存路径是否正确
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
- 测试结束后,自动关闭测试过程中启动的前后端进程
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`
---
## 协作约定
### 基础协作
- 前端开发直接在当前分支进行,不需要额外创建 git worktree
- 给出方案时,必须保持最短路径实现,不允许提供兼容性、补丁性或过度设计的方案
- 不允许自行扩展出用户需求之外的兜底、降级或变体方案,避免业务逻辑偏移
- 输出方案前必须完成全链路逻辑校验,确保方案逻辑正确、链路闭环
### Git 与变更管理
- Git 提交前必须检查暂存区,仅保留本次任务相关文件
- 若暂存区存在无关文件,必须先移出暂存或与用户确认,禁止顺带提交
- `.DS_Store` 默认忽略,不纳入任务变更范围
### 文档产出
- 若需求来自设计文档,默认同时沉淀后端与前端两份实施计划
- 功能设计同时涉及前端和后端改动时,实施计划分别放在 `docs/plans/backend/``docs/plans/frontend/`
- 功能修改只涉及前端或只涉及后端时,只输出对应的实施计划
- 非前后端架构项目不强制拆分两份实施计划
- 每一次改动都需要留下实施文档,实施记录优先放在 `docs/reports/implementation/`
- 每次新增或修改设计文档、实施计划、实施记录前,都要先确认保存路径是否正确
### 测试与运行
- 测试结束后,自动关闭测试过程中启动的前后端进程
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`,不要直接手工执行 `java -jar` 替代正式重启流程
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
### 数据库与编码
- 遇到 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`
- 所有业务表、系统表新增或修改时,必须显式使用 `utf8mb4` 字符集与 `utf8mb4_general_ci` 排序规则
- 禁止引入 `utf8mb4_0900_ai_ci``utf8mb4_unicode_ci` 或其他混用排序规则
- 银行流水打标相关规则与参数编码需要统一使用全大写;新增或修改 `rule_code``indicator_code``param_code` 时,禁止混用大小写风格
---
@@ -61,6 +100,9 @@ mvn clean package -DskipTests
```bash
cd ruoyi-ui
# 使用 nvm 切换到项目所需 Node 版本
nvm use
# 安装依赖
npm install --registry=https://registry.npmmirror.com
@@ -164,7 +206,10 @@ return AjaxResult.success(result);
- 非业务字段如 `create_by``create_time` 由后端自动维护
- 前端表单不要暴露通用审计字段
- 新增菜单、字典、初始化数据时,同步补充 SQL 脚本
- 执行数据库脚本或导入数据库前,需确认客户端会话字符集为 `utf8mb4`;涉及中文插入、更新、导入时默认使用 `bin/mysql_utf8_exec.sh`
- 执行数据库脚本或导入数据库前,需确认客户端会话字符集为 `utf8mb4`
- 涉及中文插入、更新、导入时默认使用 `bin/mysql_utf8_exec.sh`
- 所有系统表和业务表的表级、字符字段级排序规则统一为 `utf8mb4_general_ci`
- 新增建表 SQL、字段追加 SQL、表结构修复 SQL 必须显式声明字符集与排序规则,避免因默认排序规则漂移导致联表或条件查询报错
### 前端规范
@@ -225,15 +270,10 @@ ccdi/
### 主要业务代码分布
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/`
-`controller``domain``mapper``service``annotation``validation` 等目录
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/`
-`config``controller``domain``mapper``service`
- `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/`
-`client``config``constants``controller``domain/request``domain/response`
- `ruoyi-ui/src/views/`
- 当前包含 `ccdi``ccdiBaseStaff``ccdiProject``ccdiPurchaseTransaction``ccdiIntermediary`、亲属关系、员工调动、招聘等业务页面
- `ruoyi-ui/src/api/ccdi/`
- 放置纪检初核业务 API 封装
### 添加新后端模块时
@@ -294,6 +334,9 @@ ccdi/
- 只有历史资料或外部原始材料才放入 `assets/`
- 如果移动了文档,需同步修正文档内引用路径
- 若需求来自设计文档,默认同时沉淀后端与前端两份实施计划
- 功能设计同时涉及前端和后端改动时,必须分别输出后端与前端两份实施计划;若仅涉及前端或仅涉及后端,则只输出对应实施计划;非前后端架构项目不强制拆分双文档
- 每一次改动都需要留下实施文档,记录本次修改内容、影响范围与验证情况,实施记录优先放在 `docs/reports/implementation/`
- 每次新增或修改设计文档、实施计划、实施记录前,都要先确认保存路径是否正确
---
@@ -304,3 +347,5 @@ ccdi/
- `docker/backend``docker/frontend``docker/mock` 分别对应三类运行时镜像
- `sql/migration/` 用于增量迁移脚本,新增修复脚本优先按日期或功能命名
- 启动前后端或 Mock 服务做验证后,结束测试时要主动停止进程,避免残留占用端口
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本

BIN
assets/异常账户.xlsx Normal file

Binary file not shown.

View File

@@ -83,7 +83,7 @@ collect_pids() {
fi
fi
marker_pids=$(pgrep -f "$APP_MARKER" 2>/dev/null || true)
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
@@ -92,6 +92,15 @@ collect_pids() {
done
fi
port_pids=$(lsof -tiTCP:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true)
if [ -n "${port_pids:-}" ]; then
for pid in $port_pids; do
if is_managed_backend_pid "$pid"; then
all_pids="$all_pids $pid"
fi
done
fi
unique_pids=""
for pid in $all_pids; do
case " $unique_pids " in

View File

@@ -57,6 +57,12 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ccdi-lsfx</artifactId>
<version>3.9.1</version>
<scope>compile</scope>
</dependency>
</dependencies>

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

@@ -0,0 +1,146 @@
package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.annotation.Log;
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.enums.BusinessType;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
import com.ruoyi.info.collection.domain.vo.EnterpriseBaseInfoImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoService;
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.ArrayList;
import java.util.List;
/**
* 实体库管理 Controller
*
* @author ruoyi
* @date 2026-04-17
*/
@Tag(name = "实体库管理")
@RestController
@RequestMapping("/ccdi/enterpriseBaseInfo")
public class CcdiEnterpriseBaseInfoController extends BaseController {
@Resource
private ICcdiEnterpriseBaseInfoService enterpriseBaseInfoService;
@Resource
private ICcdiEnterpriseBaseInfoImportService enterpriseBaseInfoImportService;
@Operation(summary = "查询实体库列表")
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:list')")
@GetMapping("/list")
public TableDataInfo list(CcdiEnterpriseBaseInfoQueryDTO queryDTO) {
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiEnterpriseBaseInfoVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiEnterpriseBaseInfoVO> result = enterpriseBaseInfoService.selectEnterpriseBaseInfoPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
@Operation(summary = "获取实体库详细信息")
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:query')")
@GetMapping("/{socialCreditCode}")
public AjaxResult getInfo(@PathVariable String socialCreditCode) {
return success(enterpriseBaseInfoService.selectEnterpriseBaseInfoById(socialCreditCode));
}
@Operation(summary = "新增实体库信息")
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:add')")
@Log(title = "实体库管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiEnterpriseBaseInfoAddDTO addDTO) {
return toAjax(enterpriseBaseInfoService.insertEnterpriseBaseInfo(addDTO));
}
@Operation(summary = "修改实体库信息")
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:edit')")
@Log(title = "实体库管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiEnterpriseBaseInfoEditDTO editDTO) {
return toAjax(enterpriseBaseInfoService.updateEnterpriseBaseInfo(editDTO));
}
@Operation(summary = "删除实体库信息")
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:remove')")
@Log(title = "实体库管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{socialCreditCodes}")
public AjaxResult remove(@PathVariable String[] socialCreditCodes) {
return toAjax(enterpriseBaseInfoService.deleteEnterpriseBaseInfoByIds(socialCreditCodes));
}
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiEnterpriseBaseInfoExcel.class, "实体库管理");
}
@Operation(summary = "导入实体库信息")
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:import')")
@Log(title = "实体库管理", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file) throws Exception {
List<CcdiEnterpriseBaseInfoExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiEnterpriseBaseInfoExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
String taskId = enterpriseBaseInfoService.importEnterpriseBaseInfo(list);
ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId);
result.setStatus("PROCESSING");
result.setMessage("导入任务已提交,正在后台处理");
return AjaxResult.success("导入任务已提交,正在后台处理", result);
}
@Operation(summary = "查询导入状态")
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:import')")
@GetMapping("/importStatus/{taskId}")
public AjaxResult getImportStatus(@PathVariable String taskId) {
ImportStatusVO status = enterpriseBaseInfoImportService.getImportStatus(taskId);
return success(status);
}
@Operation(summary = "查询导入失败记录")
@PreAuthorize("@ss.hasPermi('ccdi:enterpriseBaseInfo:import')")
@GetMapping("/importFailures/{taskId}")
public TableDataInfo getImportFailures(@PathVariable String taskId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
List<EnterpriseBaseInfoImportFailureVO> failures = enterpriseBaseInfoImportService.getImportFailures(taskId);
int fromIndex = (pageNum - 1) * pageSize;
if (fromIndex >= failures.size()) {
return getDataTable(new ArrayList<>(), failures.size());
}
int toIndex = Math.min(fromIndex + pageSize, failures.size());
return getDataTable(failures.subList(fromIndex, toIndex), failures.size());
}
}

View File

@@ -138,4 +138,30 @@ public class CcdiEnumController {
}
return AjaxResult.success(options);
}
/**
* 获取实体风险等级选项
*/
@Operation(summary = "获取实体风险等级选项")
@GetMapping("/enterpriseRiskLevel")
public AjaxResult getEnterpriseRiskLevelOptions() {
List<EnumOptionVO> options = new ArrayList<>();
for (EnterpriseRiskLevel level : EnterpriseRiskLevel.values()) {
options.add(new EnumOptionVO(level.getCode(), level.getDesc()));
}
return AjaxResult.success(options);
}
/**
* 获取企业来源选项
*/
@Operation(summary = "获取企业来源选项")
@GetMapping("/enterpriseSource")
public AjaxResult getEnterpriseSourceOptions() {
List<EnumOptionVO> options = new ArrayList<>();
for (EnterpriseSource source : EnterpriseSource.values()) {
options.add(new EnumOptionVO(source.getCode(), source.getDesc()));
}
return AjaxResult.success(options);
}
}

View File

@@ -2,10 +2,10 @@ package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.*;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEntityExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.*;
import com.ruoyi.info.collection.service.ICcdiIntermediaryEntityImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
@@ -46,7 +46,7 @@ public class CcdiIntermediaryController extends BaseController {
private ICcdiIntermediaryPersonImportService personImportService;
@Resource
private ICcdiIntermediaryEntityImportService entityImportService;
private ICcdiIntermediaryEnterpriseRelationImportService enterpriseRelationImportService;
/**
* 查询中介列表
@@ -72,6 +72,26 @@ public class CcdiIntermediaryController extends BaseController {
return success(vo);
}
/**
* 查询中介亲属列表
*/
@Operation(summary = "查询中介亲属列表")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:query')")
@GetMapping("/{bizId}/relatives")
public AjaxResult getRelativeList(@PathVariable String bizId) {
return success(intermediaryService.selectIntermediaryRelativeList(bizId));
}
/**
* 查询中介亲属详情
*/
@Operation(summary = "查询中介亲属详情")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:query')")
@GetMapping("/relative/{relativeBizId}")
public AjaxResult getRelativeInfo(@PathVariable String relativeBizId) {
return success(intermediaryService.selectIntermediaryRelativeDetail(relativeBizId));
}
/**
* 查询实体中介详情
*/
@@ -105,6 +125,28 @@ public class CcdiIntermediaryController extends BaseController {
return toAjax(intermediaryService.updateIntermediaryPerson(editDTO));
}
/**
* 新增中介亲属
*/
@Operation(summary = "新增中介亲属")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:add')")
@Log(title = "中介亲属", businessType = BusinessType.INSERT)
@PostMapping("/{bizId}/relative")
public AjaxResult addRelative(@PathVariable String bizId, @Validated @RequestBody CcdiIntermediaryRelativeAddDTO addDTO) {
return toAjax(intermediaryService.insertIntermediaryRelative(bizId, addDTO));
}
/**
* 修改中介亲属
*/
@Operation(summary = "修改中介亲属")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:edit')")
@Log(title = "中介亲属", businessType = BusinessType.UPDATE)
@PutMapping("/relative")
public AjaxResult editRelative(@Validated @RequestBody CcdiIntermediaryRelativeEditDTO editDTO) {
return toAjax(intermediaryService.updateIntermediaryRelative(editDTO));
}
/**
* 新增实体中介
*/
@@ -127,6 +169,49 @@ public class CcdiIntermediaryController extends BaseController {
return toAjax(intermediaryService.updateIntermediaryEntity(editDTO));
}
/**
* 查询中介关联机构列表
*/
@Operation(summary = "查询中介关联机构列表")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:query')")
@GetMapping("/{bizId}/enterprise-relations")
public AjaxResult getEnterpriseRelationList(@PathVariable String bizId) {
return success(intermediaryService.selectIntermediaryEnterpriseRelationList(bizId));
}
/**
* 查询中介关联机构详情
*/
@Operation(summary = "查询中介关联机构详情")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:query')")
@GetMapping("/enterprise-relation/{id}")
public AjaxResult getEnterpriseRelationInfo(@PathVariable Long id) {
return success(intermediaryService.selectIntermediaryEnterpriseRelationDetail(id));
}
/**
* 新增中介关联机构
*/
@Operation(summary = "新增中介关联机构")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:add')")
@Log(title = "中介关联机构", businessType = BusinessType.INSERT)
@PostMapping("/{bizId}/enterprise-relation")
public AjaxResult addEnterpriseRelation(@PathVariable String bizId,
@Validated @RequestBody CcdiIntermediaryEnterpriseRelationAddDTO addDTO) {
return toAjax(intermediaryService.insertIntermediaryEnterpriseRelation(bizId, addDTO));
}
/**
* 修改中介关联机构
*/
@Operation(summary = "修改中介关联机构")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:edit')")
@Log(title = "中介关联机构", businessType = BusinessType.UPDATE)
@PutMapping("/enterprise-relation")
public AjaxResult editEnterpriseRelation(@Validated @RequestBody CcdiIntermediaryEnterpriseRelationEditDTO editDTO) {
return toAjax(intermediaryService.updateIntermediaryEnterpriseRelation(editDTO));
}
/**
* 删除中介
*/
@@ -138,6 +223,28 @@ public class CcdiIntermediaryController extends BaseController {
return toAjax(intermediaryService.deleteIntermediaryByIds(ids));
}
/**
* 删除中介亲属
*/
@Operation(summary = "删除中介亲属")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:remove')")
@Log(title = "中介亲属", businessType = BusinessType.DELETE)
@DeleteMapping("/relative/{relativeBizId}")
public AjaxResult removeRelative(@PathVariable String relativeBizId) {
return toAjax(intermediaryService.deleteIntermediaryRelative(relativeBizId));
}
/**
* 删除中介关联机构
*/
@Operation(summary = "删除中介关联机构")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:remove')")
@Log(title = "中介关联机构", businessType = BusinessType.DELETE)
@DeleteMapping("/enterprise-relation/{id}")
public AjaxResult removeEnterpriseRelation(@PathVariable Long id) {
return toAjax(intermediaryService.deleteIntermediaryEnterpriseRelation(id));
}
/**
* 校验人员ID唯一性
*/
@@ -170,10 +277,10 @@ public class CcdiIntermediaryController extends BaseController {
/**
* 下载实体中介导入模板
*/
@Operation(summary = "下载实体中介导入模板")
@PostMapping("/importEntityTemplate")
public void importEntityTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiIntermediaryEntityExcel.class, "实体中介信息");
@Operation(summary = "下载中介实体关联关系导入模板")
@PostMapping("/importEnterpriseRelationTemplate")
public void importEnterpriseRelationTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiIntermediaryEnterpriseRelationExcel.class, "中介实体关联关系信息");
}
/**
@@ -206,20 +313,19 @@ public class CcdiIntermediaryController extends BaseController {
/**
* 导入实体中介数据(异步)
*/
@Operation(summary = "导入实体中介数据")
@Operation(summary = "导入中介实体关联关系数据")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
@Log(title = "实体中介", businessType = BusinessType.IMPORT)
@PostMapping("/importEntityData")
public AjaxResult importEntityData(MultipartFile file) throws Exception {
List<CcdiIntermediaryEntityExcel> list = EasyExcelUtil.importExcel(
file.getInputStream(), CcdiIntermediaryEntityExcel.class);
@Log(title = "中介实体关联关系", businessType = BusinessType.IMPORT)
@PostMapping("/importEnterpriseRelationData")
public AjaxResult importEnterpriseRelationData(MultipartFile file) throws Exception {
List<CcdiIntermediaryEnterpriseRelationExcel> list = EasyExcelUtil.importExcel(
file.getInputStream(), CcdiIntermediaryEnterpriseRelationExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
// 提交异步任务
String taskId = intermediaryService.importIntermediaryEntity(list);
String taskId = intermediaryService.importIntermediaryEnterpriseRelation(list);
// 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO();
@@ -276,12 +382,12 @@ public class CcdiIntermediaryController extends BaseController {
/**
* 查询实体中介导入状态
*/
@Operation(summary = "查询实体中介导入状态")
@Operation(summary = "查询中介实体关联关系导入状态")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
@GetMapping("/importEntityStatus/{taskId}")
public AjaxResult getEntityImportStatus(@PathVariable String taskId) {
@GetMapping("/importEnterpriseRelationStatus/{taskId}")
public AjaxResult getEnterpriseRelationImportStatus(@PathVariable String taskId) {
try {
ImportStatusVO status = entityImportService.getImportStatus(taskId);
ImportStatusVO status = enterpriseRelationImportService.getImportStatus(taskId);
return success(status);
} catch (Exception e) {
return error(e.getMessage());
@@ -289,18 +395,18 @@ public class CcdiIntermediaryController extends BaseController {
}
/**
* 查询实体中介导入失败记录
* 查询中介实体关联关系导入失败记录
*/
@Operation(summary = "查询实体中介导入失败记录")
@Operation(summary = "查询中介实体关联关系导入失败记录")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
@GetMapping("/importEntityFailures/{taskId}")
public TableDataInfo getEntityImportFailures(
@GetMapping("/importEnterpriseRelationFailures/{taskId}")
public TableDataInfo getEnterpriseRelationImportFailures(
@PathVariable String taskId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
List<IntermediaryEntityImportFailureVO> failures =
entityImportService.getImportFailures(taskId);
List<IntermediaryEnterpriseRelationImportFailureVO> failures =
enterpriseRelationImportService.getImportFailures(taskId);
// 手动分页
int fromIndex = (pageNum - 1) * pageSize;
@@ -311,7 +417,7 @@ public class CcdiIntermediaryController extends BaseController {
return getDataTable(new ArrayList<>(), failures.size());
}
List<IntermediaryEntityImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
List<IntermediaryEnterpriseRelationImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
return getDataTable(pageData, failures.size());
}

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,120 @@
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_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;
/** 是否实控账户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;
/** 状态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

@@ -43,6 +43,10 @@ public class CcdiBaseStaff implements Serializable {
/** 入职时间 */
private Date hireDate;
/** 是否党员0-否 1-是 */
@TableField("is_party_member")
private Integer partyMember;
/** 状态 */
private String status;

View File

@@ -63,7 +63,7 @@ public class CcdiBizIntermediary implements Serializable {
/** 职位 */
private String position;
/** 关联人员ID */
/** 关联中介本人证件号码 */
private String relatedNumId;
/** 数据来源MANUAL:手动录入, SYSTEM:系统同步, IMPORT:批量导入, API:接口获取 */

View File

@@ -0,0 +1,46 @@
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_intermediary_enterprise_relation
*/
@Data
@TableName("ccdi_intermediary_enterprise_relation")
public class CcdiIntermediaryEnterpriseRelation implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
private String intermediaryBizId;
private String socialCreditCode;
private String relationPersonPost;
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

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

@@ -53,6 +53,10 @@ public class CcdiBaseStaffAddDTO implements Serializable {
/** 入职时间 */
private Date hireDate;
/** 是否党员0-否 1-是 */
@NotNull(message = "是否党员不能为空")
private Integer partyMember;
/** 状态 */
@NotBlank(message = "状态不能为空")
private String status;

View File

@@ -52,6 +52,10 @@ public class CcdiBaseStaffEditDTO implements Serializable {
/** 入职时间 */
private Date hireDate;
/** 是否党员0-否 1-是 */
@NotNull(message = "是否党员不能为空")
private Integer partyMember;
/** 状态 */
private String status;

View File

@@ -0,0 +1,107 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 实体库管理新增 DTO
*
* @author ruoyi
* @date 2026-04-17
*/
@Data
@Schema(description = "实体库管理新增DTO")
public class CcdiEnterpriseBaseInfoAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "统一社会信用代码")
@NotBlank(message = "统一社会信用代码不能为空")
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "统一社会信用代码格式不正确")
private String socialCreditCode;
@Schema(description = "企业名称")
@NotBlank(message = "企业名称不能为空")
@Size(max = 200, message = "企业名称长度不能超过200个字符")
private String enterpriseName;
@Schema(description = "企业类型")
@Size(max = 50, message = "企业类型长度不能超过50个字符")
private String enterpriseType;
@Schema(description = "企业性质")
@Size(max = 50, message = "企业性质长度不能超过50个字符")
private String enterpriseNature;
@Schema(description = "行业分类")
@Size(max = 100, message = "行业分类长度不能超过100个字符")
private String industryClass;
@Schema(description = "所属行业")
@Size(max = 100, message = "所属行业长度不能超过100个字符")
private String industryName;
@Schema(description = "成立日期")
private Date establishDate;
@Schema(description = "注册地址")
@Size(max = 500, message = "注册地址长度不能超过500个字符")
private String registerAddress;
@Schema(description = "法定代表人")
@Size(max = 100, message = "法定代表人长度不能超过100个字符")
private String legalRepresentative;
@Schema(description = "法定代表人证件类型")
@Size(max = 50, message = "法定代表人证件类型长度不能超过50个字符")
private String legalCertType;
@Schema(description = "法定代表人证件号码")
@Size(max = 50, message = "法定代表人证件号码长度不能超过50个字符")
private String legalCertNo;
@Schema(description = "股东1")
@Size(max = 100, message = "股东1长度不能超过100个字符")
private String shareholder1;
@Schema(description = "股东2")
@Size(max = 100, message = "股东2长度不能超过100个字符")
private String shareholder2;
@Schema(description = "股东3")
@Size(max = 100, message = "股东3长度不能超过100个字符")
private String shareholder3;
@Schema(description = "股东4")
@Size(max = 100, message = "股东4长度不能超过100个字符")
private String shareholder4;
@Schema(description = "股东5")
@Size(max = 100, message = "股东5长度不能超过100个字符")
private String shareholder5;
@Schema(description = "经营状态")
@NotBlank(message = "经营状态不能为空")
@Size(max = 50, message = "经营状态长度不能超过50个字符")
private String status;
@Schema(description = "风险等级")
@NotBlank(message = "风险等级不能为空")
private String riskLevel;
@Schema(description = "企业来源")
@NotBlank(message = "企业来源不能为空")
private String entSource;
@Schema(description = "数据来源")
@NotBlank(message = "数据来源不能为空")
private String dataSource;
}

View File

@@ -0,0 +1,107 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 实体库管理编辑 DTO
*
* @author ruoyi
* @date 2026-04-17
*/
@Data
@Schema(description = "实体库管理编辑DTO")
public class CcdiEnterpriseBaseInfoEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "统一社会信用代码")
@NotBlank(message = "统一社会信用代码不能为空")
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "统一社会信用代码格式不正确")
private String socialCreditCode;
@Schema(description = "企业名称")
@NotBlank(message = "企业名称不能为空")
@Size(max = 200, message = "企业名称长度不能超过200个字符")
private String enterpriseName;
@Schema(description = "企业类型")
@Size(max = 50, message = "企业类型长度不能超过50个字符")
private String enterpriseType;
@Schema(description = "企业性质")
@Size(max = 50, message = "企业性质长度不能超过50个字符")
private String enterpriseNature;
@Schema(description = "行业分类")
@Size(max = 100, message = "行业分类长度不能超过100个字符")
private String industryClass;
@Schema(description = "所属行业")
@Size(max = 100, message = "所属行业长度不能超过100个字符")
private String industryName;
@Schema(description = "成立日期")
private Date establishDate;
@Schema(description = "注册地址")
@Size(max = 500, message = "注册地址长度不能超过500个字符")
private String registerAddress;
@Schema(description = "法定代表人")
@Size(max = 100, message = "法定代表人长度不能超过100个字符")
private String legalRepresentative;
@Schema(description = "法定代表人证件类型")
@Size(max = 50, message = "法定代表人证件类型长度不能超过50个字符")
private String legalCertType;
@Schema(description = "法定代表人证件号码")
@Size(max = 50, message = "法定代表人证件号码长度不能超过50个字符")
private String legalCertNo;
@Schema(description = "股东1")
@Size(max = 100, message = "股东1长度不能超过100个字符")
private String shareholder1;
@Schema(description = "股东2")
@Size(max = 100, message = "股东2长度不能超过100个字符")
private String shareholder2;
@Schema(description = "股东3")
@Size(max = 100, message = "股东3长度不能超过100个字符")
private String shareholder3;
@Schema(description = "股东4")
@Size(max = 100, message = "股东4长度不能超过100个字符")
private String shareholder4;
@Schema(description = "股东5")
@Size(max = 100, message = "股东5长度不能超过100个字符")
private String shareholder5;
@Schema(description = "经营状态")
@NotBlank(message = "经营状态不能为空")
@Size(max = 50, message = "经营状态长度不能超过50个字符")
private String status;
@Schema(description = "风险等级")
@NotBlank(message = "风险等级不能为空")
private String riskLevel;
@Schema(description = "企业来源")
@NotBlank(message = "企业来源不能为空")
private String entSource;
@Schema(description = "数据来源")
@NotBlank(message = "数据来源不能为空")
private String dataSource;
}

View File

@@ -0,0 +1,45 @@
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-17
*/
@Data
@Schema(description = "实体库管理查询DTO")
public class CcdiEnterpriseBaseInfoQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "企业名称")
private String enterpriseName;
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
@Schema(description = "企业类型")
private String enterpriseType;
@Schema(description = "企业性质")
private String enterpriseNature;
@Schema(description = "行业分类")
private String industryClass;
@Schema(description = "经营状态")
private String status;
@Schema(description = "风险等级")
private String riskLevel;
@Schema(description = "企业来源")
private String entSource;
}

View File

@@ -0,0 +1,33 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 中介关联机构新增DTO
*/
@Data
@Schema(description = "中介关联机构新增DTO")
public class CcdiIntermediaryEnterpriseRelationAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "统一社会信用代码")
@NotBlank(message = "统一社会信用代码不能为空")
@Size(max = 18, message = "统一社会信用代码长度不能超过18个字符")
private String socialCreditCode;
@Schema(description = "关联角色/职务")
@Size(max = 100, message = "关联角色/职务长度不能超过100个字符")
private String relationPersonPost;
@Schema(description = "备注")
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
}

View File

@@ -0,0 +1,38 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
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;
/**
* 中介关联机构编辑DTO
*/
@Data
@Schema(description = "中介关联机构编辑DTO")
public class CcdiIntermediaryEnterpriseRelationEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
@NotNull(message = "主键ID不能为空")
private Long id;
@Schema(description = "统一社会信用代码")
@NotBlank(message = "统一社会信用代码不能为空")
@Size(max = 18, message = "统一社会信用代码长度不能超过18个字符")
private String socialCreditCode;
@Schema(description = "关联角色/职务")
@Size(max = 100, message = "关联角色/职务长度不能超过100个字符")
private String relationPersonPost;
@Schema(description = "备注")
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
}

View File

@@ -67,8 +67,8 @@ public class CcdiIntermediaryPersonAddDTO implements Serializable {
@Size(max = 100, message = "职位长度不能超过100个字符")
private String position;
@Schema(description = "关联人员ID")
@Size(max = 50, message = "关联人员ID长度不能超过50个字符")
@Schema(description = "关联中介本人证件号码")
@Size(max = 50, message = "关联中介本人证件号码长度不能超过50个字符")
private String relatedNumId;
@Schema(description = "关联关系")

View File

@@ -70,8 +70,8 @@ public class CcdiIntermediaryPersonEditDTO implements Serializable {
@Size(max = 100, message = "职位长度不能超过100个字符")
private String position;
@Schema(description = "关联人员ID")
@Size(max = 50, message = "关联人员ID长度不能超过50个字符")
@Schema(description = "关联中介本人证件号码")
@Size(max = 50, message = "关联中介本人证件号码长度不能超过50个字符")
private String relatedNumId;
@Schema(description = "关联关系")

View File

@@ -19,12 +19,15 @@ public class CcdiIntermediaryQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "姓名/机构名称")
@Schema(description = "名称")
private String name;
@Schema(description = "证件号/统一社会信用代码")
@Schema(description = "证件号")
private String certificateNo;
@Schema(description = "中介类型(1=个人, 2=实体)")
private String intermediaryType;
@Schema(description = "记录类型(INTERMEDIARY/RELATIVE/ENTERPRISE_RELATION)")
private String recordType;
@Schema(description = "关联中介信息(姓名或证件号)")
private String relatedIntermediaryKeyword;
}

View File

@@ -0,0 +1,72 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 中介亲属新增DTO
*/
@Data
@Schema(description = "中介亲属新增DTO")
public class CcdiIntermediaryRelativeAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "姓名")
@NotBlank(message = "姓名不能为空")
@Size(max = 100, message = "姓名长度不能超过100个字符")
private String name;
@Schema(description = "人员类型")
private String personType;
@Schema(description = "亲属关系")
@NotBlank(message = "亲属关系不能为空")
@Size(max = 50, message = "亲属关系长度不能超过50个字符")
private String personSubType;
@Schema(description = "性别")
private String gender;
@Schema(description = "证件类型")
private String idType;
@Schema(description = "证件号码")
@NotBlank(message = "证件号码不能为空")
@Size(max = 50, message = "证件号码长度不能超过50个字符")
private String personId;
@Schema(description = "手机号码")
@Size(max = 20, message = "手机号码长度不能超过20个字符")
private String mobile;
@Schema(description = "微信号")
@Size(max = 50, message = "微信号长度不能超过50个字符")
private String wechatNo;
@Schema(description = "联系地址")
@Size(max = 200, message = "联系地址长度不能超过200个字符")
private String contactAddress;
@Schema(description = "所在公司")
@Size(max = 200, message = "所在公司长度不能超过200个字符")
private String company;
@Schema(description = "企业统一信用码")
@Size(max = 50, message = "企业统一信用码长度不能超过50个字符")
private String socialCreditCode;
@Schema(description = "职位")
@Size(max = 100, message = "职位长度不能超过100个字符")
private String position;
@Schema(description = "备注")
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
}

View File

@@ -0,0 +1,75 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 中介亲属编辑DTO
*/
@Data
@Schema(description = "中介亲属编辑DTO")
public class CcdiIntermediaryRelativeEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "人员ID")
@NotBlank(message = "人员ID不能为空")
private String bizId;
@Schema(description = "姓名")
@NotBlank(message = "姓名不能为空")
@Size(max = 100, message = "姓名长度不能超过100个字符")
private String name;
@Schema(description = "人员类型")
private String personType;
@Schema(description = "亲属关系")
@NotBlank(message = "亲属关系不能为空")
@Size(max = 50, message = "亲属关系长度不能超过50个字符")
private String personSubType;
@Schema(description = "性别")
private String gender;
@Schema(description = "证件类型")
private String idType;
@Schema(description = "证件号码")
@Size(max = 50, message = "证件号码长度不能超过50个字符")
private String personId;
@Schema(description = "手机号码")
@Size(max = 20, message = "手机号码长度不能超过20个字符")
private String mobile;
@Schema(description = "微信号")
@Size(max = 50, message = "微信号长度不能超过50个字符")
private String wechatNo;
@Schema(description = "联系地址")
@Size(max = 200, message = "联系地址长度不能超过200个字符")
private String contactAddress;
@Schema(description = "所在公司")
@Size(max = 200, message = "所在公司长度不能超过200个字符")
private String company;
@Schema(description = "企业统一信用码")
@Size(max = 50, message = "企业统一信用码长度不能超过50个字符")
private String socialCreditCode;
@Schema(description = "职位")
@Size(max = 100, message = "职位长度不能超过100个字符")
private String position;
@Schema(description = "备注")
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
}

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

@@ -63,8 +63,15 @@ public class CcdiBaseStaffExcel implements Serializable {
@ColumnWidth(15)
private Date hireDate;
/** 是否党员 */
@ExcelProperty(value = "是否党员", index = 7)
@ColumnWidth(12)
@DictDropdown(dictType = "ccdi_yes_no_flag")
@Required
private Integer partyMember;
/** 状态 */
@ExcelProperty(value = "状态", index = 7)
@ExcelProperty(value = "状态", index = 8)
@ColumnWidth(10)
@DictDropdown(dictType = "ccdi_employee_status")
@Required

View File

@@ -0,0 +1,106 @@
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.DictDropdown;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 实体库管理 Excel 导入模板对象
*
* @author ruoyi
* @date 2026-04-17
*/
@Data
public class CcdiEnterpriseBaseInfoExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "统一社会信用代码*", index = 0)
@ColumnWidth(24)
private String socialCreditCode;
@ExcelProperty(value = "企业名称*", index = 1)
@ColumnWidth(30)
private String enterpriseName;
@ExcelProperty(value = "企业类型", index = 2)
@ColumnWidth(18)
@DictDropdown(dictType = "ccdi_entity_type")
private String enterpriseType;
@ExcelProperty(value = "企业性质", index = 3)
@ColumnWidth(18)
@DictDropdown(dictType = "ccdi_enterprise_nature")
private String enterpriseNature;
@ExcelProperty(value = "行业分类", index = 4)
@ColumnWidth(18)
private String industryClass;
@ExcelProperty(value = "所属行业", index = 5)
@ColumnWidth(18)
private String industryName;
@ExcelProperty(value = "成立日期", index = 6)
@ColumnWidth(16)
private Date establishDate;
@ExcelProperty(value = "注册地址", index = 7)
@ColumnWidth(36)
private String registerAddress;
@ExcelProperty(value = "法定代表人", index = 8)
@ColumnWidth(18)
private String legalRepresentative;
@ExcelProperty(value = "法定代表人证件类型", index = 9)
@ColumnWidth(22)
@DictDropdown(dictType = "ccdi_certificate_type")
private String legalCertType;
@ExcelProperty(value = "法定代表人证件号码", index = 10)
@ColumnWidth(24)
private String legalCertNo;
@ExcelProperty(value = "股东1", index = 11)
@ColumnWidth(18)
private String shareholder1;
@ExcelProperty(value = "股东2", index = 12)
@ColumnWidth(18)
private String shareholder2;
@ExcelProperty(value = "股东3", index = 13)
@ColumnWidth(18)
private String shareholder3;
@ExcelProperty(value = "股东4", index = 14)
@ColumnWidth(18)
private String shareholder4;
@ExcelProperty(value = "股东5", index = 15)
@ColumnWidth(18)
private String shareholder5;
@ExcelProperty(value = "经营状态*", index = 16)
@ColumnWidth(16)
private String status;
@ExcelProperty(value = "风险等级*", index = 17)
@ColumnWidth(16)
private String riskLevel;
@ExcelProperty(value = "企业来源*", index = 18)
@ColumnWidth(18)
private String entSource;
@ExcelProperty(value = "数据来源*", index = 19)
@ColumnWidth(18)
private String dataSource;
}

View File

@@ -0,0 +1,38 @@
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;
/**
* 中介实体关联关系导入对象
*/
@Data
public class CcdiIntermediaryEnterpriseRelationExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 中介本人证件号码 */
@ExcelProperty(value = "中介本人证件号码*", index = 0)
@ColumnWidth(24)
private String ownerPersonId;
/** 统一社会信用代码 */
@ExcelProperty(value = "统一社会信用代码*", index = 1)
@ColumnWidth(24)
private String socialCreditCode;
/** 关联人职务 */
@ExcelProperty(value = "关联人职务", index = 2)
@ColumnWidth(20)
private String relationPersonPost;
/** 备注 */
@ExcelProperty(value = "备注", index = 3)
@ColumnWidth(30)
private String remark;
}

View File

@@ -34,6 +34,7 @@ public class CcdiIntermediaryPersonExcel implements Serializable {
/** 人员子类型 */
@ExcelProperty(value = "人员子类型", index = 2)
@ColumnWidth(15)
@DictDropdown(dictType = "ccdi_person_sub_type")
private String personSubType;
/** 性别 */
@@ -83,19 +84,13 @@ public class CcdiIntermediaryPersonExcel implements Serializable {
@ColumnWidth(15)
private String position;
/** 关联人员ID */
@ExcelProperty(value = "关联人员ID", index = 12)
@ColumnWidth(15)
/** 关联中介本人证件号码 */
@ExcelProperty(value = "关联中介本人证件号码", index = 12)
@ColumnWidth(24)
private String relatedNumId;
/** 关系类型 */
@ExcelProperty(value = "关系类型", index = 13)
@ColumnWidth(15)
@DictDropdown(dictType = "ccdi_relation_type")
private String relationType;
/** 备注 */
@ExcelProperty(value = "备注", index = 14)
@ExcelProperty(value = "备注", index = 13)
@ColumnWidth(30)
private String remark;
}

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

@@ -44,6 +44,9 @@ public class CcdiBaseStaffVO implements Serializable {
/** 入职时间 */
private Date hireDate;
/** 是否党员0-否 1-是 */
private Integer partyMember;
/** 状态 */
private String status;

View File

@@ -0,0 +1,85 @@
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;
import java.util.Date;
/**
* 实体库管理 VO
*
* @author ruoyi
* @date 2026-04-17
*/
@Data
@Schema(description = "实体库管理VO")
public class CcdiEnterpriseBaseInfoVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
@Schema(description = "企业名称")
private String enterpriseName;
@Schema(description = "企业类型")
private String enterpriseType;
@Schema(description = "企业性质")
private String enterpriseNature;
@Schema(description = "行业分类")
private String industryClass;
@Schema(description = "所属行业")
private String industryName;
@Schema(description = "成立日期")
private Date establishDate;
@Schema(description = "注册地址")
private String registerAddress;
@Schema(description = "法定代表人")
private String legalRepresentative;
@Schema(description = "法定代表人证件类型")
private String legalCertType;
@Schema(description = "法定代表人证件号码")
private String legalCertNo;
@Schema(description = "股东1")
private String shareholder1;
@Schema(description = "股东2")
private String shareholder2;
@Schema(description = "股东3")
private String shareholder3;
@Schema(description = "股东4")
private String shareholder4;
@Schema(description = "股东5")
private String shareholder5;
@Schema(description = "经营状态")
private String status;
@Schema(description = "风险等级")
private String riskLevel;
@Schema(description = "企业来源")
private String entSource;
@Schema(description = "数据来源")
private String dataSource;
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -0,0 +1,48 @@
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.util.Date;
/**
* 中介关联机构VO
*/
@Data
@Schema(description = "中介关联机构VO")
public class CcdiIntermediaryEnterpriseRelationVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
private Long id;
@Schema(description = "所属中介ID")
private String intermediaryBizId;
@Schema(description = "所属中介姓名")
private String intermediaryName;
@Schema(description = "所属中介证件号")
private String intermediaryPersonId;
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
@Schema(description = "机构名称")
private String enterpriseName;
@Schema(description = "关联角色/职务")
private String relationPersonPost;
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -63,7 +63,7 @@ public class CcdiIntermediaryPersonDetailVO implements Serializable {
@Schema(description = "职位")
private String position;
@Schema(description = "关联人员ID")
@Schema(description = "关联中介本人证件号码")
private String relatedNumId;
@Schema(description = "关联关系")

View File

@@ -0,0 +1,69 @@
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.util.Date;
/**
* 中介亲属VO
*/
@Data
@Schema(description = "中介亲属VO")
public class CcdiIntermediaryRelativeVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "人员ID")
private String bizId;
@Schema(description = "关联中介本人证件号码")
private String relatedNumId;
@Schema(description = "姓名")
private String name;
@Schema(description = "人员类型")
private String personType;
@Schema(description = "亲属关系")
private String personSubType;
@Schema(description = "性别")
private String gender;
@Schema(description = "证件类型")
private String idType;
@Schema(description = "证件号码")
private String personId;
@Schema(description = "手机号码")
private String mobile;
@Schema(description = "微信号")
private String wechatNo;
@Schema(description = "联系地址")
private String contactAddress;
@Schema(description = "所在公司")
private String company;
@Schema(description = "企业统一信用码")
private String socialCreditCode;
@Schema(description = "职位")
private String position;
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}

View File

@@ -21,32 +21,25 @@ public class CcdiIntermediaryVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "ID")
private String id;
@Schema(description = "记录类型")
private String recordType;
@Schema(description = "姓名/机构名称")
@Schema(description = "记录ID")
private String recordId;
@Schema(description = "名称")
private String name;
@Schema(description = "证件号/统一社会信用代码")
@Schema(description = "证件号")
private String certificateNo;
@Schema(description = "中介类型(1=个人, 2=实体)")
private String intermediaryType;
@Schema(description = "关联中介姓名")
private String relatedIntermediaryName;
@Schema(description = "人员类型")
private String personType;
@Schema(description = "公司")
private String company;
@Schema(description = "数据来源")
private String dataSource;
@Schema(description = "关联关系")
private String relationText;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Schema(description = "修改时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

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

@@ -0,0 +1,51 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 实体库导入失败记录 VO
*
* @author ruoyi
* @date 2026-04-17
*/
@Data
@Schema(description = "实体库导入失败记录")
public class EnterpriseBaseInfoImportFailureVO {
@Schema(description = "企业名称")
private String enterpriseName;
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
@Schema(description = "企业类型")
private String enterpriseType;
@Schema(description = "企业性质")
private String enterpriseNature;
@Schema(description = "行业分类")
private String industryClass;
@Schema(description = "所属行业")
private String industryName;
@Schema(description = "法定代表人")
private String legalRepresentative;
@Schema(description = "经营状态")
private String status;
@Schema(description = "风险等级")
private String riskLevel;
@Schema(description = "企业来源")
private String entSource;
@Schema(description = "数据来源")
private String dataSource;
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -32,6 +32,9 @@ public class ImportFailureVO {
@Schema(description = "年收入")
private BigDecimal annualIncome;
@Schema(description = "是否党员0-否 1-是")
private Integer partyMember;
@Schema(description = "状态")
private String status;

View File

@@ -0,0 +1,33 @@
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;
/**
* 中介实体关联关系导入失败记录
*/
@Data
@Schema(description = "中介实体关联关系导入失败记录")
public class IntermediaryEnterpriseRelationImportFailureVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "中介本人证件号码")
private String ownerPersonId;
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
@Schema(description = "关联人职务")
private String relationPersonPost;
@Schema(description = "备注")
private String remark;
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -22,21 +22,45 @@ public class IntermediaryPersonImportFailureVO implements Serializable {
@Schema(description = "姓名")
private String name;
@Schema(description = "证件号码")
private String personId;
@Schema(description = "人员类型")
private String personType;
@Schema(description = "人员子类型")
private String personSubType;
@Schema(description = "性别")
private String gender;
@Schema(description = "证件类型")
private String idType;
@Schema(description = "证件号码")
private String personId;
@Schema(description = "手机号码")
private String mobile;
@Schema(description = "微信号")
private String wechatNo;
@Schema(description = "联系地址")
private String contactAddress;
@Schema(description = "所在公司")
private String company;
@Schema(description = "企业统一信用码")
private String socialCreditCode;
@Schema(description = "职位")
private String position;
@Schema(description = "关联中介本人证件号码")
private String relatedNumId;
@Schema(description = "备注")
private String remark;
@Schema(description = "错误信息")
private String errorMessage;
}

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,56 @@
package com.ruoyi.info.collection.enums;
/**
* 实体风险等级枚举
*
* @author ruoyi
*/
public enum EnterpriseRiskLevel {
HIGH("1", "高风险"),
MEDIUM("2", "中风险"),
LOW("3", "低风险");
private final String code;
private final String desc;
EnterpriseRiskLevel(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 (EnterpriseRiskLevel value : values()) {
if (value.code.equals(code)) {
return value.desc;
}
}
return null;
}
public static boolean contains(String code) {
for (EnterpriseRiskLevel value : values()) {
if (value.code.equals(code)) {
return true;
}
}
return false;
}
public static String resolveCode(String value) {
for (EnterpriseRiskLevel item : values()) {
if (item.code.equals(value) || item.desc.equals(value)) {
return item.code;
}
}
return null;
}
}

View File

@@ -0,0 +1,58 @@
package com.ruoyi.info.collection.enums;
/**
* 企业来源枚举
*
* @author ruoyi
*/
public enum EnterpriseSource {
GENERAL("GENERAL", "一般企业"),
EMP_RELATION("EMP_RELATION", "员工关系人"),
CREDIT_CUSTOMER("CREDIT_CUSTOMER", "信贷客户"),
INTERMEDIARY("INTERMEDIARY", "中介"),
BOTH("BOTH", "兼有");
private final String code;
private final String desc;
EnterpriseSource(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 (EnterpriseSource value : values()) {
if (value.code.equals(code)) {
return value.desc;
}
}
return null;
}
public static boolean contains(String code) {
for (EnterpriseSource value : values()) {
if (value.code.equals(code)) {
return true;
}
}
return false;
}
public static String resolveCode(String value) {
for (EnterpriseSource item : values()) {
if (item.code.equals(value) || item.desc.equals(value)) {
return item.code;
}
}
return null;
}
}

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

@@ -1,7 +1,10 @@
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.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -16,6 +19,16 @@ import java.util.List;
@Mapper
public interface CcdiEnterpriseBaseInfoMapper extends BaseMapper<CcdiEnterpriseBaseInfo> {
/**
* 分页查询实体库列表
*
* @param page 分页参数
* @param queryDTO 查询条件
* @return 分页结果
*/
Page<CcdiEnterpriseBaseInfoVO> selectEnterpriseBaseInfoPage(Page<CcdiEnterpriseBaseInfoVO> page,
@Param("queryDTO") CcdiEnterpriseBaseInfoQueryDTO queryDTO);
/**
* 批量插入实体中介
*

View File

@@ -0,0 +1,27 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 中介关联机构关系Mapper
*/
@Mapper
public interface CcdiIntermediaryEnterpriseRelationMapper extends BaseMapper<CcdiIntermediaryEnterpriseRelation> {
int insertBatch(@Param("list") List<CcdiIntermediaryEnterpriseRelation> list);
List<CcdiIntermediaryEnterpriseRelationVO> selectByIntermediaryBizId(@Param("bizId") String bizId);
CcdiIntermediaryEnterpriseRelationVO selectDetailById(@Param("id") Long id);
boolean existsByIntermediaryBizIdAndSocialCreditCode(@Param("bizId") String bizId,
@Param("socialCreditCode") String socialCreditCode);
List<String> batchExistsByCombinations(@Param("combinations") List<String> combinations);
}

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

@@ -0,0 +1,29 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
import com.ruoyi.info.collection.domain.vo.EnterpriseBaseInfoImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import java.util.List;
import java.util.Set;
/**
* 实体库管理导入 Service 接口
*
* @author ruoyi
* @date 2026-04-17
*/
public interface ICcdiEnterpriseBaseInfoImportService {
void importEnterpriseBaseInfoAsync(List<CcdiEnterpriseBaseInfoExcel> excelList, String taskId, String userName);
ImportStatusVO getImportStatus(String taskId);
List<EnterpriseBaseInfoImportFailureVO> getImportFailures(String taskId);
CcdiEnterpriseBaseInfo validateAndBuildEntity(CcdiEnterpriseBaseInfoExcel excel,
Set<String> existingCreditCodes,
Set<String> processedCreditCodes,
String userName);
}

View File

@@ -0,0 +1,34 @@
package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
import java.util.List;
/**
* 实体库管理 Service 接口
*
* @author ruoyi
* @date 2026-04-17
*/
public interface ICcdiEnterpriseBaseInfoService {
Page<CcdiEnterpriseBaseInfoVO> selectEnterpriseBaseInfoPage(Page<CcdiEnterpriseBaseInfoVO> page,
CcdiEnterpriseBaseInfoQueryDTO queryDTO);
CcdiEnterpriseBaseInfoVO selectEnterpriseBaseInfoById(String socialCreditCode);
int insertEnterpriseBaseInfo(CcdiEnterpriseBaseInfoAddDTO addDTO);
int updateEnterpriseBaseInfo(CcdiEnterpriseBaseInfoEditDTO editDTO);
int deleteEnterpriseBaseInfoByIds(String[] socialCreditCodes);
List<CcdiEnterpriseBaseInfoExcel> selectEnterpriseBaseInfoListForExport(CcdiEnterpriseBaseInfoQueryDTO queryDTO);
String importEnterpriseBaseInfo(List<CcdiEnterpriseBaseInfoExcel> excelList);
}

View File

@@ -0,0 +1,38 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO;
import java.util.List;
/**
* 中介实体关联关系异步导入服务接口
*/
public interface ICcdiIntermediaryEnterpriseRelationImportService {
/**
* 异步导入中介实体关联关系
*
* @param excelList Excel数据
* @param taskId 任务ID
* @param userName 当前用户名
*/
void importAsync(List<CcdiIntermediaryEnterpriseRelationExcel> excelList, String taskId, String userName);
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态
*/
ImportStatusVO getImportStatus(String taskId);
/**
* 查询导入失败记录
*
* @param taskId 任务ID
* @return 失败记录
*/
List<IntermediaryEnterpriseRelationImportFailureVO> getImportFailures(String taskId);
}

View File

@@ -7,7 +7,7 @@ import com.ruoyi.info.collection.domain.vo.IntermediaryPersonImportFailureVO;
import java.util.List;
/**
* 个人中介异步导入Service接口
* 中介信息异步导入Service接口
*
* @author ruoyi
* @date 2026-02-06
@@ -15,7 +15,7 @@ import java.util.List;
public interface ICcdiIntermediaryPersonImportService {
/**
* 异步导入个人中介数据
* 异步导入中介信息
*
* @param excelList Excel数据列表
* @param taskId 任务ID

View File

@@ -2,10 +2,13 @@ package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.*;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEntityExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEntityDetailVO;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryPersonDetailVO;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryRelativeVO;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryVO;
import java.util.List;
@@ -35,6 +38,22 @@ public interface ICcdiIntermediaryService {
*/
CcdiIntermediaryPersonDetailVO selectIntermediaryPersonDetail(String bizId);
/**
* 查询中介亲属列表
*
* @param bizId 中介本人ID
* @return 亲属列表
*/
List<CcdiIntermediaryRelativeVO> selectIntermediaryRelativeList(String bizId);
/**
* 查询中介亲属详情
*
* @param relativeBizId 亲属ID
* @return 亲属详情
*/
CcdiIntermediaryRelativeVO selectIntermediaryRelativeDetail(String relativeBizId);
/**
* 查询实体中介详情
*
@@ -59,6 +78,31 @@ public interface ICcdiIntermediaryService {
*/
int updateIntermediaryPerson(CcdiIntermediaryPersonEditDTO editDTO);
/**
* 新增中介亲属
*
* @param bizId 中介本人ID
* @param addDTO 新增DTO
* @return 结果
*/
int insertIntermediaryRelative(String bizId, CcdiIntermediaryRelativeAddDTO addDTO);
/**
* 修改中介亲属
*
* @param editDTO 编辑DTO
* @return 结果
*/
int updateIntermediaryRelative(CcdiIntermediaryRelativeEditDTO editDTO);
/**
* 删除中介亲属
*
* @param relativeBizId 亲属ID
* @return 结果
*/
int deleteIntermediaryRelative(String relativeBizId);
/**
* 新增实体中介
*
@@ -75,6 +119,47 @@ public interface ICcdiIntermediaryService {
*/
int updateIntermediaryEntity(CcdiIntermediaryEntityEditDTO editDTO);
/**
* 查询中介关联机构列表
*
* @param bizId 中介本人ID
* @return 关联机构列表
*/
List<CcdiIntermediaryEnterpriseRelationVO> selectIntermediaryEnterpriseRelationList(String bizId);
/**
* 查询中介关联机构详情
*
* @param id 主键ID
* @return 关联机构详情
*/
CcdiIntermediaryEnterpriseRelationVO selectIntermediaryEnterpriseRelationDetail(Long id);
/**
* 新增中介关联机构
*
* @param bizId 中介本人ID
* @param addDTO 新增DTO
* @return 结果
*/
int insertIntermediaryEnterpriseRelation(String bizId, CcdiIntermediaryEnterpriseRelationAddDTO addDTO);
/**
* 修改中介关联机构
*
* @param editDTO 编辑DTO
* @return 结果
*/
int updateIntermediaryEnterpriseRelation(CcdiIntermediaryEnterpriseRelationEditDTO editDTO);
/**
* 删除中介关联机构
*
* @param id 主键ID
* @return 结果
*/
int deleteIntermediaryEnterpriseRelation(Long id);
/**
* 批量删除中介
*
@@ -84,7 +169,7 @@ public interface ICcdiIntermediaryService {
int deleteIntermediaryByIds(String[] ids);
/**
* 校验人员ID唯一性
* 校验中介本人证件号码唯一性
*
* @param personId 人员ID
* @param bizId 排除的人员ID
@@ -109,6 +194,14 @@ public interface ICcdiIntermediaryService {
*/
String importIntermediaryPerson(List<CcdiIntermediaryPersonExcel> list);
/**
* 导入中介实体关联关系
*
* @param list Excel实体列表
* @return 任务ID
*/
String importIntermediaryEnterpriseRelation(List<CcdiIntermediaryEnterpriseRelationExcel> list);
/**
* 导入实体中介数据
*

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,586 @@
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.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.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 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);
prepareAnalysisFields(accountInfo);
return accountInfoMapper.insert(accountInfo);
}
@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);
prepareAnalysisFields(accountInfo);
return accountInfoMapper.updateById(accountInfo);
}
@Override
@Transactional
public int deleteAccountInfoByIds(Long[] ids) {
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 prepareAnalysisFields(CcdiAccountInfo accountInfo) {
if (!"EXTERNAL".equals(accountInfo.getBankScope())) {
clearAnalysisFields(accountInfo);
return;
}
if (accountInfo.getIsActualControl() == null) {
accountInfo.setIsActualControl(1);
}
if (accountInfo.getAvgMonthTxnCount() == null) {
accountInfo.setAvgMonthTxnCount(0);
}
if (accountInfo.getAvgMonthTxnAmount() == null) {
accountInfo.setAvgMonthTxnAmount(BigDecimal.ZERO);
}
if (StringUtils.isEmpty(accountInfo.getTxnFrequencyLevel())) {
accountInfo.setTxnFrequencyLevel("MEDIUM");
}
if (StringUtils.isEmpty(accountInfo.getTxnRiskLevel())) {
accountInfo.setTxnRiskLevel("LOW");
}
}
private void clearAnalysisFields(CcdiAccountInfo accountInfo) {
accountInfo.setIsActualControl(null);
accountInfo.setAvgMonthTxnCount(null);
accountInfo.setAvgMonthTxnAmount(null);
accountInfo.setTxnFrequencyLevel(null);
accountInfo.setDebitSingleMaxAmount(null);
accountInfo.setCreditSingleMaxAmount(null);
accountInfo.setDebitDailyMaxAmount(null);
accountInfo.setCreditDailyMaxAmount(null);
accountInfo.setTxnRiskLevel(null);
}
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

@@ -320,6 +320,9 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
if (StringUtils.isEmpty(addDTO.getPhone())) {
throw new RuntimeException("电话不能为空");
}
if (addDTO.getPartyMember() == null) {
throw new RuntimeException("是否党员不能为空");
}
if (StringUtils.isEmpty(addDTO.getStatus())) {
throw new RuntimeException("状态不能为空");
}
@@ -357,6 +360,9 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
if (!"0".equals(addDTO.getStatus()) && !"1".equals(addDTO.getStatus())) {
throw new RuntimeException("状态只能填写'在职'或'离职'");
}
if (addDTO.getPartyMember() != 0 && addDTO.getPartyMember() != 1) {
throw new RuntimeException("是否党员只能填写'0'或'1'");
}
validateAnnualIncome(addDTO.getAnnualIncome(), "年收入");
}

View File

@@ -112,7 +112,7 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
CcdiBaseStaff staff = baseStaffMapper.selectById(staffId);
CcdiBaseStaffVO vo = convertToVO(staff);
if (staff != null) {
vo.setAssetInfoList(assetInfoService.selectByFamilyId(staff.getIdCard()).stream().map(asset -> {
vo.setAssetInfoList(assetInfoService.selectByFamilyIdAndPersonId(staff.getIdCard(), staff.getIdCard()).stream().map(asset -> {
CcdiAssetInfoVO assetInfoVO = new CcdiAssetInfoVO();
BeanUtils.copyProperties(asset, assetInfoVO);
return assetInfoVO;
@@ -131,6 +131,7 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
@Transactional
public int insertBaseStaff(CcdiBaseStaffAddDTO addDTO) {
validateAnnualIncome(addDTO.getAnnualIncome(), "年收入");
validatePartyMember(addDTO.getPartyMember(), "是否党员");
// 检查员工ID唯一性
if (baseStaffMapper.selectById(addDTO.getStaffId()) != null) {
throw new RuntimeException("该员工ID已存在");
@@ -161,6 +162,7 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
@Transactional
public int updateBaseStaff(CcdiBaseStaffEditDTO editDTO) {
validateAnnualIncome(editDTO.getAnnualIncome(), "年收入");
validatePartyMember(editDTO.getPartyMember(), "是否党员");
CcdiBaseStaff existing = baseStaffMapper.selectById(editDTO.getStaffId());
if (existing == null) {
throw new RuntimeException("员工不存在");
@@ -291,4 +293,13 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
}
}
private void validatePartyMember(Integer partyMember, String fieldLabel) {
if (partyMember == null) {
throw new RuntimeException(fieldLabel + "不能为空");
}
if (partyMember != 0 && partyMember != 1) {
throw new RuntimeException(fieldLabel + "只能填写'0'或'1'");
}
}
}

View File

@@ -0,0 +1,225 @@
package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
import com.ruoyi.info.collection.domain.vo.EnterpriseBaseInfoImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.enums.DataSource;
import com.ruoyi.info.collection.enums.EnterpriseRiskLevel;
import com.ruoyi.info.collection.enums.EnterpriseSource;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 实体库管理导入 Service 实现
*
* @author ruoyi
* @date 2026-04-17
*/
@Service
@EnableAsync
public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseBaseInfoImportService {
@Resource
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
@Async
public void importEnterpriseBaseInfoAsync(List<CcdiEnterpriseBaseInfoExcel> excelList, String taskId, String userName) {
List<CcdiEnterpriseBaseInfo> successRecords = new ArrayList<>();
List<EnterpriseBaseInfoImportFailureVO> failures = new ArrayList<>();
Set<String> existingCreditCodes = getExistingCreditCodes(excelList);
Set<String> processedCreditCodes = new HashSet<>();
for (CcdiEnterpriseBaseInfoExcel excel : excelList) {
try {
CcdiEnterpriseBaseInfo entity = validateAndBuildEntity(excel, existingCreditCodes, processedCreditCodes, userName);
successRecords.add(entity);
processedCreditCodes.add(entity.getSocialCreditCode());
} catch (Exception e) {
EnterpriseBaseInfoImportFailureVO failureVO = new EnterpriseBaseInfoImportFailureVO();
BeanUtils.copyProperties(excel, failureVO);
failureVO.setErrorMessage(e.getMessage());
failures.add(failureVO);
}
}
if (!successRecords.isEmpty()) {
saveBatch(successRecords, 500);
}
if (!failures.isEmpty()) {
redisTemplate.opsForValue().set(buildFailuresKey(taskId), failures, 7, TimeUnit.DAYS);
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(successRecords.size());
result.setFailureCount(failures.size());
updateImportStatus(taskId, failures.isEmpty() ? "SUCCESS" : "PARTIAL_SUCCESS", result);
}
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = buildStatusKey(taskId);
Boolean exists = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(exists)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
@Override
public List<EnterpriseBaseInfoImportFailureVO> getImportFailures(String taskId) {
Object failuresObj = redisTemplate.opsForValue().get(buildFailuresKey(taskId));
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), EnterpriseBaseInfoImportFailureVO.class);
}
@Override
public CcdiEnterpriseBaseInfo validateAndBuildEntity(CcdiEnterpriseBaseInfoExcel excel,
Set<String> existingCreditCodes,
Set<String> processedCreditCodes,
String userName) {
if (excel == null) {
throw new RuntimeException("导入数据不能为空");
}
if (StringUtils.isEmpty(excel.getEnterpriseName())) {
throw new RuntimeException("企业名称不能为空");
}
if (StringUtils.isEmpty(excel.getSocialCreditCode())) {
throw new RuntimeException("统一社会信用代码不能为空");
}
if (!excel.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
throw new RuntimeException("统一社会信用代码格式不正确");
}
if (StringUtils.isEmpty(excel.getStatus())) {
throw new RuntimeException("经营状态不能为空");
}
String riskLevel = EnterpriseRiskLevel.resolveCode(StringUtils.trim(excel.getRiskLevel()));
if (riskLevel == null) {
throw new RuntimeException("风险等级不在允许范围内");
}
String entSource = EnterpriseSource.resolveCode(StringUtils.trim(excel.getEntSource()));
if (entSource == null) {
throw new RuntimeException("企业来源不在允许范围内");
}
String dataSource = resolveDataSourceCode(StringUtils.trim(excel.getDataSource()));
if (dataSource == null) {
throw new RuntimeException("数据来源不在允许范围内");
}
if (existingCreditCodes.contains(excel.getSocialCreditCode())) {
throw new RuntimeException(String.format("统一社会信用代码[%s]已存在,请勿重复导入", excel.getSocialCreditCode()));
}
if (processedCreditCodes.contains(excel.getSocialCreditCode())) {
throw new RuntimeException(String.format("统一社会信用代码[%s]在导入文件中重复,已跳过此条记录", excel.getSocialCreditCode()));
}
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
BeanUtils.copyProperties(excel, entity);
entity.setRiskLevel(riskLevel);
entity.setEntSource(entSource);
entity.setDataSource(dataSource);
entity.setStatus(StringUtils.trim(excel.getStatus()));
entity.setCreatedBy(userName);
entity.setUpdatedBy(userName);
return entity;
}
private Set<String> getExistingCreditCodes(List<CcdiEnterpriseBaseInfoExcel> excelList) {
List<String> creditCodes = excelList.stream()
.map(CcdiEnterpriseBaseInfoExcel::getSocialCreditCode)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toList());
if (creditCodes.isEmpty()) {
return Collections.emptySet();
}
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, creditCodes);
return enterpriseBaseInfoMapper.selectList(wrapper).stream()
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
private int saveBatch(List<CcdiEnterpriseBaseInfo> list, int batchSize) {
int total = 0;
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
total += enterpriseBaseInfoMapper.insertBatch(list.subList(i, end));
}
return total;
}
private void updateImportStatus(String taskId, String status, ImportResult result) {
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(buildStatusKey(taskId), statusData);
}
private String resolveDataSourceCode(String value) {
for (DataSource source : DataSource.values()) {
if (source.getCode().equals(value) || source.getDesc().equals(value)) {
return source.getCode();
}
}
return null;
}
private String buildStatusKey(String taskId) {
return "import:enterpriseBaseInfo:" + taskId;
}
private String buildFailuresKey(String taskId) {
return "import:enterpriseBaseInfo:" + taskId + ":failures";
}
}

View File

@@ -0,0 +1,220 @@
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.common.utils.StringUtils;
import com.ruoyi.info.collection.domain.CcdiCustEnterpriseRelation;
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
import com.ruoyi.info.collection.enums.DataSource;
import com.ruoyi.info.collection.enums.EnterpriseRiskLevel;
import com.ruoyi.info.collection.enums.EnterpriseSource;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoImportService;
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoService;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 实体库管理 Service 实现
*
* @author ruoyi
* @date 2026-04-17
*/
@Service
public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInfoService {
@Resource
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
@Resource
private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper;
@Resource
private CcdiCustEnterpriseRelationMapper custEnterpriseRelationMapper;
@Resource
private CcdiIntermediaryEnterpriseRelationMapper intermediaryEnterpriseRelationMapper;
@Resource
private ICcdiEnterpriseBaseInfoImportService enterpriseBaseInfoImportService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public Page<CcdiEnterpriseBaseInfoVO> selectEnterpriseBaseInfoPage(Page<CcdiEnterpriseBaseInfoVO> page,
CcdiEnterpriseBaseInfoQueryDTO queryDTO) {
return enterpriseBaseInfoMapper.selectEnterpriseBaseInfoPage(page, queryDTO);
}
@Override
public CcdiEnterpriseBaseInfoVO selectEnterpriseBaseInfoById(String socialCreditCode) {
CcdiEnterpriseBaseInfo entity = enterpriseBaseInfoMapper.selectById(socialCreditCode);
if (entity == null) {
return null;
}
CcdiEnterpriseBaseInfoVO vo = new CcdiEnterpriseBaseInfoVO();
BeanUtils.copyProperties(entity, vo);
return vo;
}
@Override
@Transactional
public int insertEnterpriseBaseInfo(CcdiEnterpriseBaseInfoAddDTO addDTO) {
if (enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode()) != null) {
throw new RuntimeException("该统一社会信用代码已存在");
}
validateEnumFields(addDTO.getStatus(), addDTO.getRiskLevel(), addDTO.getEntSource(), addDTO.getDataSource());
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
BeanUtils.copyProperties(addDTO, entity);
return enterpriseBaseInfoMapper.insert(entity);
}
@Override
@Transactional
public int updateEnterpriseBaseInfo(CcdiEnterpriseBaseInfoEditDTO editDTO) {
CcdiEnterpriseBaseInfo existing = enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode());
if (existing == null) {
throw new RuntimeException("实体库记录不存在");
}
validateEnumFields(editDTO.getStatus(), editDTO.getRiskLevel(), editDTO.getEntSource(), editDTO.getDataSource());
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
BeanUtils.copyProperties(editDTO, entity);
return enterpriseBaseInfoMapper.updateById(entity);
}
@Override
@Transactional
public int deleteEnterpriseBaseInfoByIds(String[] socialCreditCodes) {
if (socialCreditCodes == null || socialCreditCodes.length == 0) {
return 0;
}
for (String socialCreditCode : socialCreditCodes) {
validateDeleteRelations(socialCreditCode);
}
return enterpriseBaseInfoMapper.deleteBatchIds(List.of(socialCreditCodes));
}
@Override
public List<CcdiEnterpriseBaseInfoExcel> selectEnterpriseBaseInfoListForExport(CcdiEnterpriseBaseInfoQueryDTO queryDTO) {
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = buildQueryWrapper(queryDTO);
return enterpriseBaseInfoMapper.selectList(wrapper).stream().map(entity -> {
CcdiEnterpriseBaseInfoExcel excel = new CcdiEnterpriseBaseInfoExcel();
BeanUtils.copyProperties(entity, excel);
return excel;
}).toList();
}
@Override
@Transactional
public String importEnterpriseBaseInfo(List<CcdiEnterpriseBaseInfoExcel> excelList) {
String taskId = UUID.randomUUID().toString();
String statusKey = "import:enterpriseBaseInfo:" + 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", System.currentTimeMillis());
statusData.put("message", "正在处理...");
redisTemplate.opsForHash().putAll(statusKey, statusData);
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
enterpriseBaseInfoImportService.importEnterpriseBaseInfoAsync(excelList, taskId, SecurityUtils.getUsername());
return taskId;
}
private LambdaQueryWrapper<CcdiEnterpriseBaseInfo> buildQueryWrapper(CcdiEnterpriseBaseInfoQueryDTO queryDTO) {
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
if (queryDTO == null) {
return wrapper.orderByDesc(CcdiEnterpriseBaseInfo::getCreateTime);
}
wrapper.like(StringUtils.isNotEmpty(queryDTO.getEnterpriseName()),
CcdiEnterpriseBaseInfo::getEnterpriseName, queryDTO.getEnterpriseName());
wrapper.eq(StringUtils.isNotEmpty(queryDTO.getSocialCreditCode()),
CcdiEnterpriseBaseInfo::getSocialCreditCode, queryDTO.getSocialCreditCode());
wrapper.eq(StringUtils.isNotEmpty(queryDTO.getEnterpriseType()),
CcdiEnterpriseBaseInfo::getEnterpriseType, queryDTO.getEnterpriseType());
wrapper.eq(StringUtils.isNotEmpty(queryDTO.getEnterpriseNature()),
CcdiEnterpriseBaseInfo::getEnterpriseNature, queryDTO.getEnterpriseNature());
wrapper.like(StringUtils.isNotEmpty(queryDTO.getIndustryClass()),
CcdiEnterpriseBaseInfo::getIndustryClass, queryDTO.getIndustryClass());
wrapper.eq(StringUtils.isNotEmpty(queryDTO.getStatus()),
CcdiEnterpriseBaseInfo::getStatus, queryDTO.getStatus());
wrapper.eq(StringUtils.isNotEmpty(queryDTO.getRiskLevel()),
CcdiEnterpriseBaseInfo::getRiskLevel, queryDTO.getRiskLevel());
wrapper.eq(StringUtils.isNotEmpty(queryDTO.getEntSource()),
CcdiEnterpriseBaseInfo::getEntSource, queryDTO.getEntSource());
return wrapper.orderByDesc(CcdiEnterpriseBaseInfo::getCreateTime);
}
private void validateEnumFields(String status, String riskLevel, String entSource, String dataSource) {
if (StringUtils.isEmpty(status)) {
throw new RuntimeException("经营状态不能为空");
}
if (!EnterpriseRiskLevel.contains(riskLevel)) {
throw new RuntimeException("风险等级不在允许范围内");
}
if (!EnterpriseSource.contains(entSource)) {
throw new RuntimeException("企业来源不在允许范围内");
}
if (!containsDataSource(dataSource)) {
throw new RuntimeException("数据来源不在允许范围内");
}
}
private boolean containsDataSource(String code) {
for (DataSource source : DataSource.values()) {
if (source.getCode().equals(code)) {
return true;
}
}
return false;
}
private void validateDeleteRelations(String socialCreditCode) {
StringJoiner relationTypes = new StringJoiner("");
if (staffEnterpriseRelationMapper.selectCount(new LambdaQueryWrapper<CcdiStaffEnterpriseRelation>()
.eq(CcdiStaffEnterpriseRelation::getSocialCreditCode, socialCreditCode)) > 0) {
relationTypes.add("员工");
}
if (custEnterpriseRelationMapper.selectCount(new LambdaQueryWrapper<CcdiCustEnterpriseRelation>()
.eq(CcdiCustEnterpriseRelation::getSocialCreditCode, socialCreditCode)) > 0) {
relationTypes.add("信贷客户");
}
if (intermediaryEnterpriseRelationMapper.selectCount(new LambdaQueryWrapper<CcdiIntermediaryEnterpriseRelation>()
.eq(CcdiIntermediaryEnterpriseRelation::getSocialCreditCode, socialCreditCode)) > 0) {
relationTypes.add("中介");
}
if (relationTypes.length() > 0) {
throw new RuntimeException("统一社会信用代码[" + socialCreditCode + "]已关联" + relationTypes + ",删除失败");
}
}
}

View File

@@ -0,0 +1,266 @@
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.CcdiBizIntermediary;
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.IdCardUtil;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 中介实体关联关系异步导入实现
*/
@Service
@EnableAsync
public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcdiIntermediaryEnterpriseRelationImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiIntermediaryEnterpriseRelationImportServiceImpl.class);
private static final String STATUS_KEY_PREFIX = "import:intermediary-enterprise-relation:";
@Resource
private CcdiIntermediaryEnterpriseRelationMapper relationMapper;
@Resource
private CcdiBizIntermediaryMapper intermediaryMapper;
@Resource
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
@Async
@Transactional(rollbackFor = Exception.class)
public void importAsync(List<CcdiIntermediaryEnterpriseRelationExcel> excelList, String taskId, String userName) {
long startTime = System.currentTimeMillis();
ImportLogUtils.logImportStart(log, taskId, "中介实体关联关系", excelList.size(), userName);
Map<String, String> ownerBizIdByPersonId = getOwnerBizIdByPersonId(excelList);
Set<String> existingEnterpriseCodes = getExistingEnterpriseCodes(excelList);
Set<String> existingCombinations = getExistingRelationCombinations(ownerBizIdByPersonId, excelList);
List<CcdiIntermediaryEnterpriseRelation> successRecords = new ArrayList<>();
List<IntermediaryEnterpriseRelationImportFailureVO> failures = new ArrayList<>();
Set<String> processedCombinations = new HashSet<>();
for (int i = 0; i < excelList.size(); i++) {
CcdiIntermediaryEnterpriseRelationExcel excel = excelList.get(i);
try {
validateExcel(excel);
String ownerBizId = ownerBizIdByPersonId.get(excel.getOwnerPersonId());
if (StringUtils.isEmpty(ownerBizId)) {
throw new RuntimeException("中介本人不存在,请先导入或维护中介本人信息");
}
if (!existingEnterpriseCodes.contains(excel.getSocialCreditCode())) {
throw new RuntimeException("统一社会信用代码不存在于系统机构表");
}
String combination = ownerBizId + "|" + excel.getSocialCreditCode();
if (existingCombinations.contains(combination)) {
throw new RuntimeException("中介实体关联关系已存在,请勿重复导入");
}
if (!processedCombinations.add(combination)) {
throw new RuntimeException("同一中介本人与统一社会信用代码组合在导入文件中重复");
}
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
BeanUtils.copyProperties(excel, relation);
relation.setIntermediaryBizId(ownerBizId);
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
successRecords.add(relation);
} catch (Exception e) {
failures.add(createFailureVO(excel, e.getMessage()));
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(),
String.format("中介本人证件号码=%s, 统一社会信用代码=%s", excel.getOwnerPersonId(), excel.getSocialCreditCode()));
}
}
if (!successRecords.isEmpty()) {
saveBatch(successRecords, 500);
}
if (!failures.isEmpty()) {
redisTemplate.opsForValue().set(failureKey(taskId), failures, 7, TimeUnit.DAYS);
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(successRecords.size());
result.setFailureCount(failures.size());
updateImportStatus(taskId, result);
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "中介实体关联关系",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = statusKey(taskId);
if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
@Override
public List<IntermediaryEnterpriseRelationImportFailureVO> getImportFailures(String taskId) {
Object failuresObj = redisTemplate.opsForValue().get(failureKey(taskId));
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), IntermediaryEnterpriseRelationImportFailureVO.class);
}
private Map<String, String> getOwnerBizIdByPersonId(List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
List<String> ownerPersonIds = excelList.stream()
.map(CcdiIntermediaryEnterpriseRelationExcel::getOwnerPersonId)
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
if (ownerPersonIds.isEmpty()) {
return Collections.emptyMap();
}
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBizIntermediary::getPersonSubType, "本人")
.in(CcdiBizIntermediary::getPersonId, ownerPersonIds);
return intermediaryMapper.selectList(wrapper).stream()
.collect(Collectors.toMap(CcdiBizIntermediary::getPersonId, CcdiBizIntermediary::getBizId, (left, right) -> left));
}
private Set<String> getExistingEnterpriseCodes(List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
List<String> socialCreditCodes = excelList.stream()
.map(CcdiIntermediaryEnterpriseRelationExcel::getSocialCreditCode)
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
if (socialCreditCodes.isEmpty()) {
return Collections.emptySet();
}
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, socialCreditCodes);
return enterpriseBaseInfoMapper.selectList(wrapper).stream()
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
.collect(Collectors.toSet());
}
private Set<String> getExistingRelationCombinations(Map<String, String> ownerBizIdByPersonId,
List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
List<String> combinations = excelList.stream()
.map(excel -> {
String ownerBizId = ownerBizIdByPersonId.get(excel.getOwnerPersonId());
if (StringUtils.isEmpty(ownerBizId) || StringUtils.isEmpty(excel.getSocialCreditCode())) {
return null;
}
return ownerBizId + "|" + excel.getSocialCreditCode();
})
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
if (combinations.isEmpty()) {
return Collections.emptySet();
}
return new HashSet<>(relationMapper.batchExistsByCombinations(combinations));
}
private void validateExcel(CcdiIntermediaryEnterpriseRelationExcel excel) {
if (StringUtils.isEmpty(excel.getOwnerPersonId())) {
throw new RuntimeException("中介本人证件号码不能为空");
}
if (StringUtils.isEmpty(excel.getSocialCreditCode())) {
throw new RuntimeException("统一社会信用代码不能为空");
}
String ownerPersonIdError = IdCardUtil.getErrorMessage(excel.getOwnerPersonId());
if (ownerPersonIdError != null) {
throw new RuntimeException("中介本人证件号码" + ownerPersonIdError);
}
if (StringUtils.isNotEmpty(excel.getRelationPersonPost()) && excel.getRelationPersonPost().length() > 100) {
throw new RuntimeException("关联人职务长度不能超过100个字符");
}
if (StringUtils.isNotEmpty(excel.getRemark()) && excel.getRemark().length() > 500) {
throw new RuntimeException("备注长度不能超过500个字符");
}
}
private IntermediaryEnterpriseRelationImportFailureVO createFailureVO(CcdiIntermediaryEnterpriseRelationExcel excel,
String errorMessage) {
IntermediaryEnterpriseRelationImportFailureVO failure = new IntermediaryEnterpriseRelationImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(errorMessage);
return failure;
}
private void saveBatch(List<CcdiIntermediaryEnterpriseRelation> list, int batchSize) {
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
relationMapper.insertBatch(list.subList(i, end));
}
}
private void updateImportStatus(String taskId, ImportResult result) {
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS");
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
statusData.put("message", result.getFailureCount() == 0
? "全部成功!共导入" + result.getTotalCount() + "条数据"
: "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
redisTemplate.opsForHash().putAll(statusKey(taskId), statusData);
}
private String statusKey(String taskId) {
return STATUS_KEY_PREFIX + taskId;
}
private String failureKey(String taskId) {
return statusKey(taskId) + ":failures";
}
}

View File

@@ -22,15 +22,18 @@ import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 个人中介异步导入Service实现
*
* @author ruoyi
* @date 2026-02-06
* 中介信息异步导入实现
*/
@Service
@EnableAsync
@@ -38,6 +41,8 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
private static final Logger log = LoggerFactory.getLogger(CcdiIntermediaryPersonImportServiceImpl.class);
private static final String STATUS_KEY_PREFIX = "import:intermediary:";
@Resource
private CcdiBizIntermediaryMapper intermediaryMapper;
@@ -47,110 +52,104 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
@Override
@Async
@Transactional(rollbackFor = Exception.class)
public void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList,
String taskId,
String userName) {
public void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList, String taskId, String userName) {
long startTime = System.currentTimeMillis();
ImportLogUtils.logImportStart(log, taskId, "中介信息", excelList.size(), userName);
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "个人中介", excelList.size(), userName);
List<CcdiBizIntermediary> newRecords = new ArrayList<>();
List<CcdiIntermediaryPersonExcel> ownerRows = new ArrayList<>();
List<CcdiIntermediaryPersonExcel> relativeRows = new ArrayList<>();
List<IntermediaryPersonImportFailureVO> failures = new ArrayList<>();
// 批量查询已存在的证件号
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的证件号", excelList.size());
Set<String> existingPersonIds = getExistingPersonIds(excelList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "证件号", existingPersonIds.size());
// 用于检测Excel内部的重复ID
Set<String> excelProcessedIds = new HashSet<>();
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiIntermediaryPersonExcel excel = excelList.get(i);
try {
// 验证数据
validatePersonData(excel, existingPersonIds);
CcdiBizIntermediary intermediary = new CcdiBizIntermediary();
BeanUtils.copyProperties(excel, intermediary);
// 设置数据来源和审计字段
intermediary.setDataSource("IMPORT");
intermediary.setCreatedBy(userName);
intermediary.setUpdatedBy(userName);
if (existingPersonIds.contains(excel.getPersonId())) {
// 证件号码在数据库中已存在,直接报错
throw new RuntimeException(String.format("证件号码[%s]已存在,请勿重复导入", excel.getPersonId()));
} else if (excelProcessedIds.contains(excel.getPersonId())) {
// 证件号码在Excel文件内部重复
throw new RuntimeException(String.format("证件号码[%s]在导入文件中重复,已跳过此条记录", excel.getPersonId()));
validateCommonRow(excel);
if (isOwnerRow(excel)) {
validateOwnerRow(excel);
ownerRows.add(excel);
} else {
newRecords.add(intermediary);
excelProcessedIds.add(excel.getPersonId()); // 标记为已处理
validateRelativeRow(excel);
relativeRows.add(excel);
}
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
} catch (Exception e) {
failures.add(createFailureVO(excel, e.getMessage()));
// 记录验证失败日志
String keyData = String.format("姓名=%s, 证件号码=%s",
excel.getName(), excel.getPersonId());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(),
String.format("姓名=%s, 证件号码=%s", excel.getName(), excel.getPersonId()));
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
Set<String> existingOwnerPersonIds = getExistingOwnerPersonIds(ownerRows);
Set<String> existingOwnerRefs = getExistingOwnerRefs(relativeRows);
Set<String> existingRelativeCombinations = getExistingRelativeCombinations(relativeRows);
// 保存失败记录到Redis
if (!failures.isEmpty()) {
List<CcdiBizIntermediary> successRecords = new ArrayList<>();
Set<String> importedOwnerPersonIds = new HashSet<>();
for (CcdiIntermediaryPersonExcel ownerExcel : ownerRows) {
try {
String failuresKey = "import:intermediary:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
String ownerPersonId = ownerExcel.getPersonId();
if (existingOwnerPersonIds.contains(ownerPersonId)) {
throw new RuntimeException(String.format("中介本人证件号码[%s]已存在,请勿重复导入", ownerPersonId));
}
if (!importedOwnerPersonIds.add(ownerPersonId)) {
throw new RuntimeException(String.format("中介本人证件号码[%s]在导入文件中重复", ownerPersonId));
}
successRecords.add(buildRecord(ownerExcel, userName, null));
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
failures.add(createFailureVO(ownerExcel, e.getMessage()));
}
}
Set<String> validOwnerRefs = new HashSet<>(existingOwnerRefs);
validOwnerRefs.addAll(importedOwnerPersonIds);
Set<String> processedRelativeCombinations = new HashSet<>();
for (CcdiIntermediaryPersonExcel relativeExcel : relativeRows) {
try {
String ownerPersonId = relativeExcel.getRelatedNumId();
String combination = ownerPersonId + "|" + relativeExcel.getPersonId();
if (!validOwnerRefs.contains(ownerPersonId)) {
throw new RuntimeException(String.format("关联中介本人证件号码[%s]不存在", ownerPersonId));
}
if (existingRelativeCombinations.contains(combination)) {
throw new RuntimeException(String.format("同一中介本人名下证件号码[%s]的亲属已存在,请勿重复导入", relativeExcel.getPersonId()));
}
if (!processedRelativeCombinations.add(combination)) {
throw new RuntimeException(String.format("同一中介本人名下证件号码[%s]的亲属在导入文件中重复", relativeExcel.getPersonId()));
}
successRecords.add(buildRecord(relativeExcel, userName, ownerPersonId));
} catch (Exception e) {
failures.add(createFailureVO(relativeExcel, e.getMessage()));
}
}
if (!successRecords.isEmpty()) {
saveBatch(successRecords, 500);
}
if (!failures.isEmpty()) {
redisTemplate.opsForValue().set(failureKey(taskId), failures, 7, TimeUnit.DAYS);
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size());
result.setSuccessCount(successRecords.size());
result.setFailureCount(failures.size());
updateImportStatus(taskId, result);
// 更新最终状态
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);
ImportLogUtils.logImportComplete(log, taskId, "中介信息",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:intermediary:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
String key = statusKey(taskId);
if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
@@ -161,83 +160,120 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
@Override
public List<IntermediaryPersonImportFailureVO> getImportFailures(String taskId) {
String key = "import:intermediary:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
Object failuresObj = redisTemplate.opsForValue().get(failureKey(taskId));
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), IntermediaryPersonImportFailureVO.class);
}
/**
* 批量查询已存在的证件号
*/
private Set<String> getExistingPersonIds(List<CcdiIntermediaryPersonExcel> excelList) {
List<String> personIds = excelList.stream()
.map(CcdiIntermediaryPersonExcel::getPersonId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toList());
private boolean isOwnerRow(CcdiIntermediaryPersonExcel excel) {
return "本人".equals(excel.getPersonSubType());
}
if (personIds.isEmpty()) {
private void validateCommonRow(CcdiIntermediaryPersonExcel excel) {
if (StringUtils.isEmpty(excel.getName())) {
throw new RuntimeException("姓名不能为空");
}
if (StringUtils.isEmpty(excel.getPersonSubType())) {
throw new RuntimeException("人员子类型不能为空");
}
if (StringUtils.isEmpty(excel.getPersonId())) {
throw new RuntimeException("证件号码不能为空");
}
String idCardError = IdCardUtil.getErrorMessage(excel.getPersonId());
if (idCardError != null) {
throw new RuntimeException("证件号码" + idCardError);
}
}
private void validateOwnerRow(CcdiIntermediaryPersonExcel excel) {
if (StringUtils.isNotEmpty(excel.getRelatedNumId())) {
throw new RuntimeException("本人行关联中介本人证件号码必须为空");
}
}
private void validateRelativeRow(CcdiIntermediaryPersonExcel excel) {
if (StringUtils.isEmpty(excel.getRelatedNumId())) {
throw new RuntimeException("亲属行必须填写关联中介本人证件号码");
}
}
private Set<String> getExistingOwnerPersonIds(List<CcdiIntermediaryPersonExcel> ownerRows) {
List<String> ownerPersonIds = ownerRows.stream()
.map(CcdiIntermediaryPersonExcel::getPersonId)
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
if (ownerPersonIds.isEmpty()) {
return Collections.emptySet();
}
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CcdiBizIntermediary::getPersonId, personIds);
List<CcdiBizIntermediary> existingIntermediaries = intermediaryMapper.selectList(wrapper);
return existingIntermediaries.stream()
.map(CcdiBizIntermediary::getPersonId)
.collect(Collectors.toSet());
wrapper.eq(CcdiBizIntermediary::getPersonSubType, "本人")
.in(CcdiBizIntermediary::getPersonId, ownerPersonIds);
return intermediaryMapper.selectList(wrapper).stream()
.map(CcdiBizIntermediary::getPersonId)
.collect(Collectors.toSet());
}
/**
* 批量保存(使用ON DUPLICATE KEY UPDATE)
*/
private int saveBatchWithUpsert(List<CcdiBizIntermediary> list, int batchSize) {
int totalCount = 0;
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiBizIntermediary> subList = list.subList(i, end);
int count = intermediaryMapper.importPersonBatch(subList);
totalCount += count;
}
return totalCount;
}
/**
* 从数据库获取已存在的证件号
*/
private Set<String> getExistingPersonIdsFromDb(List<CcdiBizIntermediary> records) {
List<String> personIds = records.stream()
.map(CcdiBizIntermediary::getPersonId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toList());
if (personIds.isEmpty()) {
private Set<String> getExistingOwnerRefs(List<CcdiIntermediaryPersonExcel> relativeRows) {
List<String> ownerRefs = relativeRows.stream()
.map(CcdiIntermediaryPersonExcel::getRelatedNumId)
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
if (ownerRefs.isEmpty()) {
return Collections.emptySet();
}
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CcdiBizIntermediary::getPersonId, personIds);
List<CcdiBizIntermediary> existing = intermediaryMapper.selectList(wrapper);
return existing.stream()
.map(CcdiBizIntermediary::getPersonId)
.collect(Collectors.toSet());
wrapper.eq(CcdiBizIntermediary::getPersonSubType, "本人")
.in(CcdiBizIntermediary::getPersonId, ownerRefs);
return intermediaryMapper.selectList(wrapper).stream()
.map(CcdiBizIntermediary::getPersonId)
.collect(Collectors.toSet());
}
private Set<String> getExistingRelativeCombinations(List<CcdiIntermediaryPersonExcel> relativeRows) {
List<String> ownerRefs = relativeRows.stream()
.map(CcdiIntermediaryPersonExcel::getRelatedNumId)
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
List<String> relativePersonIds = relativeRows.stream()
.map(CcdiIntermediaryPersonExcel::getPersonId)
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
if (ownerRefs.isEmpty() || relativePersonIds.isEmpty()) {
return Collections.emptySet();
}
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.ne(CcdiBizIntermediary::getPersonSubType, "本人")
.in(CcdiBizIntermediary::getRelatedNumId, ownerRefs)
.in(CcdiBizIntermediary::getPersonId, relativePersonIds);
return intermediaryMapper.selectList(wrapper).stream()
.map(item -> item.getRelatedNumId() + "|" + item.getPersonId())
.collect(Collectors.toSet());
}
private CcdiBizIntermediary buildRecord(CcdiIntermediaryPersonExcel excel, String userName, String ownerPersonId) {
CcdiBizIntermediary intermediary = new CcdiBizIntermediary();
BeanUtils.copyProperties(excel, intermediary);
intermediary.setRelatedNumId(ownerPersonId);
intermediary.setDataSource("IMPORT");
intermediary.setCreatedBy(userName);
intermediary.setUpdatedBy(userName);
return intermediary;
}
/**
* 创建失败记录VO
*/
private IntermediaryPersonImportFailureVO createFailureVO(CcdiIntermediaryPersonExcel excel, String errorMsg) {
IntermediaryPersonImportFailureVO failure = new IntermediaryPersonImportFailureVO();
BeanUtils.copyProperties(excel, failure);
@@ -245,73 +281,31 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
return failure;
}
/**
* 创建失败记录VO(重载方法)
*/
private IntermediaryPersonImportFailureVO createFailureVO(CcdiBizIntermediary record, String errorMsg) {
CcdiIntermediaryPersonExcel excel = new CcdiIntermediaryPersonExcel();
BeanUtils.copyProperties(record, excel);
return createFailureVO(excel, errorMsg);
}
/**
* 批量保存
*/
private int saveBatch(List<CcdiBizIntermediary> list, int batchSize) {
// 使用真正的批量插入,分批次执行以提高性能
int totalCount = 0;
private void saveBatch(List<CcdiBizIntermediary> list, int batchSize) {
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiBizIntermediary> subList = list.subList(i, end);
int count = intermediaryMapper.insertBatch(subList);
totalCount += count;
intermediaryMapper.insertBatch(list.subList(i, end));
}
return totalCount;
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:intermediary:" + taskId;
private void updateImportStatus(String taskId, ImportResult result) {
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("status", result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS");
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(key, statusData);
statusData.put("message", result.getFailureCount() == 0
? "全部成功!共导入" + result.getTotalCount() + "条数据"
: "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
redisTemplate.opsForHash().putAll(statusKey(taskId), statusData);
}
/**
* 验证个人中介数据
*
* @param excel Excel数据
* @param existingPersonIds 已存在的证件号集合
*/
private void validatePersonData(CcdiIntermediaryPersonExcel excel,
Set<String> existingPersonIds) {
// 验证必填字段:姓名
if (StringUtils.isEmpty(excel.getName())) {
throw new RuntimeException("姓名不能为空");
}
private String statusKey(String taskId) {
return STATUS_KEY_PREFIX + taskId;
}
// 验证必填字段:证件号码
if (StringUtils.isEmpty(excel.getPersonId())) {
throw new RuntimeException("证件号码不能为空");
}
// 验证证件号码格式
String idCardError = IdCardUtil.getErrorMessage(excel.getPersonId());
if (idCardError != null) {
throw new RuntimeException("证件号码" + idCardError);
}
private String failureKey(String taskId) {
return statusKey(taskId) + ":failures";
}
}

View File

@@ -4,15 +4,21 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.*;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEntityExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEntityDetailVO;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryPersonDetailVO;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryRelativeVO;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryVO;
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryMapper;
import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryEntityImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryService;
@@ -48,12 +54,18 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
@Resource
private CcdiIntermediaryMapper intermediaryMapper;
@Resource
private CcdiIntermediaryEnterpriseRelationMapper enterpriseRelationMapper;
@Resource
private ICcdiIntermediaryPersonImportService personImportService;
@Resource
private ICcdiIntermediaryEntityImportService entityImportService;
@Resource
private ICcdiIntermediaryEnterpriseRelationImportService enterpriseRelationImportService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@@ -81,7 +93,7 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
@Override
public CcdiIntermediaryPersonDetailVO selectIntermediaryPersonDetail(String bizId) {
CcdiBizIntermediary person = bizIntermediaryMapper.selectById(bizId);
if (person == null) {
if (person == null || !isIntermediaryPerson(person)) {
return null;
}
@@ -92,6 +104,25 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
return vo;
}
@Override
public List<CcdiIntermediaryRelativeVO> selectIntermediaryRelativeList(String bizId) {
CcdiBizIntermediary owner = requireIntermediaryPerson(bizId);
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBizIntermediary::getRelatedNumId, owner.getPersonId())
.ne(CcdiBizIntermediary::getPersonSubType, "本人")
.orderByDesc(CcdiBizIntermediary::getCreateTime);
return bizIntermediaryMapper.selectList(wrapper).stream().map(this::buildRelativeVo).toList();
}
@Override
public CcdiIntermediaryRelativeVO selectIntermediaryRelativeDetail(String relativeBizId) {
CcdiBizIntermediary relative = bizIntermediaryMapper.selectById(relativeBizId);
if (relative == null || isIntermediaryPerson(relative)) {
return null;
}
return buildRelativeVo(relative);
}
/**
* 查询实体中介详情
*
@@ -130,6 +161,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
CcdiBizIntermediary person = new CcdiBizIntermediary();
BeanUtils.copyProperties(addDTO, person);
person.setPersonSubType("本人");
person.setRelatedNumId(null);
person.setDataSource("MANUAL");
return bizIntermediaryMapper.insert(person);
@@ -151,10 +184,63 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
}
}
CcdiBizIntermediary existing = bizIntermediaryMapper.selectById(editDTO.getBizId());
if (existing == null || !isIntermediaryPerson(existing)) {
throw new RuntimeException("中介本人不存在");
}
CcdiBizIntermediary person = new CcdiBizIntermediary();
BeanUtils.copyProperties(editDTO, person);
person.setPersonSubType("本人");
person.setRelatedNumId(null);
int updated = bizIntermediaryMapper.updateById(person);
syncRelativeOwnerPersonId(existing.getPersonId(), editDTO.getPersonId());
return updated;
}
return bizIntermediaryMapper.updateById(person);
@Override
@Transactional
public int insertIntermediaryRelative(String bizId, CcdiIntermediaryRelativeAddDTO addDTO) {
CcdiBizIntermediary owner = requireIntermediaryPerson(bizId);
validateRelativePersonSubType(addDTO.getPersonSubType());
if (!checkRelativePersonUnique(owner.getPersonId(), addDTO.getPersonId(), null)) {
throw new RuntimeException("该中介本人下已存在相同证件号亲属");
}
CcdiBizIntermediary relative = new CcdiBizIntermediary();
BeanUtils.copyProperties(addDTO, relative);
relative.setRelatedNumId(owner.getPersonId());
relative.setDataSource("MANUAL");
return bizIntermediaryMapper.insert(relative);
}
@Override
@Transactional
public int updateIntermediaryRelative(CcdiIntermediaryRelativeEditDTO editDTO) {
CcdiBizIntermediary existing = bizIntermediaryMapper.selectById(editDTO.getBizId());
if (existing == null || isIntermediaryPerson(existing)) {
throw new RuntimeException("中介亲属不存在");
}
validateRelativePersonSubType(editDTO.getPersonSubType());
if (StringUtils.isNotEmpty(editDTO.getPersonId())
&& !checkRelativePersonUnique(existing.getRelatedNumId(), editDTO.getPersonId(), editDTO.getBizId())) {
throw new RuntimeException("该中介本人下已存在相同证件号亲属");
}
CcdiBizIntermediary relative = new CcdiBizIntermediary();
BeanUtils.copyProperties(editDTO, relative);
relative.setRelatedNumId(existing.getRelatedNumId());
return bizIntermediaryMapper.updateById(relative);
}
@Override
@Transactional
public int deleteIntermediaryRelative(String relativeBizId) {
CcdiBizIntermediary existing = bizIntermediaryMapper.selectById(relativeBizId);
if (existing == null || isIntermediaryPerson(existing)) {
throw new RuntimeException("中介亲属不存在");
}
return bizIntermediaryMapper.deleteById(relativeBizId);
}
/**
@@ -197,6 +283,49 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
return enterpriseBaseInfoMapper.updateById(entity);
}
@Override
public List<CcdiIntermediaryEnterpriseRelationVO> selectIntermediaryEnterpriseRelationList(String bizId) {
return enterpriseRelationMapper.selectByIntermediaryBizId(bizId);
}
@Override
public CcdiIntermediaryEnterpriseRelationVO selectIntermediaryEnterpriseRelationDetail(Long id) {
return enterpriseRelationMapper.selectDetailById(id);
}
@Override
@Transactional
public int insertIntermediaryEnterpriseRelation(String bizId, CcdiIntermediaryEnterpriseRelationAddDTO addDTO) {
CcdiBizIntermediary owner = requireIntermediaryPerson(bizId);
validateEnterpriseRelation(owner.getBizId(), addDTO.getSocialCreditCode(), null);
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
BeanUtils.copyProperties(addDTO, relation);
relation.setIntermediaryBizId(owner.getBizId());
return enterpriseRelationMapper.insert(relation);
}
@Override
@Transactional
public int updateIntermediaryEnterpriseRelation(CcdiIntermediaryEnterpriseRelationEditDTO editDTO) {
CcdiIntermediaryEnterpriseRelation existing = enterpriseRelationMapper.selectById(editDTO.getId());
if (existing == null) {
throw new RuntimeException("中介关联机构不存在");
}
validateEnterpriseRelation(existing.getIntermediaryBizId(), editDTO.getSocialCreditCode(), existing.getId());
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
BeanUtils.copyProperties(editDTO, relation);
relation.setIntermediaryBizId(existing.getIntermediaryBizId());
return enterpriseRelationMapper.updateById(relation);
}
@Override
@Transactional
public int deleteIntermediaryEnterpriseRelation(Long id) {
return enterpriseRelationMapper.deleteById(id);
}
/**
* 批量删除中介
*
@@ -208,12 +337,20 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
public int deleteIntermediaryByIds(String[] ids) {
int count = 0;
for (String id : ids) {
// 判断是个人还是实体个人ID长度较长实体统一社会信用代码18位
if (id.length() > 18) {
// 个人中介
CcdiBizIntermediary intermediary = bizIntermediaryMapper.selectById(id);
if (intermediary != null) {
if (isIntermediaryPerson(intermediary)) {
bizIntermediaryMapper.delete(new LambdaQueryWrapper<CcdiBizIntermediary>()
.eq(CcdiBizIntermediary::getRelatedNumId, intermediary.getPersonId())
.ne(CcdiBizIntermediary::getPersonSubType, "本人"));
enterpriseRelationMapper.delete(new LambdaQueryWrapper<CcdiIntermediaryEnterpriseRelation>()
.eq(CcdiIntermediaryEnterpriseRelation::getIntermediaryBizId, id));
}
count += bizIntermediaryMapper.deleteById(id);
} else {
// 实体中介
continue;
}
if (enterpriseBaseInfoMapper.selectById(id) != null) {
count += enterpriseBaseInfoMapper.deleteById(id);
}
}
@@ -230,7 +367,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
@Override
public boolean checkPersonIdUnique(String personId, String bizId) {
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBizIntermediary::getPersonId, personId);
wrapper.eq(CcdiBizIntermediary::getPersonId, personId)
.eq(CcdiBizIntermediary::getPersonSubType, "本人");
if (StringUtils.isNotEmpty(bizId)) {
wrapper.ne(CcdiBizIntermediary::getBizId, bizId);
}
@@ -290,6 +428,31 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
return taskId;
}
@Override
@Transactional
public String importIntermediaryEnterpriseRelation(List<CcdiIntermediaryEnterpriseRelationExcel> list) {
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
String statusKey = "import:intermediary-enterprise-relation:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("taskId", taskId);
statusData.put("status", "PROCESSING");
statusData.put("totalCount", list.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);
String userName = SecurityUtils.getUsername();
enterpriseRelationImportService.importAsync(list, taskId, userName);
return taskId;
}
/**
* 导入实体中介数据(异步)
*
@@ -325,4 +488,70 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
return taskId;
}
private boolean isIntermediaryPerson(CcdiBizIntermediary person) {
return "本人".equals(person.getPersonSubType());
}
private CcdiBizIntermediary requireIntermediaryPerson(String bizId) {
CcdiBizIntermediary owner = bizIntermediaryMapper.selectById(bizId);
if (owner == null || !isIntermediaryPerson(owner)) {
throw new RuntimeException("中介本人不存在");
}
return owner;
}
private void validateRelativePersonSubType(String personSubType) {
if ("本人".equals(personSubType)) {
throw new RuntimeException("亲属关系不能为本人");
}
}
private boolean checkRelativePersonUnique(String ownerPersonId, String personId, String excludeBizId) {
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBizIntermediary::getRelatedNumId, ownerPersonId)
.eq(CcdiBizIntermediary::getPersonId, personId)
.ne(CcdiBizIntermediary::getPersonSubType, "本人");
if (StringUtils.isNotEmpty(excludeBizId)) {
wrapper.ne(CcdiBizIntermediary::getBizId, excludeBizId);
}
return bizIntermediaryMapper.selectCount(wrapper) == 0;
}
private void validateEnterpriseRelation(String bizId, String socialCreditCode, Long excludeId) {
requireIntermediaryPerson(bizId);
if (enterpriseBaseInfoMapper.selectById(socialCreditCode) == null) {
throw new RuntimeException("关联机构不存在");
}
boolean exists = enterpriseRelationMapper.existsByIntermediaryBizIdAndSocialCreditCode(bizId, socialCreditCode);
if (exists) {
if (excludeId == null) {
throw new RuntimeException("该中介已关联此机构");
}
CcdiIntermediaryEnterpriseRelation existing = enterpriseRelationMapper.selectById(excludeId);
if (existing == null || !socialCreditCode.equals(existing.getSocialCreditCode())) {
throw new RuntimeException("该中介已关联此机构");
}
}
}
private void syncRelativeOwnerPersonId(String oldOwnerPersonId, String newOwnerPersonId) {
if (StringUtils.isEmpty(oldOwnerPersonId)
|| StringUtils.isEmpty(newOwnerPersonId)
|| oldOwnerPersonId.equals(newOwnerPersonId)) {
return;
}
CcdiBizIntermediary relative = new CcdiBizIntermediary();
relative.setRelatedNumId(newOwnerPersonId);
bizIntermediaryMapper.update(relative, new LambdaQueryWrapper<CcdiBizIntermediary>()
.eq(CcdiBizIntermediary::getRelatedNumId, oldOwnerPersonId)
.ne(CcdiBizIntermediary::getPersonSubType, "本人"));
}
private CcdiIntermediaryRelativeVO buildRelativeVo(CcdiBizIntermediary relative) {
CcdiIntermediaryRelativeVO vo = new CcdiIntermediaryRelativeVO();
BeanUtils.copyProperties(relative, vo);
return vo;
}
}

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,165 @@
<?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,
ai.is_self_account AS isActualControl,
ai.monthly_avg_trans_count AS avgMonthTxnCount,
ai.monthly_avg_trans_amount AS avgMonthTxnAmount,
ai.trans_freq_type AS txnFrequencyLevel,
ai.dr_max_single_amount AS debitSingleMaxAmount,
ai.cr_max_single_amount AS creditSingleMaxAmount,
ai.dr_max_daily_amount AS debitDailyMaxAmount,
ai.cr_max_daily_amount AS creditDailyMaxAmount,
ai.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 ai.is_self_account = #{query.isActualControl}
</if>
<if test="query.riskLevel != null and query.riskLevel != ''">
AND ai.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_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_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_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

@@ -14,13 +14,14 @@
<result property="phone" column="phone"/>
<result property="annualIncome" column="annual_income"/>
<result property="hireDate" column="hire_date"/>
<result property="partyMember" column="is_party_member"/>
<result property="status" column="status"/>
<result property="createTime" column="create_time"/>
</resultMap>
<select id="selectBaseStaffPageWithDept" resultMap="CcdiBaseStaffVOResult">
SELECT
e.staff_id, e.name, e.dept_id, e.id_card, e.phone, e.annual_income, e.hire_date, e.status, e.create_time,
e.staff_id, e.name, e.dept_id, e.id_card, e.phone, e.annual_income, e.hire_date, e.is_party_member, e.status, e.create_time,
d.dept_name
FROM ccdi_base_staff e
LEFT JOIN sys_dept d ON e.dept_id = d.dept_id
@@ -47,12 +48,12 @@
<!-- 批量插入或更新员工信息只更新非null字段 -->
<insert id="insertOrUpdateBatch" parameterType="java.util.List">
INSERT INTO ccdi_base_staff
(staff_id, name, dept_id, id_card, phone, annual_income, hire_date, status,
(staff_id, name, dept_id, id_card, phone, annual_income, hire_date, is_party_member, status,
create_time, create_by, update_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.staffId}, #{item.name}, #{item.deptId}, #{item.idCard},
#{item.phone}, #{item.annualIncome}, #{item.hireDate}, #{item.status}, NOW(),
#{item.phone}, #{item.annualIncome}, #{item.hireDate}, #{item.partyMember}, #{item.status}, NOW(),
#{item.createBy}, #{item.updateBy}, NOW())
</foreach>
ON DUPLICATE KEY UPDATE
@@ -61,6 +62,7 @@
phone = COALESCE(VALUES(phone), phone),
annual_income = COALESCE(VALUES(annual_income), annual_income),
hire_date = COALESCE(VALUES(hire_date), hire_date),
is_party_member = COALESCE(VALUES(is_party_member), is_party_member),
status = COALESCE(VALUES(status), status),
update_by = COALESCE(VALUES(update_by), update_by),
update_time = NOW()
@@ -69,12 +71,12 @@
<!-- 批量插入员工信息 -->
<insert id="insertBatch" parameterType="java.util.List">
INSERT INTO ccdi_base_staff
(staff_id, name, dept_id, id_card, phone, annual_income, hire_date, status,
(staff_id, name, dept_id, id_card, phone, annual_income, hire_date, is_party_member, status,
create_time, create_by, update_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.staffId}, #{item.name}, #{item.deptId}, #{item.idCard},
#{item.phone}, #{item.annualIncome}, #{item.hireDate}, #{item.status}, NOW(),
#{item.phone}, #{item.annualIncome}, #{item.hireDate}, #{item.partyMember}, #{item.status}, NOW(),
#{item.createBy}, #{item.updateBy}, NOW())
</foreach>
</insert>
@@ -86,6 +88,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

@@ -4,6 +4,83 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper">
<resultMap id="CcdiEnterpriseBaseInfoVoResultMap" type="com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO">
<id property="socialCreditCode" column="social_credit_code"/>
<result property="enterpriseName" column="enterprise_name"/>
<result property="enterpriseType" column="enterprise_type"/>
<result property="enterpriseNature" column="enterprise_nature"/>
<result property="industryClass" column="industry_class"/>
<result property="industryName" column="industry_name"/>
<result property="establishDate" column="establish_date"/>
<result property="registerAddress" column="register_address"/>
<result property="legalRepresentative" column="legal_representative"/>
<result property="legalCertType" column="legal_cert_type"/>
<result property="legalCertNo" column="legal_cert_no"/>
<result property="shareholder1" column="shareholder1"/>
<result property="shareholder2" column="shareholder2"/>
<result property="shareholder3" column="shareholder3"/>
<result property="shareholder4" column="shareholder4"/>
<result property="shareholder5" column="shareholder5"/>
<result property="status" column="status"/>
<result property="riskLevel" column="risk_level"/>
<result property="entSource" column="ent_source"/>
<result property="dataSource" column="data_source"/>
<result property="createTime" column="create_time"/>
</resultMap>
<select id="selectEnterpriseBaseInfoPage" resultMap="CcdiEnterpriseBaseInfoVoResultMap">
SELECT
social_credit_code,
enterprise_name,
enterprise_type,
enterprise_nature,
industry_class,
industry_name,
establish_date,
register_address,
legal_representative,
legal_cert_type,
legal_cert_no,
shareholder1,
shareholder2,
shareholder3,
shareholder4,
shareholder5,
status,
risk_level,
ent_source,
data_source,
create_time
FROM ccdi_enterprise_base_info
<where>
<if test="queryDTO != null and queryDTO.enterpriseName != null and queryDTO.enterpriseName != ''">
AND enterprise_name LIKE CONCAT('%', #{queryDTO.enterpriseName}, '%')
</if>
<if test="queryDTO != null and queryDTO.socialCreditCode != null and queryDTO.socialCreditCode != ''">
AND social_credit_code = #{queryDTO.socialCreditCode}
</if>
<if test="queryDTO != null and queryDTO.enterpriseType != null and queryDTO.enterpriseType != ''">
AND enterprise_type = #{queryDTO.enterpriseType}
</if>
<if test="queryDTO != null and queryDTO.enterpriseNature != null and queryDTO.enterpriseNature != ''">
AND enterprise_nature = #{queryDTO.enterpriseNature}
</if>
<if test="queryDTO != null and queryDTO.industryClass != null and queryDTO.industryClass != ''">
AND industry_class LIKE CONCAT('%', #{queryDTO.industryClass}, '%')
</if>
<if test="queryDTO != null and queryDTO.status != null and queryDTO.status != ''">
AND status = #{queryDTO.status}
</if>
<if test="queryDTO != null and queryDTO.riskLevel != null and queryDTO.riskLevel != ''">
AND risk_level = #{queryDTO.riskLevel}
</if>
<if test="queryDTO != null and queryDTO.entSource != null and queryDTO.entSource != ''">
AND ent_source = #{queryDTO.entSource}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 批量插入实体中介 -->
<insert id="insertBatch" parameterType="java.util.List">
INSERT INTO ccdi_enterprise_base_info (
@@ -21,7 +98,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{item.legalRepresentative}, #{item.legalCertType}, #{item.legalCertNo},
#{item.shareholder1}, #{item.shareholder2}, #{item.shareholder3}, #{item.shareholder4}, #{item.shareholder5},
#{item.status}, #{item.riskLevel}, #{item.entSource}, #{item.dataSource},
#{item.createdBy}, #{item.updatedBy}, #{item.createTime}, #{item.updateTime}
#{item.createdBy}, #{item.updatedBy}, NOW(), NOW()
)
</foreach>
</insert>
@@ -43,7 +120,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{item.legalRepresentative}, #{item.legalCertType}, #{item.legalCertNo},
#{item.shareholder1}, #{item.shareholder2}, #{item.shareholder3}, #{item.shareholder4}, #{item.shareholder5},
#{item.status}, #{item.riskLevel}, #{item.entSource}, #{item.dataSource},
#{item.createdBy}, #{item.updatedBy}, #{item.createTime}, #{item.updateTime}
#{item.createdBy}, #{item.updatedBy}, NOW(), NOW()
)
</foreach>
ON DUPLICATE KEY UPDATE
@@ -67,7 +144,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ent_source = VALUES(ent_source),
data_source = VALUES(data_source),
updated_by = VALUES(updated_by),
update_time = VALUES(update_time)
update_time = NOW()
</insert>
<!-- 批量更新实体中介 -->
@@ -95,7 +172,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="item.entSource != null">ent_source = #{item.entSource},</if>
<if test="item.dataSource != null">data_source = #{item.dataSource},</if>
<if test="item.updatedBy != null">updated_by = #{item.updatedBy},</if>
update_time = #{item.updateTime}
update_time = NOW()
</set>
WHERE social_credit_code = #{item.socialCreditCode}
</foreach>

View File

@@ -0,0 +1,88 @@
<?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.CcdiIntermediaryEnterpriseRelationMapper">
<insert id="insertBatch" parameterType="java.util.List">
INSERT INTO ccdi_intermediary_enterprise_relation (
intermediary_biz_id, social_credit_code, relation_person_post, remark,
created_by, updated_by, create_time, update_time
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.intermediaryBizId}, #{item.socialCreditCode}, #{item.relationPersonPost}, #{item.remark},
#{item.createdBy}, #{item.updatedBy}, NOW(), NOW()
)
</foreach>
</insert>
<resultMap id="CcdiIntermediaryEnterpriseRelationVOResult"
type="com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO">
<id property="id" column="id"/>
<result property="intermediaryBizId" column="intermediary_biz_id"/>
<result property="intermediaryName" column="intermediary_name"/>
<result property="intermediaryPersonId" column="intermediary_person_id"/>
<result property="socialCreditCode" column="social_credit_code"/>
<result property="enterpriseName" column="enterprise_name"/>
<result property="relationPersonPost" column="relation_person_post"/>
<result property="remark" column="remark"/>
<result property="createTime" column="create_time"/>
</resultMap>
<select id="selectByIntermediaryBizId" resultMap="CcdiIntermediaryEnterpriseRelationVOResult">
SELECT
rel.id,
rel.intermediary_biz_id,
parent.name AS intermediary_name,
parent.person_id AS intermediary_person_id,
rel.social_credit_code,
ent.enterprise_name,
rel.relation_person_post,
rel.remark,
rel.create_time
FROM ccdi_intermediary_enterprise_relation rel
INNER JOIN ccdi_biz_intermediary parent
ON rel.intermediary_biz_id = parent.biz_id
LEFT JOIN ccdi_enterprise_base_info ent
ON rel.social_credit_code = ent.social_credit_code
WHERE rel.intermediary_biz_id = #{bizId}
ORDER BY rel.create_time DESC
</select>
<select id="selectDetailById" resultMap="CcdiIntermediaryEnterpriseRelationVOResult">
SELECT
rel.id,
rel.intermediary_biz_id,
parent.name AS intermediary_name,
parent.person_id AS intermediary_person_id,
rel.social_credit_code,
ent.enterprise_name,
rel.relation_person_post,
rel.remark,
rel.create_time
FROM ccdi_intermediary_enterprise_relation rel
INNER JOIN ccdi_biz_intermediary parent
ON rel.intermediary_biz_id = parent.biz_id
LEFT JOIN ccdi_enterprise_base_info ent
ON rel.social_credit_code = ent.social_credit_code
WHERE rel.id = #{id}
</select>
<select id="existsByIntermediaryBizIdAndSocialCreditCode" resultType="boolean">
SELECT COUNT(1) > 0
FROM ccdi_intermediary_enterprise_relation
WHERE intermediary_biz_id = #{bizId}
AND social_credit_code = #{socialCreditCode}
</select>
<select id="batchExistsByCombinations" resultType="string">
SELECT CONCAT(intermediary_biz_id, '|', social_credit_code)
FROM ccdi_intermediary_enterprise_relation
WHERE CONCAT(intermediary_biz_id, '|', social_credit_code) IN
<foreach collection="combinations" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
</mapper>

View File

@@ -4,57 +4,86 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiIntermediaryMapper">
<!--
中介黑名单联合查询
支持按中介类型筛选: 1=个人中介, 2=实体中介, null=全部
使用MyBatis Plus分页插件自动处理分页
-->
<!-- 中介综合库联合查询 -->
<select id="selectIntermediaryList" resultType="com.ruoyi.info.collection.domain.vo.CcdiIntermediaryVO">
SELECT * FROM (
<!-- 查询个人中介 -->
SELECT
biz_id as id,
name,
person_id as certificate_no,
'1' as intermediary_type,
person_type,
company,
data_source,
create_time,
update_time
CAST('INTERMEDIARY' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS record_type,
biz_id COLLATE utf8mb4_general_ci AS record_id,
name COLLATE utf8mb4_general_ci AS name,
person_id COLLATE utf8mb4_general_ci AS certificate_no,
name COLLATE utf8mb4_general_ci AS related_intermediary_name,
CAST('本人' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS relation_text,
person_id COLLATE utf8mb4_general_ci AS related_intermediary_certificate_no,
create_time
FROM ccdi_biz_intermediary
WHERE person_sub_type COLLATE utf8mb4_general_ci = '本人' COLLATE utf8mb4_general_ci
UNION ALL
<!-- 查询实体中介 -->
SELECT
social_credit_code as id,
enterprise_name as name,
social_credit_code as certificate_no,
'2' as intermediary_type,
'实体' as person_type,
enterprise_name as company,
data_source,
create_time,
update_time
FROM ccdi_enterprise_base_info
WHERE risk_level = '1' AND ent_source = 'INTERMEDIARY'
CAST('RELATIVE' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS record_type,
child.biz_id COLLATE utf8mb4_general_ci AS record_id,
child.name COLLATE utf8mb4_general_ci AS name,
child.person_id COLLATE utf8mb4_general_ci AS certificate_no,
parent.name COLLATE utf8mb4_general_ci AS related_intermediary_name,
child.person_sub_type COLLATE utf8mb4_general_ci AS relation_text,
parent.person_id COLLATE utf8mb4_general_ci AS related_intermediary_certificate_no,
child.create_time
FROM ccdi_biz_intermediary child
INNER JOIN ccdi_biz_intermediary parent
ON child.related_num_id COLLATE utf8mb4_general_ci = parent.person_id COLLATE utf8mb4_general_ci
AND parent.person_sub_type COLLATE utf8mb4_general_ci = '本人' COLLATE utf8mb4_general_ci
WHERE child.person_sub_type IS NOT NULL
AND child.person_sub_type COLLATE utf8mb4_general_ci != '本人' COLLATE utf8mb4_general_ci
UNION ALL
SELECT
CAST('ENTERPRISE_RELATION' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS record_type,
CAST(rel.id AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci AS record_id,
COALESCE(
ent.enterprise_name COLLATE utf8mb4_general_ci,
rel.social_credit_code COLLATE utf8mb4_general_ci
) COLLATE utf8mb4_general_ci AS name,
rel.social_credit_code COLLATE utf8mb4_general_ci AS certificate_no,
parent.name COLLATE utf8mb4_general_ci AS related_intermediary_name,
COALESCE(
NULLIF(rel.relation_person_post COLLATE utf8mb4_general_ci, ''),
CAST('实体' AS CHAR CHARACTER SET utf8mb4) COLLATE utf8mb4_general_ci
) COLLATE utf8mb4_general_ci AS relation_text,
parent.person_id COLLATE utf8mb4_general_ci AS related_intermediary_certificate_no,
rel.create_time
FROM ccdi_intermediary_enterprise_relation rel
INNER JOIN ccdi_biz_intermediary parent
ON rel.intermediary_biz_id COLLATE utf8mb4_general_ci = parent.biz_id COLLATE utf8mb4_general_ci
AND parent.person_sub_type COLLATE utf8mb4_general_ci = '本人' COLLATE utf8mb4_general_ci
LEFT JOIN ccdi_enterprise_base_info ent
ON rel.social_credit_code COLLATE utf8mb4_general_ci = ent.social_credit_code COLLATE utf8mb4_general_ci
) AS combined_result
<where>
<!-- 按中介类型筛选 -->
<if test="query.intermediaryType != null and query.intermediaryType != ''">
AND intermediary_type = #{query.intermediaryType}
<if test="query.recordType != null and query.recordType != ''">
AND record_type COLLATE utf8mb4_general_ci =
CONVERT(#{query.recordType} USING utf8mb4) COLLATE utf8mb4_general_ci
</if>
<!-- 按姓名/机构名称模糊查询 -->
<if test="query.name != null and query.name != ''">
AND name LIKE CONCAT('%', #{query.name}, '%')
AND name COLLATE utf8mb4_general_ci LIKE
CONCAT('%', CONVERT(#{query.name} USING utf8mb4), '%') COLLATE utf8mb4_general_ci
</if>
<!-- 按证件号/统一社会信用代码精确查询 -->
<if test="query.certificateNo != null and query.certificateNo != ''">
AND certificate_no = #{query.certificateNo}
AND certificate_no COLLATE utf8mb4_general_ci =
CONVERT(#{query.certificateNo} USING utf8mb4) COLLATE utf8mb4_general_ci
</if>
<if test="query.relatedIntermediaryKeyword != null and query.relatedIntermediaryKeyword != ''">
AND (
related_intermediary_name COLLATE utf8mb4_general_ci LIKE
CONCAT('%', CONVERT(#{query.relatedIntermediaryKeyword} USING utf8mb4), '%') COLLATE utf8mb4_general_ci
OR related_intermediary_certificate_no COLLATE utf8mb4_general_ci LIKE
CONCAT('%', CONVERT(#{query.relatedIntermediaryKeyword} USING utf8mb4), '%') COLLATE utf8mb4_general_ci
)
</if>
</where>
ORDER BY update_time DESC
ORDER BY create_time DESC
</select>
</mapper>

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

@@ -0,0 +1,172 @@
package com.ruoyi.info.collection.controller;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.domain.vo.IntermediaryPersonImportFailureVO;
import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiIntermediaryControllerTest {
@InjectMocks
private CcdiIntermediaryController controller;
@Mock
private ICcdiIntermediaryService intermediaryService;
@Mock
private ICcdiIntermediaryPersonImportService personImportService;
@Mock
private ICcdiIntermediaryEnterpriseRelationImportService enterpriseRelationImportService;
@Test
void importPersonData_shouldReturnWarnWhenExcelHasNoRows() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"intermediary-empty.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"empty".getBytes(StandardCharsets.UTF_8)
);
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiIntermediaryPersonExcel.class)))
.thenReturn(List.of());
AjaxResult result = controller.importPersonData(file);
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
assertEquals("至少需要一条数据", result.get(AjaxResult.MSG_TAG));
}
}
@Test
void importPersonData_shouldReturnSuccessWhenTaskCreated() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"intermediary-person.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"person".getBytes(StandardCharsets.UTF_8)
);
CcdiIntermediaryPersonExcel excel = new CcdiIntermediaryPersonExcel();
excel.setPersonId("320101199001010011");
when(intermediaryService.importIntermediaryPerson(List.of(excel))).thenReturn("task-person");
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiIntermediaryPersonExcel.class)))
.thenReturn(List.of(excel));
AjaxResult result = controller.importPersonData(file);
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
ImportResultVO data = (ImportResultVO) result.get(AjaxResult.DATA_TAG);
assertEquals("task-person", data.getTaskId());
}
}
@Test
void importEnterpriseRelationData_shouldReturnSuccessWhenTaskCreated() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file",
"intermediary-relation.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"relation".getBytes(StandardCharsets.UTF_8)
);
CcdiIntermediaryEnterpriseRelationExcel excel = new CcdiIntermediaryEnterpriseRelationExcel();
excel.setOwnerPersonId("320101199001010011");
excel.setSocialCreditCode("91330100MA27X12345");
when(intermediaryService.importIntermediaryEnterpriseRelation(List.of(excel))).thenReturn("task-relation");
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiIntermediaryEnterpriseRelationExcel.class)))
.thenReturn(List.of(excel));
AjaxResult result = controller.importEnterpriseRelationData(file);
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
ImportResultVO data = (ImportResultVO) result.get(AjaxResult.DATA_TAG);
assertEquals("task-relation", data.getTaskId());
}
}
@Test
void getEnterpriseRelationImportStatus_shouldDelegateToRelationImportService() {
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId("task-status");
when(enterpriseRelationImportService.getImportStatus("task-status")).thenReturn(statusVO);
AjaxResult result = controller.getEnterpriseRelationImportStatus("task-status");
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
assertEquals(statusVO, result.get(AjaxResult.DATA_TAG));
}
@Test
void getEnterpriseRelationImportFailures_shouldReturnPagedRows() {
IntermediaryEnterpriseRelationImportFailureVO failure1 = new IntermediaryEnterpriseRelationImportFailureVO();
failure1.setOwnerPersonId("A1");
IntermediaryEnterpriseRelationImportFailureVO failure2 = new IntermediaryEnterpriseRelationImportFailureVO();
failure2.setOwnerPersonId("A2");
when(enterpriseRelationImportService.getImportFailures("task-failures")).thenReturn(List.of(failure1, failure2));
TableDataInfo result = controller.getEnterpriseRelationImportFailures("task-failures", 2, 1);
assertEquals(2, result.getTotal());
assertEquals(1, result.getRows().size());
assertEquals("A2", ((IntermediaryEnterpriseRelationImportFailureVO) result.getRows().get(0)).getOwnerPersonId());
}
@Test
void getPersonImportFailures_shouldReturnPagedRows() {
IntermediaryPersonImportFailureVO failure1 = new IntermediaryPersonImportFailureVO();
failure1.setPersonId("A1");
IntermediaryPersonImportFailureVO failure2 = new IntermediaryPersonImportFailureVO();
failure2.setPersonId("A2");
when(personImportService.getImportFailures("task-person-failures")).thenReturn(List.of(failure1, failure2));
TableDataInfo result = controller.getPersonImportFailures("task-person-failures", 2, 1);
assertEquals(2, result.getTotal());
assertEquals(1, result.getRows().size());
assertEquals("A2", ((IntermediaryPersonImportFailureVO) result.getRows().get(0)).getPersonId());
}
@Test
void importEnterpriseRelationTemplate_shouldUseRelationTemplateName() {
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
controller.importEnterpriseRelationTemplate(null);
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(
null,
CcdiIntermediaryEnterpriseRelationExcel.class,
"中介实体关联关系信息"
));
}
}
}

View File

@@ -0,0 +1,109 @@
package com.ruoyi.info.collection.mapper;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoQueryDTO;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.ibatis.type.TypeAliasRegistry;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CcdiAccountInfoMapperTest {
private static final String RESOURCE = "mapper/info/collection/CcdiAccountInfoMapper.xml";
@Test
void selectAccountInfoPage_shouldReadAnalysisColumnsFromAccountInfoTableOnly() throws Exception {
MappedStatement mappedStatement = loadMappedStatement(
"com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper.selectAccountInfoPage");
String sql = renderSql(mappedStatement, Map.of("query", new CcdiAccountInfoQueryDTO())).toLowerCase();
assertTrue(sql.contains("from ccdi_account_info ai"), sql);
assertFalse(sql.contains("ccdi_account_result"), sql);
assertTrue(sql.contains("ai.is_self_account as isactualcontrol"), sql);
assertTrue(sql.contains("ai.monthly_avg_trans_count as avgmonthtxncount"), sql);
assertTrue(sql.contains("ai.trans_risk_level as txnrisklevel"), sql);
}
private MappedStatement loadMappedStatement(String statementId) throws Exception {
Configuration configuration = new Configuration();
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));
registerTypeAliases(configuration.getTypeAliasRegistry());
configuration.getLanguageRegistry().register(XMLLanguageDriver.class);
configuration.addMapper(CcdiAccountInfoMapper.class);
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
XMLMapperBuilder xmlMapperBuilder =
new XMLMapperBuilder(inputStream, configuration, RESOURCE, configuration.getSqlFragments());
xmlMapperBuilder.parse();
}
return configuration.getMappedStatement(statementId);
}
private String renderSql(MappedStatement mappedStatement, Map<String, Object> params) {
BoundSql boundSql = mappedStatement.getBoundSql(new HashMap<>(params));
return boundSql.getSql().replaceAll("\\s+", " ").trim();
}
private void registerTypeAliases(TypeAliasRegistry typeAliasRegistry) {
typeAliasRegistry.registerAlias("map", Map.class);
}
private static class NoOpDataSource implements DataSource {
@Override
public java.sql.Connection getConnection() {
throw new UnsupportedOperationException("Not required for SQL rendering tests");
}
@Override
public java.sql.Connection getConnection(String username, String password) {
throw new UnsupportedOperationException("Not required for SQL rendering tests");
}
@Override
public java.io.PrintWriter getLogWriter() {
return null;
}
@Override
public void setLogWriter(java.io.PrintWriter out) {
}
@Override
public void setLoginTimeout(int seconds) {
}
@Override
public int getLoginTimeout() {
return 0;
}
@Override
public java.util.logging.Logger getParentLogger() {
return java.util.logging.Logger.getGlobal();
}
@Override
public <T> T unwrap(Class<T> iface) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return false;
}
}
}

View File

@@ -17,6 +17,8 @@ class CcdiBaseStaffMapperTest {
assertTrue(xml.contains("annual_income"), xml);
assertTrue(xml.contains("#{item.annualIncome}"), xml);
assertTrue(xml.contains("is_party_member"), xml);
assertTrue(xml.contains("#{item.partyMember}"), xml);
}
}
}

View File

@@ -0,0 +1,124 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiAccountInfo;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.dto.CcdiAccountInfoAddDTO;
import com.ruoyi.info.collection.mapper.CcdiAccountInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.service.impl.CcdiAccountInfoServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.BeanWrapperImpl;
import java.math.BigDecimal;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiAccountInfoServiceImplTest {
@InjectMocks
private CcdiAccountInfoServiceImpl service;
@Mock
private CcdiAccountInfoMapper accountInfoMapper;
@Mock
private CcdiBaseStaffMapper baseStaffMapper;
@Mock
private CcdiStaffFmyRelationMapper staffFmyRelationMapper;
@Test
void insertExternalAccount_shouldPersistAnalysisFieldsOnAccountInfo() {
CcdiAccountInfoAddDTO dto = buildBaseAddDto();
dto.setOwnerType("EXTERNAL");
dto.setOwnerId("330101199001010011");
dto.setBankScope("EXTERNAL");
dto.setIsActualControl(0);
dto.setAvgMonthTxnCount(6);
dto.setAvgMonthTxnAmount(new BigDecimal("1234.56"));
dto.setTxnFrequencyLevel("HIGH");
dto.setDebitSingleMaxAmount(new BigDecimal("100.00"));
dto.setCreditSingleMaxAmount(new BigDecimal("200.00"));
dto.setDebitDailyMaxAmount(new BigDecimal("300.00"));
dto.setCreditDailyMaxAmount(new BigDecimal("400.00"));
dto.setTxnRiskLevel("MEDIUM");
when(accountInfoMapper.selectCount(any())).thenReturn(0L);
when(accountInfoMapper.insert(any(CcdiAccountInfo.class))).thenReturn(1);
service.insertAccountInfo(dto);
ArgumentCaptor<CcdiAccountInfo> captor = ArgumentCaptor.forClass(CcdiAccountInfo.class);
verify(accountInfoMapper).insert(captor.capture());
BeanWrapperImpl wrapper = new BeanWrapperImpl(captor.getValue());
assertEquals(0, wrapper.getPropertyValue("isActualControl"));
assertEquals(6, wrapper.getPropertyValue("avgMonthTxnCount"));
assertEquals(new BigDecimal("1234.56"), wrapper.getPropertyValue("avgMonthTxnAmount"));
assertEquals("HIGH", wrapper.getPropertyValue("txnFrequencyLevel"));
assertEquals("MEDIUM", wrapper.getPropertyValue("txnRiskLevel"));
}
@Test
void insertInternalAccount_shouldClearAnalysisFieldsOnAccountInfo() {
CcdiAccountInfoAddDTO dto = buildBaseAddDto();
dto.setOwnerType("EMPLOYEE");
dto.setOwnerId("330101199001010022");
dto.setBankScope("INTERNAL");
dto.setIsActualControl(1);
dto.setAvgMonthTxnCount(8);
dto.setAvgMonthTxnAmount(new BigDecimal("9988.66"));
dto.setTxnFrequencyLevel("HIGH");
dto.setDebitSingleMaxAmount(new BigDecimal("111.11"));
dto.setCreditSingleMaxAmount(new BigDecimal("222.22"));
dto.setDebitDailyMaxAmount(new BigDecimal("333.33"));
dto.setCreditDailyMaxAmount(new BigDecimal("444.44"));
dto.setTxnRiskLevel("HIGH");
CcdiBaseStaff staff = new CcdiBaseStaff();
staff.setIdCard(dto.getOwnerId());
when(baseStaffMapper.selectOne(any())).thenReturn(staff);
when(accountInfoMapper.selectCount(any())).thenReturn(0L);
when(accountInfoMapper.insert(any(CcdiAccountInfo.class))).thenReturn(1);
service.insertAccountInfo(dto);
ArgumentCaptor<CcdiAccountInfo> captor = ArgumentCaptor.forClass(CcdiAccountInfo.class);
verify(accountInfoMapper).insert(captor.capture());
BeanWrapperImpl wrapper = new BeanWrapperImpl(captor.getValue());
assertNull(wrapper.getPropertyValue("isActualControl"));
assertNull(wrapper.getPropertyValue("avgMonthTxnCount"));
assertNull(wrapper.getPropertyValue("avgMonthTxnAmount"));
assertNull(wrapper.getPropertyValue("txnFrequencyLevel"));
assertNull(wrapper.getPropertyValue("debitSingleMaxAmount"));
assertNull(wrapper.getPropertyValue("creditSingleMaxAmount"));
assertNull(wrapper.getPropertyValue("debitDailyMaxAmount"));
assertNull(wrapper.getPropertyValue("creditDailyMaxAmount"));
assertNull(wrapper.getPropertyValue("txnRiskLevel"));
}
private CcdiAccountInfoAddDTO buildBaseAddDto() {
CcdiAccountInfoAddDTO dto = new CcdiAccountInfoAddDTO();
dto.setAccountNo("6222024000000001");
dto.setAccountType("BANK");
dto.setAccountName("测试账户");
dto.setOpenBank("中国银行");
dto.setBankCode("BOC");
dto.setCurrency("CNY");
dto.setStatus(1);
dto.setEffectiveDate(new Date());
return dto;
}
}

View File

@@ -27,6 +27,28 @@ class CcdiBaseStaffImportServiceImplTest {
assertDoesNotThrow(() -> service.validateStaffData(buildDto(new BigDecimal("12345.67")), false, Collections.emptySet(), Collections.emptySet()));
}
@Test
void validateStaffData_shouldAllowPartyMemberValuesZeroAndOne() {
CcdiBaseStaffAddDTO nonPartyMember = buildDto(null);
nonPartyMember.setPartyMember(0);
CcdiBaseStaffAddDTO partyMember = buildDto(null);
partyMember.setPartyMember(1);
assertDoesNotThrow(() -> service.validateStaffData(nonPartyMember, false, Collections.emptySet(), Collections.emptySet()));
assertDoesNotThrow(() -> service.validateStaffData(partyMember, false, Collections.emptySet(), Collections.emptySet()));
}
@Test
void validateStaffData_shouldRejectInvalidPartyMemberValue() {
CcdiBaseStaffAddDTO dto = buildDto(null);
dto.setPartyMember(2);
RuntimeException exception = assertThrows(RuntimeException.class,
() -> service.validateStaffData(dto, false, Set.of(), Set.of()));
assertEquals("是否党员只能填写'0'或'1'", exception.getMessage());
}
@Test
void validateStaffData_shouldRejectNegativeAnnualIncome() {
RuntimeException exception = assertThrows(RuntimeException.class,
@@ -51,6 +73,7 @@ class CcdiBaseStaffImportServiceImplTest {
dto.setIdCard("320101199001010014");
dto.setPhone("13812345678");
dto.setStatus("0");
dto.setPartyMember(1);
dto.setAnnualIncome(annualIncome);
return dto;
}

View File

@@ -55,6 +55,7 @@ class CcdiBaseStaffServiceImplTest {
addDTO.setIdCard("320101199001010011");
addDTO.setPhone("13812345678");
addDTO.setStatus("0");
addDTO.setPartyMember(1);
addDTO.setAnnualIncome(new BigDecimal("12345.67"));
addDTO.setAssetInfoList(List.of(
buildAssetDto("房产"),
@@ -70,6 +71,7 @@ class CcdiBaseStaffServiceImplTest {
assertEquals(1, result);
ArgumentCaptor<CcdiBaseStaff> staffCaptor = ArgumentCaptor.forClass(CcdiBaseStaff.class);
verify(baseStaffMapper).insert(staffCaptor.capture());
assertEquals(1, staffCaptor.getValue().getPartyMember());
assertEquals(new BigDecimal("12345.67"), staffCaptor.getValue().getAnnualIncome());
ArgumentCaptor<List<CcdiAssetInfoDTO>> captor = ArgumentCaptor.forClass(List.class);
verify(assetInfoService).replaceByFamilyId(eq("320101199001010011"), captor.capture());
@@ -92,6 +94,7 @@ class CcdiBaseStaffServiceImplTest {
editDTO.setIdCard("320101199001010011");
editDTO.setPhone("13812345678");
editDTO.setStatus("0");
editDTO.setPartyMember(0);
editDTO.setAnnualIncome(new BigDecimal("45678.90"));
editDTO.setAssetInfoList(List.of(buildAssetDto("车辆")));
@@ -104,6 +107,7 @@ class CcdiBaseStaffServiceImplTest {
assertEquals(1, result);
ArgumentCaptor<CcdiBaseStaff> staffCaptor = ArgumentCaptor.forClass(CcdiBaseStaff.class);
verify(baseStaffMapper).updateById(staffCaptor.capture());
assertEquals(0, staffCaptor.getValue().getPartyMember());
assertEquals(new BigDecimal("45678.90"), staffCaptor.getValue().getAnnualIncome());
verify(assetInfoService, never()).deleteByFamilyId("320101199001010011");
verify(assetInfoService).replaceByFamilyId("320101199001010011", editDTO.getAssetInfoList());
@@ -122,6 +126,7 @@ class CcdiBaseStaffServiceImplTest {
editDTO.setIdCard("320101199001010011");
editDTO.setPhone("13812345678");
editDTO.setStatus("0");
editDTO.setPartyMember(1);
editDTO.setAssetInfoList(List.of(buildAssetDto("车辆")));
when(baseStaffMapper.selectById(1001L)).thenReturn(existing);
@@ -135,17 +140,18 @@ class CcdiBaseStaffServiceImplTest {
}
@Test
void selectBaseStaffById_shouldReturnAssetInfoList() {
void selectBaseStaffById_shouldReturnSelfOwnedAssetInfoList() {
CcdiBaseStaff staff = new CcdiBaseStaff();
staff.setStaffId(1001L);
staff.setName("张三");
staff.setIdCard("320101199001010011");
staff.setStatus("0");
staff.setPartyMember(1);
staff.setAnnualIncome(new BigDecimal("88888.88"));
CcdiAssetInfo assetInfo = new CcdiAssetInfo();
assetInfo.setFamilyId("320101199001010011");
assetInfo.setPersonId("320101199201010022");
assetInfo.setPersonId("320101199001010011");
assetInfo.setAssetMainType("车辆");
assetInfo.setAssetSubType("小汽车");
assetInfo.setAssetName("家庭车辆");
@@ -153,14 +159,16 @@ class CcdiBaseStaffServiceImplTest {
assetInfo.setAssetStatus("正常");
when(baseStaffMapper.selectById(1001L)).thenReturn(staff);
when(assetInfoService.selectByFamilyId("320101199001010011")).thenReturn(List.of(assetInfo));
when(assetInfoService.selectByFamilyIdAndPersonId("320101199001010011", "320101199001010011"))
.thenReturn(List.of(assetInfo));
CcdiBaseStaffVO result = service.selectBaseStaffById(1001L);
assertNotNull(result.getAssetInfoList());
assertEquals(1, result.getPartyMember());
assertEquals(new BigDecimal("88888.88"), result.getAnnualIncome());
assertEquals(1, result.getAssetInfoList().size());
assertEquals("320101199201010022", result.getAssetInfoList().get(0).getPersonId());
assertEquals("320101199001010011", result.getAssetInfoList().get(0).getPersonId());
assertEquals("车辆", result.getAssetInfoList().get(0).getAssetMainType());
}

View File

@@ -0,0 +1,172 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.impl.CcdiIntermediaryEnterpriseRelationImportServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
@InjectMocks
private CcdiIntermediaryEnterpriseRelationImportServiceImpl service;
@Mock
private CcdiIntermediaryEnterpriseRelationMapper relationMapper;
@Mock
private CcdiBizIntermediaryMapper intermediaryMapper;
@Mock
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Mock
private HashOperations<String, Object, Object> hashOperations;
@Mock
private ValueOperations<String, Object> valueOperations;
@Test
void importEnterpriseRelationAsync_shouldFailWhenOwnerPersonIdDoesNotExist() {
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
prepareFailureRedisMocks();
when(intermediaryMapper.selectList(any())).thenReturn(List.of());
when(enterpriseBaseInfoMapper.selectList(any())).thenReturn(List.of(enterprise("91330100MA27X12345")));
service.importAsync(List.of(excel), "task-owner-miss", "tester");
verify(relationMapper, never()).insertBatch(any());
IntermediaryEnterpriseRelationImportFailureVO failure =
firstFailure("import:intermediary-enterprise-relation:task-owner-miss:failures");
assertEquals("320101199001010014", failure.getOwnerPersonId());
assertTrue(failure.getErrorMessage().contains("中介本人不存在"));
}
@Test
void importEnterpriseRelationAsync_shouldFailWhenEnterpriseDoesNotExist() {
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
prepareFailureRedisMocks();
when(intermediaryMapper.selectList(any())).thenReturn(List.of(owner("owner-biz", "320101199001010014")));
when(enterpriseBaseInfoMapper.selectList(any())).thenReturn(List.of());
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of());
service.importAsync(List.of(excel), "task-ent-miss", "tester");
verify(relationMapper, never()).insertBatch(any());
IntermediaryEnterpriseRelationImportFailureVO failure =
firstFailure("import:intermediary-enterprise-relation:task-ent-miss:failures");
assertEquals("91330100MA27X12345", failure.getSocialCreditCode());
assertTrue(failure.getErrorMessage().contains("机构表"));
}
@Test
void importEnterpriseRelationAsync_shouldRejectFileDuplicateAndDbDuplicate() {
CcdiIntermediaryEnterpriseRelationExcel duplicateInDb = buildExcel("320101199001010014", "91330100MA27X12345");
CcdiIntermediaryEnterpriseRelationExcel duplicateInFile1 = buildExcel("320101199003030035", "91330100MA27X12346");
CcdiIntermediaryEnterpriseRelationExcel duplicateInFile2 = buildExcel("320101199003030035", "91330100MA27X12346");
prepareFailureRedisMocks();
when(intermediaryMapper.selectList(any())).thenReturn(List.of(
owner("owner-biz-1", "320101199001010014"),
owner("owner-biz-2", "320101199003030035")
));
when(enterpriseBaseInfoMapper.selectList(any())).thenReturn(List.of(
enterprise("91330100MA27X12345"),
enterprise("91330100MA27X12346")
));
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of("owner-biz-1|91330100MA27X12345"));
service.importAsync(List.of(duplicateInDb, duplicateInFile1, duplicateInFile2), "task-duplicate", "tester");
ArgumentCaptor<List<CcdiIntermediaryEnterpriseRelation>> captor = ArgumentCaptor.forClass(List.class);
verify(relationMapper).insertBatch(captor.capture());
assertEquals(1, captor.getValue().size());
assertEquals("owner-biz-2", captor.getValue().get(0).getIntermediaryBizId());
IntermediaryEnterpriseRelationImportFailureVO failure =
firstFailure("import:intermediary-enterprise-relation:task-duplicate:failures");
assertTrue(failure.getErrorMessage().contains("重复") || failure.getErrorMessage().contains("已存在"));
}
@Test
void importEnterpriseRelationAsync_shouldImportSuccessWhenOwnerAndEnterpriseExist() {
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
prepareStatusRedisMock();
when(intermediaryMapper.selectList(any())).thenReturn(List.of(owner("owner-biz", "320101199001010014")));
when(enterpriseBaseInfoMapper.selectList(any())).thenReturn(List.of(enterprise("91330100MA27X12345")));
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of());
service.importAsync(List.of(excel), "task-success", "tester");
ArgumentCaptor<List<CcdiIntermediaryEnterpriseRelation>> captor = ArgumentCaptor.forClass(List.class);
verify(relationMapper).insertBatch(captor.capture());
assertEquals(1, captor.getValue().size());
assertEquals("owner-biz", captor.getValue().get(0).getIntermediaryBizId());
verify(valueOperations, never()).set(any(), any(), any(Long.class), any(TimeUnit.class));
}
private void prepareStatusRedisMock() {
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
}
private void prepareFailureRedisMocks() {
prepareStatusRedisMock();
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
}
private IntermediaryEnterpriseRelationImportFailureVO firstFailure(String key) {
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
verify(valueOperations).set(org.mockito.ArgumentMatchers.eq(key), failureCaptor.capture(),
org.mockito.ArgumentMatchers.eq(7L), org.mockito.ArgumentMatchers.eq(TimeUnit.DAYS));
return (IntermediaryEnterpriseRelationImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
}
private CcdiIntermediaryEnterpriseRelationExcel buildExcel(String ownerPersonId, String socialCreditCode) {
CcdiIntermediaryEnterpriseRelationExcel excel = new CcdiIntermediaryEnterpriseRelationExcel();
excel.setOwnerPersonId(ownerPersonId);
excel.setSocialCreditCode(socialCreditCode);
excel.setRelationPersonPost("董事");
excel.setRemark("备注");
return excel;
}
private CcdiBizIntermediary owner(String bizId, String personId) {
CcdiBizIntermediary owner = new CcdiBizIntermediary();
owner.setBizId(bizId);
owner.setPersonId(personId);
owner.setPersonSubType("本人");
return owner;
}
private CcdiEnterpriseBaseInfo enterprise(String socialCreditCode) {
CcdiEnterpriseBaseInfo enterprise = new CcdiEnterpriseBaseInfo();
enterprise.setSocialCreditCode(socialCreditCode);
enterprise.setEnterpriseName("机构" + socialCreditCode.substring(socialCreditCode.length() - 2));
return enterprise;
}
}

View File

@@ -0,0 +1,176 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.common.annotation.DictDropdown;
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.IntermediaryPersonImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
import com.ruoyi.info.collection.service.impl.CcdiIntermediaryPersonImportServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiIntermediaryPersonImportServiceImplTest {
@InjectMocks
private CcdiIntermediaryPersonImportServiceImpl service;
@Mock
private CcdiBizIntermediaryMapper intermediaryMapper;
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Mock
private HashOperations<String, Object, Object> hashOperations;
@Mock
private ValueOperations<String, Object> valueOperations;
@Test
void intermediaryPersonExcel_shouldUsePersonSubTypeDropdownAndDropRelationType() throws Exception {
Field personSubTypeField = CcdiIntermediaryPersonExcel.class.getDeclaredField("personSubType");
DictDropdown dictDropdown = personSubTypeField.getAnnotation(DictDropdown.class);
assertNotNull(dictDropdown);
assertEquals("ccdi_person_sub_type", dictDropdown.dictType());
assertThrows(NoSuchFieldException.class, () -> CcdiIntermediaryPersonExcel.class.getDeclaredField("relationType"));
assertThrows(NoSuchFieldException.class, () -> IntermediaryPersonImportFailureVO.class.getDeclaredField("relationType"));
}
@Test
void importPersonAsync_shouldFailWhenOwnerRowContainsOwnerPersonIdReference() {
CcdiIntermediaryPersonExcel owner = buildOwnerExcel("320101199001010014");
owner.setRelatedNumId("320101199105050053");
prepareFailureRedisMocks();
service.importPersonAsync(List.of(owner), "task-owner", "tester");
verify(intermediaryMapper, never()).insertBatch(any());
IntermediaryPersonImportFailureVO failure = firstFailure("import:intermediary:task-owner:failures");
assertEquals("320101199105050053", failure.getRelatedNumId());
assertTrue(failure.getErrorMessage().contains("本人"));
}
@Test
void importPersonAsync_shouldFailWhenRelativeRowMissesOwnerPersonId() {
CcdiIntermediaryPersonExcel relative = buildRelativeExcel("320101199201010027", "配偶", null);
prepareFailureRedisMocks();
service.importPersonAsync(List.of(relative), "task-relative", "tester");
verify(intermediaryMapper, never()).insertBatch(any());
IntermediaryPersonImportFailureVO failure = firstFailure("import:intermediary:task-relative:failures");
assertEquals("320101199201010027", failure.getPersonId());
assertTrue(failure.getErrorMessage().contains("关联中介本人证件号码"));
}
@Test
void importPersonAsync_shouldAllowRelativeReferencingSuccessfulOwnerInSameFile() {
CcdiIntermediaryPersonExcel owner = buildOwnerExcel("320101199001010014");
CcdiIntermediaryPersonExcel relative = buildRelativeExcel("320101199201010027", "配偶", "320101199001010014");
prepareStatusRedisMock();
when(intermediaryMapper.selectList(any())).thenReturn(List.of());
service.importPersonAsync(List.of(owner, relative), "task-mixed", "tester");
ArgumentCaptor<List<CcdiBizIntermediary>> captor = ArgumentCaptor.forClass(List.class);
verify(intermediaryMapper).insertBatch(captor.capture());
assertEquals(2, captor.getValue().size());
assertEquals("本人", captor.getValue().get(0).getPersonSubType());
assertEquals("320101199001010014", captor.getValue().get(1).getRelatedNumId());
verify(valueOperations, never()).set(any(), any(), any(Long.class), any(TimeUnit.class));
}
@Test
void importPersonAsync_shouldAllowSameRelativePersonIdUnderDifferentOwners() {
CcdiIntermediaryPersonExcel owner1 = buildOwnerExcel("320101199001010014");
CcdiIntermediaryPersonExcel owner2 = buildOwnerExcel("320101199003030035");
CcdiIntermediaryPersonExcel relative1 = buildRelativeExcel("320101199201010027", "配偶", "320101199001010014");
CcdiIntermediaryPersonExcel relative2 = buildRelativeExcel("320101199201010027", "配偶", "320101199003030035");
prepareStatusRedisMock();
when(intermediaryMapper.selectList(any())).thenReturn(List.of());
service.importPersonAsync(List.of(owner1, owner2, relative1, relative2), "task-owner-scope", "tester");
ArgumentCaptor<List<CcdiBizIntermediary>> captor = ArgumentCaptor.forClass(List.class);
verify(intermediaryMapper).insertBatch(captor.capture());
assertEquals(4, captor.getValue().size());
verify(valueOperations, never()).set(any(), any(), any(Long.class), any(TimeUnit.class));
}
@Test
void importPersonAsync_shouldRejectDuplicateRelativeUnderSameOwner() {
CcdiIntermediaryPersonExcel owner = buildOwnerExcel("320101199001010014");
CcdiIntermediaryPersonExcel relative1 = buildRelativeExcel("320101199201010027", "配偶", "320101199001010014");
CcdiIntermediaryPersonExcel relative2 = buildRelativeExcel("320101199201010027", "配偶", "320101199001010014");
prepareFailureRedisMocks();
when(intermediaryMapper.selectList(any())).thenReturn(List.of());
service.importPersonAsync(List.of(owner, relative1, relative2), "task-duplicate", "tester");
ArgumentCaptor<List<CcdiBizIntermediary>> captor = ArgumentCaptor.forClass(List.class);
verify(intermediaryMapper).insertBatch(captor.capture());
assertEquals(2, captor.getValue().size());
IntermediaryPersonImportFailureVO failure = firstFailure("import:intermediary:task-duplicate:failures");
assertEquals("320101199201010027", failure.getPersonId());
assertTrue(failure.getErrorMessage().contains("重复"));
}
private void prepareStatusRedisMock() {
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
}
private void prepareFailureRedisMocks() {
prepareStatusRedisMock();
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
}
private IntermediaryPersonImportFailureVO firstFailure(String key) {
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
verify(valueOperations).set(org.mockito.ArgumentMatchers.eq(key), failureCaptor.capture(),
org.mockito.ArgumentMatchers.eq(7L), org.mockito.ArgumentMatchers.eq(TimeUnit.DAYS));
return (IntermediaryPersonImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
}
private CcdiIntermediaryPersonExcel buildOwnerExcel(String personId) {
CcdiIntermediaryPersonExcel excel = new CcdiIntermediaryPersonExcel();
excel.setName("中介本人" + personId.substring(personId.length() - 2));
excel.setPersonType("中介");
excel.setPersonSubType("本人");
excel.setIdType("身份证");
excel.setPersonId(personId);
return excel;
}
private CcdiIntermediaryPersonExcel buildRelativeExcel(String personId, String personSubType, String ownerPersonId) {
CcdiIntermediaryPersonExcel excel = new CcdiIntermediaryPersonExcel();
excel.setName("中介亲属" + personId.substring(personId.length() - 2));
excel.setPersonType("中介");
excel.setPersonSubType(personSubType);
excel.setIdType("身份证");
excel.setPersonId(personId);
excel.setRelatedNumId(ownerPersonId);
return excel;
}
}

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