Compare commits

32 Commits

Author SHA1 Message Date
wkc
aa08ab4711 员工亲属实体关联 2026-04-24 13:29:13 +08:00
wkc
b7db711906 完成员工亲属实体关联改造并清理旧数据 2026-04-24 08:55:05 +08:00
wkc
b7d020c0b2 调整实体库管理数据来源维护规则 2026-04-23 17:31:56 +08:00
wkc
d444eafd5f 调整征信记录维护页面搜索区并去掉顶部标题 2026-04-23 17:24:52 +08:00
wkc
c9398881f3 新增员工亲属实体关联维护实施计划 2026-04-23 16:52:15 +08:00
wkc
d79a60ab8c 新增员工亲属实体关联维护设计文档 2026-04-23 16:47:26 +08:00
wkc
e9403662e2 新增信息维护页面搜索区四列栅格前端实施计划 2026-04-23 16:40:06 +08:00
wkc
fd79bfe62f 新增信息维护页面搜索区四列栅格统一设计文档 2026-04-23 16:21:13 +08:00
wkc
c7f4982451 补充招投标供应商企业详情查看 2026-04-23 16:12:56 +08:00
wkc
0b2571b962 统一信息维护页面头部按钮布局 2026-04-23 16:07:17 +08:00
wkc
129e44c808 新增信息维护页面头部按钮统一前端实施计划 2026-04-23 15:53:32 +08:00
wkc
50c177da78 新增信息维护页面头部按钮统一设计文档 2026-04-23 15:50:44 +08:00
wkc
c660025bcc 完善招投标供应商企业详情设计与计划 2026-04-23 15:40:42 +08:00
wkc
bd51991248 新增招投标供应商企业详情设计文档 2026-04-23 15:18:12 +08:00
wkc
a2ba044ebe 完成招聘双Sheet导入改造 2026-04-23 10:27:08 +08:00
wkc
110817abba 新增招聘信息双Sheet导入实施计划 2026-04-23 09:45:50 +08:00
wkc
ff9627d0d9 新增招聘信息双Sheet导入设计文档 2026-04-23 09:40:15 +08:00
wkc
2d1b02474c Add import page test guidelines 2026-04-22 16:38:52 +08:00
wkc
5a9b79d4ee 完善招投标导入测试与文档 2026-04-22 16:20:37 +08:00
wkc
0c5fa6b2c8 Remove obsolete export APIs and persist recruitment work history 2026-04-22 13:38:43 +08:00
wkc
94507e3747 Unify staff recruitment work table collation 2026-04-22 10:07:05 +08:00
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
284 changed files with 18668 additions and 2950 deletions

BIN
.DS_Store vendored

Binary file not shown.

6
.gitignore vendored
View File

@@ -88,3 +88,9 @@ ruoyi-ui/vue.config.js
.pytest_cache/ .pytest_cache/
tests/ tests/
tongweb_62318.properties
.superpowers/
tmp/

View File

@@ -15,21 +15,58 @@
--- ---
## 协作约定 ## 高优先级规则
- 使用简体中文进行思考和对话 - 使用简体中文进行思考和对话
- Git 提交说明使用中文 - Git 提交说明必须使用中文
- Git 提交前必须检查暂存区,仅允许包含本次任务相关文件 - 忽略 `.DS_Store` 文件,不将其视为本次任务需要处理或提交的有效变更
- 若暂存区存在无关文件,必须先移出暂存或与用户确认,禁止顺带提交 - 仅当用户明确声明调用 `using-superpowers` 时才允许启用;未明确声明时按普通流程直接处理需求
- 根据设计文档产出实施计划时,默认输出两份文档: - Git 提交前必须检查暂存区,仅允许包含本次任务相关文件;若存在无关文件,必须先移出暂存或与用户确认
- 后端实施计划放 `docs/plans/backend/` - 每一次改动都需要留下实施文档,记录修改内容、影响范围与验证情况
- 前端实施计划放 `docs/plans/frontend/` - 功能设计同时涉及前端和后端改动时,必须分别输出后端与前端两份实施计划;若仅涉及单侧,则只输出对应实施计划
- 新增或修改设计文档、实施计划、实施记录前,必须先确认保存路径是否正确
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
- 测试结束后,自动关闭测试过程中启动的前后端进程
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`
---
## 协作约定
### 基础协作
- 前端开发直接在当前分支进行,不需要额外创建 git worktree - 前端开发直接在当前分支进行,不需要额外创建 git worktree
- 给出方案时,必须保持最短路径实现,不允许提供兼容性、补丁性或过度设计的方案
- 不允许自行扩展出用户需求之外的兜底、降级或变体方案,避免业务逻辑偏移
- 输出方案前必须完成全链路逻辑校验,确保方案逻辑正确、链路闭环
### Git 与变更管理
- Git 提交前必须检查暂存区,仅保留本次任务相关文件
- 若暂存区存在无关文件,必须先移出暂存或与用户确认,禁止顺带提交
- `.DS_Store` 默认忽略,不纳入任务变更范围
### 文档产出
- 若需求来自设计文档,默认同时沉淀后端与前端两份实施计划
- 功能设计同时涉及前端和后端改动时,实施计划分别放在 `docs/plans/backend/``docs/plans/frontend/`
- 功能修改只涉及前端或只涉及后端时,只输出对应的实施计划
- 非前后端架构项目不强制拆分两份实施计划
- 每一次改动都需要留下实施文档,实施记录优先放在 `docs/reports/implementation/`
- 每次新增或修改设计文档、实施计划、实施记录前,都要先确认保存路径是否正确
### 测试与运行
- 测试结束后,自动关闭测试过程中启动的前后端进程 - 测试结束后,自动关闭测试过程中启动的前后端进程
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`,不要直接手工执行 `java -jar` 替代正式重启流程 - 重启后端时,必须优先使用 `bin/restart_java_backend.sh`,不要直接手工执行 `java -jar` 替代正式重启流程
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
### 数据库与编码
- 遇到 MCP 数据库操作时,使用项目配置文件中的数据库连接信息 - 遇到 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` 或其他混用排序规则 - 所有业务表、系统表新增或修改时,必须显式使用 `utf8mb4` 字符集与 `utf8mb4_general_ci` 排序规则
- 禁止引入 `utf8mb4_0900_ai_ci``utf8mb4_unicode_ci` 或其他混用排序规则
- 银行流水打标相关规则与参数编码需要统一使用全大写;新增或修改 `rule_code``indicator_code``param_code` 时,禁止混用大小写风格 - 银行流水打标相关规则与参数编码需要统一使用全大写;新增或修改 `rule_code``indicator_code``param_code` 时,禁止混用大小写风格
--- ---
@@ -43,7 +80,7 @@
mvn clean compile mvn clean compile
# 启动主应用Jar # 启动主应用Jar
cd ruoyi-admin/target && java -jar ruoyi-admin.jar sh bin/restart_java_backend.sh
# 打包全部模块 # 打包全部模块
mvn clean package mvn clean package
@@ -63,6 +100,9 @@ mvn clean package -DskipTests
```bash ```bash
cd ruoyi-ui cd ruoyi-ui
# 使用 nvm 切换到项目所需 Node 版本
nvm use
# 安装依赖 # 安装依赖
npm install --registry=https://registry.npmmirror.com npm install --registry=https://registry.npmmirror.com
@@ -166,8 +206,10 @@ return AjaxResult.success(result);
- 非业务字段如 `create_by``create_time` 由后端自动维护 - 非业务字段如 `create_by``create_time` 由后端自动维护
- 前端表单不要暴露通用审计字段 - 前端表单不要暴露通用审计字段
- 新增菜单、字典、初始化数据时,同步补充 SQL 脚本 - 新增菜单、字典、初始化数据时,同步补充 SQL 脚本
- 执行数据库脚本或导入数据库前,需确认客户端会话字符集为 `utf8mb4`;涉及中文插入、更新、导入时默认使用 `bin/mysql_utf8_exec.sh` - 执行数据库脚本或导入数据库前,需确认客户端会话字符集为 `utf8mb4`
- 所有系统表和业务表的表级、字符字段级排序规则统一为 `utf8mb4_general_ci`;新增建表 SQL、字段追加 SQL、表结构修复 SQL 必须显式声明,避免因默认排序规则漂移导致联表或条件查询报错 - 涉及中文插入、更新、导入时默认使用 `bin/mysql_utf8_exec.sh`
- 所有系统表和业务表的表级、字符字段级排序规则统一为 `utf8mb4_general_ci`
- 新增建表 SQL、字段追加 SQL、表结构修复 SQL 必须显式声明字符集与排序规则,避免因默认排序规则漂移导致联表或条件查询报错
### 前端规范 ### 前端规范
@@ -183,6 +225,18 @@ return AjaxResult.success(result);
- 返回结果仅展示失败数据 - 返回结果仅展示失败数据
- 大数据量导入优先采用 EasyExcel + 异步处理 - 大数据量导入优先采用 EasyExcel + 异步处理
### 导入页面测试规范
- 导入功能测试必须进入真实业务页面执行,先在页面内下载当前导入模板,再基于该模板生成测试文件,禁止手工凭记忆新建表头或脱离页面直接构造上传文件
- 双 Sheet 模板的导入测试必须覆盖两个 Sheet 的联动关系;除“缺少 Sheet / 空 Sheet”专项场景外默认两个 Sheet 都要准备测试数据
- 导入测试文件优先放在 `output/spreadsheet/``output/playwright/`,不提交到 git
- 需要按场景拆分测试文件,避免多个互斥校验互相覆盖;至少覆盖空模板、主信息必填、主信息格式与金额、主从关系异常、供应商校验、缺少/空 Sheet、成功导入、成功与失败混合、失败记录查看、导入后清理回滚
- 主从关系异常测试至少覆盖:已存在主键、供应商有数据但主信息缺失、主信息重复、供应商 Sheet 中采购事项 ID 为空
- 供应商校验测试至少覆盖:重复供应商、多条中标、供应商名称为空、名称超长、联系人超长、银行账户超长、联系电话非法、统一信用代码非法、是否中标枚举非法
- 页面上传后必须核对页面提示、导入状态、失败记录弹窗和列表总数变化;异步导入场景还要核对任务状态从 `PROCESSING` 到最终状态的变化
- 对“成功导入 + 异常数据混合”的样本,必须额外核对成功数据是否真正入库、异常数据是否被拦截,以及是否存在被静默忽略的行
- 导入测试结束后,必须删除本轮成功写入的测试数据,清理页面本地导入任务缓存,并关闭测试过程中启动的前后端进程
--- ---
## 当前仓库结构 ## 当前仓库结构
@@ -228,15 +282,10 @@ ccdi/
### 主要业务代码分布 ### 主要业务代码分布
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/` - `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/` - `ccdi-project/src/main/java/com/ruoyi/ccdi/project/`
-`config``controller``domain``mapper``service`
- `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/` - `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/`
-`client``config``constants``controller``domain/request``domain/response`
- `ruoyi-ui/src/views/` - `ruoyi-ui/src/views/`
- 当前包含 `ccdi``ccdiBaseStaff``ccdiProject``ccdiPurchaseTransaction``ccdiIntermediary`、亲属关系、员工调动、招聘等业务页面
- `ruoyi-ui/src/api/ccdi/` - `ruoyi-ui/src/api/ccdi/`
- 放置纪检初核业务 API 封装
### 添加新后端模块时 ### 添加新后端模块时
@@ -297,6 +346,9 @@ ccdi/
- 只有历史资料或外部原始材料才放入 `assets/` - 只有历史资料或外部原始材料才放入 `assets/`
- 如果移动了文档,需同步修正文档内引用路径 - 如果移动了文档,需同步修正文档内引用路径
- 若需求来自设计文档,默认同时沉淀后端与前端两份实施计划 - 若需求来自设计文档,默认同时沉淀后端与前端两份实施计划
- 功能设计同时涉及前端和后端改动时,必须分别输出后端与前端两份实施计划;若仅涉及前端或仅涉及后端,则只输出对应实施计划;非前后端架构项目不强制拆分双文档
- 每一次改动都需要留下实施文档,记录本次修改内容、影响范围与验证情况,实施记录优先放在 `docs/reports/implementation/`
- 每次新增或修改设计文档、实施计划、实施记录前,都要先确认保存路径是否正确
--- ---
@@ -307,3 +359,4 @@ ccdi/
- `docker/backend``docker/frontend``docker/mock` 分别对应三类运行时镜像 - `docker/backend``docker/frontend``docker/mock` 分别对应三类运行时镜像
- `sql/migration/` 用于增量迁移脚本,新增修复脚本优先按日期或功能命名 - `sql/migration/` 用于增量迁移脚本,新增修复脚本优先按日期或功能命名
- 启动前后端或 Mock 服务做验证后,结束测试时要主动停止进程,避免残留占用端口 - 启动前后端或 Mock 服务做验证后,结束测试时要主动停止进程,避免残留占用端口
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本

View File

@@ -215,7 +215,7 @@ follow_logs() {
start_action() { start_action() {
running_pids=$(collect_pids) running_pids=$(collect_pids)
if [ -n "${running_pids:-}" ]; then if [ -n "${running_pids:-}" ]; then
log_error "检测到已有后端进程在运行: $running_pids,请先执行 stop 或 restart" log_error "检测到已有后端进程在运行: ${running_pids},请先执行 stop 或 restart"
exit 1 exit 1
fi fi

View File

@@ -80,18 +80,6 @@ public class CcdiAccountInfoController extends BaseController {
return success(accountInfoService.selectAccountInfoById(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, "账户库管理");
}
/** /**
* 新增账户 * 新增账户
*/ */

View File

@@ -4,8 +4,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel; import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import com.ruoyi.info.collection.domain.vo.*; import com.ruoyi.info.collection.domain.vo.*;
import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService;
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService; import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
import com.ruoyi.info.collection.service.ICcdiBaseStaffService; import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
import com.ruoyi.info.collection.utils.EasyExcelUtil; import com.ruoyi.info.collection.utils.EasyExcelUtil;
@@ -45,6 +47,9 @@ public class CcdiBaseStaffController extends BaseController {
@Resource @Resource
private ICcdiBaseStaffImportService importAsyncService; private ICcdiBaseStaffImportService importAsyncService;
@Resource
private ICcdiBaseStaffAssetImportService baseStaffAssetImportService;
/** /**
* 查询员工列表 * 查询员工列表
*/ */
@@ -70,18 +75,6 @@ public class CcdiBaseStaffController extends BaseController {
return success(list); return success(list);
} }
/**
* 导出员工列表
*/
@Operation(summary = "导出员工列表")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:export')")
@Log(title = "员工信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiBaseStaffQueryDTO queryDTO) {
List<CcdiBaseStaffExcel> list = baseStaffService.selectBaseStaffListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiBaseStaffExcel.class, "员工信息");
}
/** /**
* 获取员工详细信息 * 获取员工详细信息
*/ */
@@ -132,7 +125,14 @@ public class CcdiBaseStaffController extends BaseController {
@Operation(summary = "下载导入模板") @Operation(summary = "下载导入模板")
@PostMapping("/importTemplate") @PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) { public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiBaseStaffExcel.class, "员工信息"); EasyExcelUtil.importTemplateWithDictDropdown(
response,
CcdiBaseStaffExcel.class,
"员工信息",
CcdiBaseStaffAssetInfoExcel.class,
"员工资产信息",
"员工信息维护导入模板"
);
} }
/** /**
@@ -142,21 +142,33 @@ public class CcdiBaseStaffController extends BaseController {
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:import')") @PreAuthorize("@ss.hasPermi('ccdi:baseStaff:import')")
@Log(title = "员工信息", businessType = BusinessType.IMPORT) @Log(title = "员工信息", businessType = BusinessType.IMPORT)
@PostMapping("/importData") @PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { public AjaxResult importData(MultipartFile file) throws Exception {
List<CcdiBaseStaffExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiBaseStaffExcel.class); List<CcdiBaseStaffExcel> staffList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiBaseStaffExcel.class,
"员工信息"
);
List<CcdiBaseStaffAssetInfoExcel> assetList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiBaseStaffAssetInfoExcel.class,
"员工资产信息"
);
if (list == null || list.isEmpty()) { boolean hasStaffRows = staffList != null && !staffList.isEmpty();
boolean hasAssetRows = assetList != null && !assetList.isEmpty();
if (!hasStaffRows && !hasAssetRows) {
return error("至少需要一条数据"); return error("至少需要一条数据");
} }
// 提交异步任务 BaseStaffImportSubmitResultVO result = new BaseStaffImportSubmitResultVO();
String taskId = baseStaffService.importBaseStaff(list, updateSupport); if (hasStaffRows) {
result.setStaffTaskId(baseStaffService.importBaseStaff(staffList));
// 立即返回,不等待后台任务完成 }
ImportResultVO result = new ImportResultVO(); if (hasAssetRows) {
result.setTaskId(taskId); result.setAssetTaskId(baseStaffAssetImportService.importAssetInfo(assetList));
result.setStatus("PROCESSING"); }
result.setMessage("导入任务已提交,正在后台处理"); result.setMessage(buildImportSubmitMessage(hasStaffRows, hasAssetRows));
return AjaxResult.success("导入任务已提交,正在后台处理", result); return AjaxResult.success("导入任务已提交,正在后台处理", result);
} }
@@ -202,4 +214,14 @@ public class CcdiBaseStaffController extends BaseController {
return getDataTable(pageData, failures.size()); return getDataTable(pageData, failures.size());
} }
private String buildImportSubmitMessage(boolean hasStaffRows, boolean hasAssetRows) {
if (hasStaffRows && hasAssetRows) {
return "已提交员工信息和员工资产信息导入任务";
}
if (hasStaffRows) {
return "已提交员工信息导入任务";
}
return "已提交员工资产信息导入任务";
}
} }

View File

@@ -63,18 +63,6 @@ public class CcdiCustEnterpriseRelationController extends BaseController {
return getDataTable(result.getRecords(), result.getTotal()); return getDataTable(result.getRecords(), result.getTotal());
} }
/**
* 导出信贷客户实体关联列表
*/
@Operation(summary = "导出信贷客户实体关联列表")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:export')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiCustEnterpriseRelationQueryDTO queryDTO) {
List<CcdiCustEnterpriseRelationExcel> list = relationService.selectRelationListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiCustEnterpriseRelationExcel.class, "信贷客户实体关联信息");
}
/** /**
* 获取信贷客户实体关联详细信息 * 获取信贷客户实体关联详细信息
*/ */

View File

@@ -103,17 +103,6 @@ public class CcdiCustFmyRelationController extends BaseController {
return toAjax(relationService.deleteRelationByIds(ids)); return toAjax(relationService.deleteRelationByIds(ids));
} }
/**
* 导出信贷客户家庭关系
*/
@Operation(summary = "导出信贷客户家庭关系")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:export')")
@Log(title = "信贷客户家庭关系", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiCustFmyRelationQueryDTO query) {
relationService.exportRelations(query, response);
}
/** /**
* 下载带字典下拉框的导入模板 * 下载带字典下拉框的导入模板
* 使用@DictDropdown注解自动添加下拉框 * 使用@DictDropdown注解自动添加下拉框

View File

@@ -2,10 +2,10 @@ package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.*; 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.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.*; 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.ICcdiIntermediaryPersonImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryService; import com.ruoyi.info.collection.service.ICcdiIntermediaryService;
import com.ruoyi.info.collection.utils.EasyExcelUtil; import com.ruoyi.info.collection.utils.EasyExcelUtil;
@@ -46,7 +46,7 @@ public class CcdiIntermediaryController extends BaseController {
private ICcdiIntermediaryPersonImportService personImportService; private ICcdiIntermediaryPersonImportService personImportService;
@Resource @Resource
private ICcdiIntermediaryEntityImportService entityImportService; private ICcdiIntermediaryEnterpriseRelationImportService enterpriseRelationImportService;
/** /**
* 查询中介列表 * 查询中介列表
@@ -277,10 +277,10 @@ public class CcdiIntermediaryController extends BaseController {
/** /**
* 下载实体中介导入模板 * 下载实体中介导入模板
*/ */
@Operation(summary = "下载实体中介导入模板") @Operation(summary = "下载中介实体关联关系导入模板")
@PostMapping("/importEntityTemplate") @PostMapping("/importEnterpriseRelationTemplate")
public void importEntityTemplate(HttpServletResponse response) { public void importEnterpriseRelationTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiIntermediaryEntityExcel.class, "实体中介信息"); EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiIntermediaryEnterpriseRelationExcel.class, "中介实体关联关系信息");
} }
/** /**
@@ -313,20 +313,19 @@ public class CcdiIntermediaryController extends BaseController {
/** /**
* 导入实体中介数据(异步) * 导入实体中介数据(异步)
*/ */
@Operation(summary = "导入实体中介数据") @Operation(summary = "导入中介实体关联关系数据")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')") @PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
@Log(title = "实体中介", businessType = BusinessType.IMPORT) @Log(title = "中介实体关联关系", businessType = BusinessType.IMPORT)
@PostMapping("/importEntityData") @PostMapping("/importEnterpriseRelationData")
public AjaxResult importEntityData(MultipartFile file) throws Exception { public AjaxResult importEnterpriseRelationData(MultipartFile file) throws Exception {
List<CcdiIntermediaryEntityExcel> list = EasyExcelUtil.importExcel( List<CcdiIntermediaryEnterpriseRelationExcel> list = EasyExcelUtil.importExcel(
file.getInputStream(), CcdiIntermediaryEntityExcel.class); file.getInputStream(), CcdiIntermediaryEnterpriseRelationExcel.class);
if (list == null || list.isEmpty()) { if (list == null || list.isEmpty()) {
return error("至少需要一条数据"); return error("至少需要一条数据");
} }
// 提交异步任务 String taskId = intermediaryService.importIntermediaryEnterpriseRelation(list);
String taskId = intermediaryService.importIntermediaryEntity(list);
// 立即返回,不等待后台任务完成 // 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO(); ImportResultVO result = new ImportResultVO();
@@ -383,12 +382,12 @@ public class CcdiIntermediaryController extends BaseController {
/** /**
* 查询实体中介导入状态 * 查询实体中介导入状态
*/ */
@Operation(summary = "查询实体中介导入状态") @Operation(summary = "查询中介实体关联关系导入状态")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')") @PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
@GetMapping("/importEntityStatus/{taskId}") @GetMapping("/importEnterpriseRelationStatus/{taskId}")
public AjaxResult getEntityImportStatus(@PathVariable String taskId) { public AjaxResult getEnterpriseRelationImportStatus(@PathVariable String taskId) {
try { try {
ImportStatusVO status = entityImportService.getImportStatus(taskId); ImportStatusVO status = enterpriseRelationImportService.getImportStatus(taskId);
return success(status); return success(status);
} catch (Exception e) { } catch (Exception e) {
return error(e.getMessage()); return error(e.getMessage());
@@ -396,18 +395,18 @@ public class CcdiIntermediaryController extends BaseController {
} }
/** /**
* 查询实体中介导入失败记录 * 查询中介实体关联关系导入失败记录
*/ */
@Operation(summary = "查询实体中介导入失败记录") @Operation(summary = "查询中介实体关联关系导入失败记录")
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')") @PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
@GetMapping("/importEntityFailures/{taskId}") @GetMapping("/importEnterpriseRelationFailures/{taskId}")
public TableDataInfo getEntityImportFailures( public TableDataInfo getEnterpriseRelationImportFailures(
@PathVariable String taskId, @PathVariable String taskId,
@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) { @RequestParam(defaultValue = "10") Integer pageSize) {
List<IntermediaryEntityImportFailureVO> failures = List<IntermediaryEnterpriseRelationImportFailureVO> failures =
entityImportService.getImportFailures(taskId); enterpriseRelationImportService.getImportFailures(taskId);
// 手动分页 // 手动分页
int fromIndex = (pageNum - 1) * pageSize; int fromIndex = (pageNum - 1) * pageSize;
@@ -418,7 +417,7 @@ public class CcdiIntermediaryController extends BaseController {
return getDataTable(new ArrayList<>(), failures.size()); 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()); return getDataTable(pageData, failures.size());
} }

View File

@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel; import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO; import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO; import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
@@ -33,12 +34,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* 采购交易信息Controller * 招投标信息维护Controller
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-06 * @date 2026-02-06
*/ */
@Tag(name = "采购交易信息管理") @Tag(name = "招投标信息维护")
@RestController @RestController
@RequestMapping("/ccdi/purchaseTransaction") @RequestMapping("/ccdi/purchaseTransaction")
public class CcdiPurchaseTransactionController extends BaseController { public class CcdiPurchaseTransactionController extends BaseController {
@@ -50,9 +51,9 @@ public class CcdiPurchaseTransactionController extends BaseController {
private ICcdiPurchaseTransactionImportService transactionImportService; private ICcdiPurchaseTransactionImportService transactionImportService;
/** /**
* 查询采购交易列表 * 查询招投标信息列表
*/ */
@Operation(summary = "查询采购交易列表") @Operation(summary = "查询招投标信息列表")
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:list')") @PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(CcdiPurchaseTransactionQueryDTO queryDTO) { public TableDataInfo list(CcdiPurchaseTransactionQueryDTO queryDTO) {
@@ -64,21 +65,9 @@ public class CcdiPurchaseTransactionController extends BaseController {
} }
/** /**
* 导出采购交易列表 * 获取招投标信息详细信息
*/ */
@Operation(summary = "导出采购交易列表") @Operation(summary = "获取招投标信息详细信息")
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:export')")
@Log(title = "采购交易信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiPurchaseTransactionQueryDTO queryDTO) {
List<CcdiPurchaseTransactionExcel> list = transactionService.selectTransactionListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiPurchaseTransactionExcel.class, "采购交易信息");
}
/**
* 获取采购交易详细信息
*/
@Operation(summary = "获取采购交易详细信息")
@Parameter(name = "purchaseId", description = "采购事项ID", required = true) @Parameter(name = "purchaseId", description = "采购事项ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:query')") @PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:query')")
@GetMapping(value = "/{purchaseId}") @GetMapping(value = "/{purchaseId}")
@@ -87,66 +76,81 @@ public class CcdiPurchaseTransactionController extends BaseController {
} }
/** /**
* 新增采购交易 * 新增招投标信息
*/ */
@Operation(summary = "新增采购交易") @Operation(summary = "新增招投标信息")
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:add')") @PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:add')")
@Log(title = "采购交易信息", businessType = BusinessType.INSERT) @Log(title = "招投标信息维护", businessType = BusinessType.INSERT)
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody CcdiPurchaseTransactionAddDTO addDTO) { public AjaxResult add(@Validated @RequestBody CcdiPurchaseTransactionAddDTO addDTO) {
return toAjax(transactionService.insertTransaction(addDTO)); return toAjax(transactionService.insertTransaction(addDTO));
} }
/** /**
* 修改采购交易 * 修改招投标信息
*/ */
@Operation(summary = "修改采购交易") @Operation(summary = "修改招投标信息")
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:edit')") @PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:edit')")
@Log(title = "采购交易信息", businessType = BusinessType.UPDATE) @Log(title = "招投标信息维护", businessType = BusinessType.UPDATE)
@PutMapping @PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiPurchaseTransactionEditDTO editDTO) { public AjaxResult edit(@Validated @RequestBody CcdiPurchaseTransactionEditDTO editDTO) {
return toAjax(transactionService.updateTransaction(editDTO)); return toAjax(transactionService.updateTransaction(editDTO));
} }
/** /**
* 删除采购交易 * 删除招投标信息
*/ */
@Operation(summary = "删除采购交易") @Operation(summary = "删除招投标信息")
@Parameter(name = "purchaseIds", description = "采购事项ID数组", required = true) @Parameter(name = "purchaseIds", description = "采购事项ID数组", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:remove')") @PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:remove')")
@Log(title = "采购交易信息", businessType = BusinessType.DELETE) @Log(title = "招投标信息维护", businessType = BusinessType.DELETE)
@DeleteMapping("/{purchaseIds}") @DeleteMapping("/{purchaseIds}")
public AjaxResult remove(@PathVariable String[] purchaseIds) { public AjaxResult remove(@PathVariable String[] purchaseIds) {
return toAjax(transactionService.deleteTransactionByIds(purchaseIds)); return toAjax(transactionService.deleteTransactionByIds(purchaseIds));
} }
/** /**
* 下载带字典下拉框的导入模板 * 下载双Sheet导入模板
* 使用@DictDropdown注解自动添加下拉框
*/ */
@Operation(summary = "下载导入模板") @Operation(summary = "下载导入模板")
@PostMapping("/importTemplate") @PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) { public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiPurchaseTransactionExcel.class, "采购交易信息"); EasyExcelUtil.importTemplateWithDictDropdown(
response,
CcdiPurchaseTransactionExcel.class,
"招投标主信息",
CcdiPurchaseTransactionSupplierExcel.class,
"供应商明细",
"招投标信息维护导入模板"
);
} }
/** /**
* 异步导入采购交易 * 异步导入招投标信息
*/ */
@Operation(summary = "异步导入采购交易") @Operation(summary = "异步导入招投标信息")
@Parameter(name = "file", description = "导入文件", required = true) @Parameter(name = "file", description = "导入文件", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:import')") @PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:import')")
@Log(title = "采购交易信息", businessType = BusinessType.IMPORT) @Log(title = "招投标信息维护", businessType = BusinessType.IMPORT)
@PostMapping("/importData") @PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception { public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiPurchaseTransactionExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiPurchaseTransactionExcel.class); List<CcdiPurchaseTransactionExcel> mainList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiPurchaseTransactionExcel.class,
"招投标主信息"
);
List<CcdiPurchaseTransactionSupplierExcel> supplierList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiPurchaseTransactionSupplierExcel.class,
"供应商明细"
);
if (list == null || list.isEmpty()) { if ((mainList == null || mainList.isEmpty()) && (supplierList == null || supplierList.isEmpty())) {
return error("至少需要一条数据"); return error("至少需要一条数据");
} }
// 提交异步任务 // 提交异步任务
String taskId = transactionService.importTransaction(list); String taskId = transactionService.importTransaction(mainList, supplierList);
// 立即返回,不等待后台任务完成 // 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO(); ImportResultVO result = new ImportResultVO();

View File

@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO; import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
@@ -33,12 +34,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* 员工实体关系信息Controller * 员工亲属实体关Controller
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09
*/ */
@Tag(name = "员工实体关系信息管理") @Tag(name = "员工亲属实体关管理")
@RestController @RestController
@RequestMapping("/ccdi/staffEnterpriseRelation") @RequestMapping("/ccdi/staffEnterpriseRelation")
public class CcdiStaffEnterpriseRelationController extends BaseController { public class CcdiStaffEnterpriseRelationController extends BaseController {
@@ -50,9 +51,9 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
private ICcdiStaffEnterpriseRelationImportService relationImportService; private ICcdiStaffEnterpriseRelationImportService relationImportService;
/** /**
* 查询员工实体关列表 * 查询员工亲属实体关列表
*/ */
@Operation(summary = "查询员工实体关列表") @Operation(summary = "查询员工亲属实体关列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:list')") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:list')")
@GetMapping("/list") @GetMapping("/list")
public TableDataInfo list(CcdiStaffEnterpriseRelationQueryDTO queryDTO) { public TableDataInfo list(CcdiStaffEnterpriseRelationQueryDTO queryDTO) {
@@ -64,21 +65,20 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
} }
/** /**
* 导出员工实体关系列表 * 查询有效员工亲属下拉列表
*/ */
@Operation(summary = "导出员工实体关系列表") @Operation(summary = "查询有效员工亲属下拉列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:export')") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:list')")
@Log(title = "员工实体关系信息", businessType = BusinessType.EXPORT) @GetMapping("/familyOptions")
@PostMapping("/export") public AjaxResult familyOptions(@RequestParam(required = false) String query) {
public void export(HttpServletResponse response, CcdiStaffEnterpriseRelationQueryDTO queryDTO) { List<CcdiStaffEnterpriseRelationOptionVO> list = relationService.selectFamilyOptions(query);
List<CcdiStaffEnterpriseRelationExcel> list = relationService.selectRelationListForExport(queryDTO); return success(list);
EasyExcelUtil.exportExcel(response, list, CcdiStaffEnterpriseRelationExcel.class, "员工实体关系信息");
} }
/** /**
* 获取员工实体关详细信息 * 获取员工亲属实体关详细信息
*/ */
@Operation(summary = "获取员工实体关详细信息") @Operation(summary = "获取员工亲属实体关详细信息")
@Parameter(name = "id", description = "主键ID", required = true) @Parameter(name = "id", description = "主键ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:query')") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:query')")
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
@@ -87,34 +87,34 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
} }
/** /**
* 新增员工实体关 * 新增员工亲属实体关
*/ */
@Operation(summary = "新增员工实体关") @Operation(summary = "新增员工亲属实体关")
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:add')") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:add')")
@Log(title = "员工实体关系信息", businessType = BusinessType.INSERT) @Log(title = "员工亲属实体关", businessType = BusinessType.INSERT)
@PostMapping @PostMapping
public AjaxResult add(@Validated @RequestBody CcdiStaffEnterpriseRelationAddDTO addDTO) { public AjaxResult add(@Validated @RequestBody CcdiStaffEnterpriseRelationAddDTO addDTO) {
return toAjax(relationService.insertRelation(addDTO)); return toAjax(relationService.insertRelation(addDTO));
} }
/** /**
* 修改员工实体关 * 修改员工亲属实体关
*/ */
@Operation(summary = "修改员工实体关") @Operation(summary = "修改员工亲属实体关")
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:edit')") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:edit')")
@Log(title = "员工实体关系信息", businessType = BusinessType.UPDATE) @Log(title = "员工亲属实体关", businessType = BusinessType.UPDATE)
@PutMapping @PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiStaffEnterpriseRelationEditDTO editDTO) { public AjaxResult edit(@Validated @RequestBody CcdiStaffEnterpriseRelationEditDTO editDTO) {
return toAjax(relationService.updateRelation(editDTO)); return toAjax(relationService.updateRelation(editDTO));
} }
/** /**
* 删除员工实体关 * 删除员工亲属实体关
*/ */
@Operation(summary = "删除员工实体关") @Operation(summary = "删除员工亲属实体关")
@Parameter(name = "ids", description = "主键ID数组", required = true) @Parameter(name = "ids", description = "主键ID数组", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:remove')") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:remove')")
@Log(title = "员工实体关系信息", businessType = BusinessType.DELETE) @Log(title = "员工亲属实体关", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}") @DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) { public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(relationService.deleteRelationByIds(ids)); return toAjax(relationService.deleteRelationByIds(ids));
@@ -127,16 +127,16 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
@Operation(summary = "下载导入模板") @Operation(summary = "下载导入模板")
@PostMapping("/importTemplate") @PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) { public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffEnterpriseRelationExcel.class, "员工实体关系信息"); EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffEnterpriseRelationExcel.class, "员工亲属实体关");
} }
/** /**
* 异步导入员工实体关 * 异步导入员工亲属实体关
*/ */
@Operation(summary = "异步导入员工实体关") @Operation(summary = "异步导入员工亲属实体关")
@Parameter(name = "file", description = "导入文件", required = true) @Parameter(name = "file", description = "导入文件", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:import')") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:import')")
@Log(title = "员工实体关系信息", businessType = BusinessType.IMPORT) @Log(title = "员工亲属实体关", businessType = BusinessType.IMPORT)
@PostMapping("/importData") @PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception { public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiStaffEnterpriseRelationExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffEnterpriseRelationExcel.class); List<CcdiStaffEnterpriseRelationExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffEnterpriseRelationExcel.class);
@@ -152,9 +152,9 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
ImportResultVO result = new ImportResultVO(); ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId); result.setTaskId(taskId);
result.setStatus("PROCESSING"); result.setStatus("PROCESSING");
result.setMessage("导入任务已提交,正在后台处理"); result.setMessage("员工亲属实体关联导入任务已提交,正在后台处理");
return AjaxResult.success("导入任务已提交,正在后台处理", result); return AjaxResult.success("员工亲属实体关联导入任务已提交,正在后台处理", result);
} }
/** /**

View File

@@ -4,11 +4,13 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO; import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportSubmitResultVO;
import com.ruoyi.info.collection.service.ICcdiAssetInfoImportService;
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService; import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService; import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
import com.ruoyi.info.collection.utils.EasyExcelUtil; import com.ruoyi.info.collection.utils.EasyExcelUtil;
@@ -49,6 +51,9 @@ public class CcdiStaffFmyRelationController extends BaseController {
@Resource @Resource
private ICcdiStaffFmyRelationImportService relationImportService; private ICcdiStaffFmyRelationImportService relationImportService;
@Resource
private ICcdiAssetInfoImportService assetInfoImportService;
/** /**
* 查询员工亲属关系列表 * 查询员工亲属关系列表
*/ */
@@ -63,18 +68,6 @@ public class CcdiStaffFmyRelationController extends BaseController {
return getDataTable(result.getRecords(), result.getTotal()); return getDataTable(result.getRecords(), result.getTotal());
} }
/**
* 导出员工亲属关系列表
*/
@Operation(summary = "导出员工亲属关系列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:export')")
@Log(title = "员工亲属关系", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiStaffFmyRelationQueryDTO queryDTO) {
List<CcdiStaffFmyRelationExcel> list = relationService.selectRelationListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiStaffFmyRelationExcel.class, "员工亲属关系信息");
}
/** /**
* 获取员工亲属关系详细信息 * 获取员工亲属关系详细信息
*/ */
@@ -127,7 +120,14 @@ public class CcdiStaffFmyRelationController extends BaseController {
@Operation(summary = "下载导入模板") @Operation(summary = "下载导入模板")
@PostMapping("/importTemplate") @PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) { public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffFmyRelationExcel.class, "员工亲属关系信息"); EasyExcelUtil.importTemplateWithDictDropdown(
response,
CcdiStaffFmyRelationExcel.class,
"员工亲属关系信息",
CcdiAssetInfoExcel.class,
"亲属资产信息",
"员工亲属关系维护导入模板"
);
} }
/** /**
@@ -139,20 +139,32 @@ public class CcdiStaffFmyRelationController extends BaseController {
@Log(title = "员工亲属关系", businessType = BusinessType.IMPORT) @Log(title = "员工亲属关系", businessType = BusinessType.IMPORT)
@PostMapping("/importData") @PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception { public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiStaffFmyRelationExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffFmyRelationExcel.class); List<CcdiStaffFmyRelationExcel> relationList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiStaffFmyRelationExcel.class,
"员工亲属关系信息"
);
List<CcdiAssetInfoExcel> assetList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiAssetInfoExcel.class,
"亲属资产信息"
);
if (list == null || list.isEmpty()) { boolean hasRelationRows = relationList != null && !relationList.isEmpty();
boolean hasAssetRows = assetList != null && !assetList.isEmpty();
if (!hasRelationRows && !hasAssetRows) {
return error("至少需要一条数据"); return error("至少需要一条数据");
} }
// 提交异步任务 StaffFmyRelationImportSubmitResultVO result = new StaffFmyRelationImportSubmitResultVO();
String taskId = relationService.importRelation(list); if (hasRelationRows) {
result.setRelationTaskId(relationService.importRelation(relationList));
// 立即返回,不等待后台任务完成 }
ImportResultVO result = new ImportResultVO(); if (hasAssetRows) {
result.setTaskId(taskId); result.setAssetTaskId(assetInfoImportService.importAssetInfo(assetList));
result.setStatus("PROCESSING"); }
result.setMessage("导入任务已提交,正在后台处理"); result.setMessage(buildImportSubmitMessage(hasRelationRows, hasAssetRows));
return AjaxResult.success("导入任务已提交,正在后台处理", result); return AjaxResult.success("导入任务已提交,正在后台处理", result);
} }
@@ -198,4 +210,14 @@ public class CcdiStaffFmyRelationController extends BaseController {
return getDataTable(pageData, failures.size()); return getDataTable(pageData, failures.size());
} }
private String buildImportSubmitMessage(boolean hasRelationRows, boolean hasAssetRows) {
if (hasRelationRows && hasAssetRows) {
return "已提交员工亲属关系和亲属资产信息导入任务";
}
if (hasRelationRows) {
return "已提交员工亲属关系导入任务";
}
return "已提交亲属资产信息导入任务";
}
} }

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.CcdiStaffRecruitmentEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; 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.CcdiStaffRecruitmentVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO; import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
@@ -63,18 +64,6 @@ public class CcdiStaffRecruitmentController extends BaseController {
return getDataTable(result.getRecords(), result.getTotal()); return getDataTable(result.getRecords(), result.getTotal());
} }
/**
* 导出招聘信息列表
*/
@Operation(summary = "导出招聘信息列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:export')")
@Log(title = "员工招聘信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiStaffRecruitmentQueryDTO queryDTO) {
List<CcdiStaffRecruitmentExcel> list = recruitmentService.selectRecruitmentListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiStaffRecruitmentExcel.class, "员工招聘信息");
}
/** /**
* 获取招聘信息详细信息 * 获取招聘信息详细信息
*/ */
@@ -125,7 +114,14 @@ public class CcdiStaffRecruitmentController extends BaseController {
@Operation(summary = "下载导入模板") @Operation(summary = "下载导入模板")
@PostMapping("/importTemplate") @PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) { public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffRecruitmentExcel.class, "员工招聘信息"); EasyExcelUtil.importTemplateWithDictDropdown(
response,
CcdiStaffRecruitmentExcel.class,
"招聘信息",
CcdiStaffRecruitmentWorkExcel.class,
"历史工作经历",
"招聘信息管理导入模板"
);
} }
/** /**
@@ -137,16 +133,25 @@ public class CcdiStaffRecruitmentController extends BaseController {
@Log(title = "员工招聘信息", businessType = BusinessType.IMPORT) @Log(title = "员工招聘信息", businessType = BusinessType.IMPORT)
@PostMapping("/importData") @PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception { public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiStaffRecruitmentExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffRecruitmentExcel.class); List<CcdiStaffRecruitmentExcel> recruitmentList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiStaffRecruitmentExcel.class,
"招聘信息"
);
List<CcdiStaffRecruitmentWorkExcel> workList = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiStaffRecruitmentWorkExcel.class,
"历史工作经历"
);
if (list == null || list.isEmpty()) { boolean hasRecruitmentRows = recruitmentList != null && !recruitmentList.isEmpty();
boolean hasWorkRows = workList != null && !workList.isEmpty();
if (!hasRecruitmentRows && !hasWorkRows) {
return error("至少需要一条数据"); return error("至少需要一条数据");
} }
// 提交异步任务 String taskId = recruitmentService.importRecruitment(recruitmentList, workList);
String taskId = recruitmentService.importRecruitment(list);
// 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO(); ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId); result.setTaskId(taskId);
result.setStatus("PROCESSING"); result.setStatus("PROCESSING");

View File

@@ -63,18 +63,6 @@ public class CcdiStaffTransferController extends BaseController {
return getDataTable(result.getRecords(), result.getTotal()); return getDataTable(result.getRecords(), result.getTotal());
} }
/**
* 导出员工调动记录列表
*/
@Operation(summary = "导出员工调动记录列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:export')")
@Log(title = "员工调动记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiStaffTransferQueryDTO queryDTO) {
List<CcdiStaffTransferExcel> list = transferService.selectTransferListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiStaffTransferExcel.class, "员工调动记录信息");
}
/** /**
* 获取员工调动记录详细信息 * 获取员工调动记录详细信息
*/ */

View File

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

View File

@@ -0,0 +1,64 @@
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 lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 招投标供应商明细对象 ccdi_purchase_transaction_supplier
*/
@Data
public class CcdiPurchaseTransactionSupplier implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
private Long id;
/** 采购事项ID */
private String purchaseId;
/** 供应商名称 */
private String supplierName;
/** 供应商统一信用代码 */
private String supplierUscc;
/** 供应商联系人 */
private String contactPerson;
/** 供应商联系电话 */
private String contactPhone;
/** 供应商银行账户 */
private String supplierBankAccount;
/** 是否中标1-是0-否 */
private Integer isBidWinner;
/** 排序 */
private Integer sortOrder;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
}

View File

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

@@ -89,7 +89,6 @@ public class CcdiEnterpriseBaseInfoAddDTO implements Serializable {
private String shareholder5; private String shareholder5;
@Schema(description = "经营状态") @Schema(description = "经营状态")
@NotBlank(message = "经营状态不能为空")
@Size(max = 50, message = "经营状态长度不能超过50个字符") @Size(max = 50, message = "经营状态长度不能超过50个字符")
private String status; private String status;
@@ -102,6 +101,5 @@ public class CcdiEnterpriseBaseInfoAddDTO implements Serializable {
private String entSource; private String entSource;
@Schema(description = "数据来源") @Schema(description = "数据来源")
@NotBlank(message = "数据来源不能为空")
private String dataSource; private String dataSource;
} }

View File

@@ -89,7 +89,6 @@ public class CcdiEnterpriseBaseInfoEditDTO implements Serializable {
private String shareholder5; private String shareholder5;
@Schema(description = "经营状态") @Schema(description = "经营状态")
@NotBlank(message = "经营状态不能为空")
@Size(max = 50, message = "经营状态长度不能超过50个字符") @Size(max = 50, message = "经营状态长度不能超过50个字符")
private String status; private String status;

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import lombok.Data; import lombok.Data;
@@ -9,15 +10,16 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 采购交易信息新增DTO * 招投标信息新增DTO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-06 * @date 2026-02-06
*/ */
@Data @Data
@Schema(description = "采购交易信息新增") @Schema(description = "招投标信息新增")
public class CcdiPurchaseTransactionAddDTO implements Serializable { public class CcdiPurchaseTransactionAddDTO implements Serializable {
@Serial @Serial
@@ -88,30 +90,10 @@ public class CcdiPurchaseTransactionAddDTO implements Serializable {
@Schema(description = "采购方式") @Schema(description = "采购方式")
private String purchaseMethod; private String purchaseMethod;
/** 中标供应商名称 */ /** 供应商明细 */
@Size(max = 200, message = "中标供应商名称长度不能超过200个字符") @Valid
@Schema(description = "中标供应商名称") @Schema(description = "供应商明细列表")
private String supplierName; private List<CcdiPurchaseTransactionSupplierDTO> supplierList;
/** 供应商联系人 */
@Size(max = 50, message = "供应商联系人长度不能超过50个字符")
@Schema(description = "供应商联系人")
private String contactPerson;
/** 供应商联系电话 */
@Pattern(regexp = "^1[3-9]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$", message = "供应商联系电话格式不正确")
@Schema(description = "供应商联系电话")
private String contactPhone;
/** 供应商统一信用代码 */
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "供应商统一信用代码格式不正确")
@Schema(description = "供应商统一信用代码")
private String supplierUscc;
/** 供应商银行账户 */
@Size(max = 50, message = "供应商银行账户长度不能超过50个字符")
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
/** 采购申请日期(或立项日期) */ /** 采购申请日期(或立项日期) */
@NotNull(message = "采购申请日期不能为空") @NotNull(message = "采购申请日期不能为空")

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import lombok.Data; import lombok.Data;
@@ -9,15 +10,16 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 采购交易信息编辑DTO * 招投标信息编辑DTO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-06 * @date 2026-02-06
*/ */
@Data @Data
@Schema(description = "采购交易信息编辑") @Schema(description = "招投标信息编辑")
public class CcdiPurchaseTransactionEditDTO implements Serializable { public class CcdiPurchaseTransactionEditDTO implements Serializable {
@Serial @Serial
@@ -88,30 +90,10 @@ public class CcdiPurchaseTransactionEditDTO implements Serializable {
@Schema(description = "采购方式") @Schema(description = "采购方式")
private String purchaseMethod; private String purchaseMethod;
/** 中标供应商名称 */ /** 供应商明细 */
@Size(max = 200, message = "中标供应商名称长度不能超过200个字符") @Valid
@Schema(description = "中标供应商名称") @Schema(description = "供应商明细列表")
private String supplierName; private List<CcdiPurchaseTransactionSupplierDTO> supplierList;
/** 供应商联系人 */
@Size(max = 50, message = "供应商联系人长度不能超过50个字符")
@Schema(description = "供应商联系人")
private String contactPerson;
/** 供应商联系电话 */
@Pattern(regexp = "^1[3-9]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$", message = "供应商联系电话格式不正确")
@Schema(description = "供应商联系电话")
private String contactPhone;
/** 供应商统一信用代码 */
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "供应商统一信用代码格式不正确")
@Schema(description = "供应商统一信用代码")
private String supplierUscc;
/** 供应商银行账户 */
@Size(max = 50, message = "供应商银行账户长度不能超过50个字符")
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
/** 采购申请日期(或立项日期) */ /** 采购申请日期(或立项日期) */
@NotNull(message = "采购申请日期不能为空") @NotNull(message = "采购申请日期不能为空")

View File

@@ -9,13 +9,13 @@ import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
* 采购交易信息查询DTO * 招投标信息查询DTO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-06 * @date 2026-02-06
*/ */
@Data @Data
@Schema(description = "采购交易信息查询条件") @Schema(description = "招投标信息查询条件")
public class CcdiPurchaseTransactionQueryDTO implements Serializable { public class CcdiPurchaseTransactionQueryDTO implements Serializable {
@Serial @Serial

View File

@@ -0,0 +1,42 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 招投标供应商明细DTO
*/
@Data
@Schema(description = "招投标供应商明细")
public class CcdiPurchaseTransactionSupplierDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@NotBlank(message = "供应商名称不能为空")
@Schema(description = "供应商名称")
private String supplierName;
@NotBlank(message = "供应商统一信用代码不能为空")
@Schema(description = "供应商统一信用代码")
private String supplierUscc;
@Schema(description = "供应商联系人")
private String contactPerson;
@Schema(description = "供应商联系电话")
private String contactPhone;
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
@Schema(description = "是否中标1-是0-否")
private Integer isBidWinner;
@Schema(description = "排序")
private Integer sortOrder;
}

View File

@@ -10,22 +10,22 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
/** /**
* 员工实体关系信息新增DTO * 员工亲属实体关新增DTO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09
*/ */
@Data @Data
@Schema(description = "员工实体关系信息新增") @Schema(description = "员工亲属实体关新增")
public class CcdiStaffEnterpriseRelationAddDTO implements Serializable { public class CcdiStaffEnterpriseRelationAddDTO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 身份证号 */ /** 亲属身份证号 */
@NotBlank(message = "身份证号不能为空") @NotBlank(message = "亲属身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确") @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "亲属身份证号格式不正确")
@Schema(description = "身份证号") @Schema(description = "亲属身份证号")
private String personId; private String personId;
/** 关联人在企业的职务 */ /** 关联人在企业的职务 */

View File

@@ -10,13 +10,13 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
/** /**
* 员工实体关系信息编辑DTO * 员工亲属实体关编辑DTO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09
*/ */
@Data @Data
@Schema(description = "员工实体关系信息编辑") @Schema(description = "员工亲属实体关编辑")
public class CcdiStaffEnterpriseRelationEditDTO implements Serializable { public class CcdiStaffEnterpriseRelationEditDTO implements Serializable {
@Serial @Serial
@@ -27,8 +27,8 @@ public class CcdiStaffEnterpriseRelationEditDTO implements Serializable {
@Schema(description = "主键ID") @Schema(description = "主键ID")
private Long id; private Long id;
/** 身份证号 */ /** 亲属身份证号 */
@Schema(description = "身份证号(不可修改)") @Schema(description = "亲属身份证号(不可修改)")
private String personId; private String personId;
/** 关联人在企业的职务 */ /** 关联人在企业的职务 */

View File

@@ -7,22 +7,30 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
/** /**
* 员工实体关系信息查询DTO * 员工亲属实体关查询DTO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09
*/ */
@Data @Data
@Schema(description = "员工实体关系信息查询条件") @Schema(description = "员工亲属实体关查询条件")
public class CcdiStaffEnterpriseRelationQueryDTO implements Serializable { public class CcdiStaffEnterpriseRelationQueryDTO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 身份证号 */ /** 亲属身份证号 */
@Schema(description = "身份证号") @Schema(description = "亲属身份证号")
private String personId; private String personId;
/** 亲属姓名 */
@Schema(description = "亲属姓名")
private String relationName;
/** 关联员工 */
@Schema(description = "关联员工")
private String staffPersonName;
/** 统一社会信用代码 */ /** 统一社会信用代码 */
@Schema(description = "统一社会信用代码") @Schema(description = "统一社会信用代码")
private String socialCreditCode; private String socialCreditCode;

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

View File

@@ -2,14 +2,17 @@ package com.ruoyi.info.collection.domain.dto;
import com.ruoyi.info.collection.annotation.EnumValid; import com.ruoyi.info.collection.annotation.EnumValid;
import com.ruoyi.info.collection.enums.AdmitStatus; import com.ruoyi.info.collection.enums.AdmitStatus;
import com.ruoyi.info.collection.enums.RecruitType;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import jakarta.validation.Valid;
import lombok.Data; import lombok.Data;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
/** /**
* 员工招聘信息编辑DTO * 员工招聘信息编辑DTO
@@ -23,8 +26,8 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 招聘项目编号 */ /** 招聘记录编号 */
@NotNull(message = "招聘项目编号不能为空") @NotNull(message = "招聘记录编号不能为空")
private String recruitId; private String recruitId;
/** 招聘项目名称 */ /** 招聘项目名称 */
@@ -46,6 +49,10 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable {
@Size(max = 20, message = "应聘人员姓名长度不能超过20个字符") @Size(max = 20, message = "应聘人员姓名长度不能超过20个字符")
private String candName; private String candName;
/** 招聘类型 */
@EnumValid(enumClass = RecruitType.class, message = "招聘类型状态值不合法")
private String recruitType;
/** 应聘人员学历 */ /** 应聘人员学历 */
@Size(max = 20, message = "应聘人员学历长度不能超过20个字符") @Size(max = 20, message = "应聘人员学历长度不能超过20个字符")
private String candEdu; private String candEdu;
@@ -86,4 +93,8 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable {
/** 面试官2工号 */ /** 面试官2工号 */
@Size(max = 10, message = "面试官2工号长度不能超过10个字符") @Size(max = 10, message = "面试官2工号长度不能超过10个字符")
private String interviewerId2; private String interviewerId2;
/** 历史工作经历列表 */
@Valid
private List<CcdiStaffRecruitmentWorkEditDTO> workExperienceList;
} }

View File

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

View File

@@ -0,0 +1,56 @@
package com.ruoyi.info.collection.domain.dto;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 招聘记录历史工作经历编辑DTO
*
* @author ruoyi
* @date 2026-04-22
*/
@Data
public class CcdiStaffRecruitmentWorkEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 排序号 */
private Integer sortOrder;
/** 工作单位 */
@Size(max = 200, message = "工作单位长度不能超过200个字符")
private String companyName;
/** 所属部门 */
@Size(max = 100, message = "所属部门长度不能超过100个字符")
private String departmentName;
/** 岗位名称 */
@Size(max = 100, message = "岗位名称长度不能超过100个字符")
private String positionName;
/** 入职年月 */
@Pattern(regexp = "^$|^((19|20)\\d{2})-(0[1-9]|1[0-2])$", message = "入职年月格式不正确,应为YYYY-MM")
private String jobStartMonth;
/** 离职年月 */
@Pattern(regexp = "^$|^((19|20)\\d{2})-(0[1-9]|1[0-2])$", message = "离职年月格式不正确,应为YYYY-MM")
private String jobEndMonth;
/** 离职原因 */
@Size(max = 500, message = "离职原因长度不能超过500个字符")
private String departureReason;
/** 主要工作内容 */
@Size(max = 1000, message = "主要工作内容长度不能超过1000个字符")
private String workContent;
/** 备注 */
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
}

View File

@@ -88,7 +88,7 @@ public class CcdiEnterpriseBaseInfoExcel implements Serializable {
@ColumnWidth(18) @ColumnWidth(18)
private String shareholder5; private String shareholder5;
@ExcelProperty(value = "经营状态*", index = 16) @ExcelProperty(value = "经营状态", index = 16)
@ColumnWidth(16) @ColumnWidth(16)
private String status; private String status;
@@ -99,8 +99,4 @@ public class CcdiEnterpriseBaseInfoExcel implements Serializable {
@ExcelProperty(value = "企业来源*", index = 18) @ExcelProperty(value = "企业来源*", index = 18)
@ColumnWidth(18) @ColumnWidth(18)
private String entSource; 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) @ExcelProperty(value = "人员子类型", index = 2)
@ColumnWidth(15) @ColumnWidth(15)
@DictDropdown(dictType = "ccdi_person_sub_type")
private String personSubType; private String personSubType;
/** 性别 */ /** 性别 */
@@ -83,19 +84,13 @@ public class CcdiIntermediaryPersonExcel implements Serializable {
@ColumnWidth(15) @ColumnWidth(15)
private String position; private String position;
/** 关联人员ID */ /** 关联中介本人证件号码 */
@ExcelProperty(value = "关联人员ID", index = 12) @ExcelProperty(value = "关联中介本人证件号码", index = 12)
@ColumnWidth(15) @ColumnWidth(24)
private String relatedNumId; 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) @ColumnWidth(30)
private String remark; private String remark;
} }

View File

@@ -11,7 +11,7 @@ import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
/** /**
* 采购交易信息Excel导入导出对象 * 招投标主信息Excel导入导出对象
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-06 * @date 2026-02-06
@@ -88,107 +88,82 @@ public class CcdiPurchaseTransactionExcel implements Serializable {
@Required @Required
private String purchaseMethod; private String purchaseMethod;
/** 中标供应商名称 */
@ExcelProperty(value = "中标供应商名称", index = 12)
@ColumnWidth(25)
private String supplierName;
/** 供应商联系人 */
@ExcelProperty(value = "供应商联系人", index = 13)
@ColumnWidth(15)
private String contactPerson;
/** 供应商联系电话 */
@ExcelProperty(value = "供应商联系电话", index = 14)
@ColumnWidth(18)
private String contactPhone;
/** 供应商统一信用代码 */
@ExcelProperty(value = "供应商统一信用代码", index = 15)
@ColumnWidth(25)
private String supplierUscc;
/** 供应商银行账户 */
@ExcelProperty(value = "供应商银行账户", index = 16)
@ColumnWidth(20)
private String supplierBankAccount;
/** 采购申请日期(或立项日期) */ /** 采购申请日期(或立项日期) */
@ExcelProperty(value = "采购申请日期", index = 17) @ExcelProperty(value = "采购申请日期", index = 12)
@ColumnWidth(18) @ColumnWidth(18)
@Required @Required
private Date applyDate; private Date applyDate;
/** 采购计划批准日期 */ /** 采购计划批准日期 */
@ExcelProperty(value = "采购计划批准日期", index = 18) @ExcelProperty(value = "采购计划批准日期", index = 13)
@ColumnWidth(18) @ColumnWidth(18)
private Date planApproveDate; private Date planApproveDate;
/** 采购公告发布日期 */ /** 采购公告发布日期 */
@ExcelProperty(value = "采购公告发布日期", index = 19) @ExcelProperty(value = "采购公告发布日期", index = 14)
@ColumnWidth(18) @ColumnWidth(18)
private Date announceDate; private Date announceDate;
/** 开标日期 */ /** 开标日期 */
@ExcelProperty(value = "开标日期", index = 20) @ExcelProperty(value = "开标日期", index = 15)
@ColumnWidth(18) @ColumnWidth(18)
private Date bidOpenDate; private Date bidOpenDate;
/** 合同签订日期 */ /** 合同签订日期 */
@ExcelProperty(value = "合同签订日期", index = 21) @ExcelProperty(value = "合同签订日期", index = 16)
@ColumnWidth(18) @ColumnWidth(18)
private Date contractSignDate; private Date contractSignDate;
/** 预计交货日期 */ /** 预计交货日期 */
@ExcelProperty(value = "预计交货日期", index = 22) @ExcelProperty(value = "预计交货日期", index = 17)
@ColumnWidth(18) @ColumnWidth(18)
private Date expectedDeliveryDate; private Date expectedDeliveryDate;
/** 实际交货日期 */ /** 实际交货日期 */
@ExcelProperty(value = "实际交货日期", index = 23) @ExcelProperty(value = "实际交货日期", index = 18)
@ColumnWidth(18) @ColumnWidth(18)
private Date actualDeliveryDate; private Date actualDeliveryDate;
/** 验收日期 */ /** 验收日期 */
@ExcelProperty(value = "验收日期", index = 24) @ExcelProperty(value = "验收日期", index = 19)
@ColumnWidth(18) @ColumnWidth(18)
private Date acceptanceDate; private Date acceptanceDate;
/** 结算日期 */ /** 结算日期 */
@ExcelProperty(value = "结算日期", index = 25) @ExcelProperty(value = "结算日期", index = 20)
@ColumnWidth(18) @ColumnWidth(18)
private Date settlementDate; private Date settlementDate;
/** 申请人工号 */ /** 申请人工号 */
@ExcelProperty(value = "申请人工号", index = 26) @ExcelProperty(value = "申请人工号", index = 21)
@ColumnWidth(15) @ColumnWidth(15)
@Required @Required
private String applicantId; private String applicantId;
/** 申请人姓名 */ /** 申请人姓名 */
@ExcelProperty(value = "申请人姓名", index = 27) @ExcelProperty(value = "申请人姓名", index = 22)
@ColumnWidth(15) @ColumnWidth(15)
@Required @Required
private String applicantName; private String applicantName;
/** 申请部门 */ /** 申请部门 */
@ExcelProperty(value = "申请部门", index = 28) @ExcelProperty(value = "申请部门", index = 23)
@ColumnWidth(18) @ColumnWidth(18)
@Required @Required
private String applyDepartment; private String applyDepartment;
/** 采购负责人工号 */ /** 采购负责人工号 */
@ExcelProperty(value = "采购负责人工号", index = 29) @ExcelProperty(value = "采购负责人工号", index = 24)
@ColumnWidth(15) @ColumnWidth(15)
private String purchaseLeaderId; private String purchaseLeaderId;
/** 采购负责人姓名 */ /** 采购负责人姓名 */
@ExcelProperty(value = "采购负责人姓名", index = 30) @ExcelProperty(value = "采购负责人姓名", index = 25)
@ColumnWidth(15) @ColumnWidth(15)
private String purchaseLeaderName; private String purchaseLeaderName;
/** 采购部门 */ /** 采购部门 */
@ExcelProperty(value = "采购部门", index = 31) @ExcelProperty(value = "采购部门", index = 26)
@ColumnWidth(18) @ColumnWidth(18)
private String purchaseDepartment; private String purchaseDepartment;
} }

View File

@@ -0,0 +1,55 @@
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 com.ruoyi.common.annotation.Required;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 招投标供应商明细Excel对象
*/
@Data
public class CcdiPurchaseTransactionSupplierExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "采购事项ID", index = 0)
@ColumnWidth(20)
@Required
private String purchaseId;
@ExcelProperty(value = "供应商名称", index = 1)
@ColumnWidth(25)
@Required
private String supplierName;
@ExcelProperty(value = "供应商统一信用代码", index = 2)
@ColumnWidth(25)
private String supplierUscc;
@ExcelProperty(value = "供应商联系人", index = 3)
@ColumnWidth(15)
private String contactPerson;
@ExcelProperty(value = "供应商联系电话", index = 4)
@ColumnWidth(18)
private String contactPhone;
@ExcelProperty(value = "供应商银行账户", index = 5)
@ColumnWidth(20)
private String supplierBankAccount;
@ExcelProperty(value = "是否中标", index = 6)
@ColumnWidth(12)
@DictDropdown(dictType = "ccdi_yes_no_flag")
private String isBidWinner;
@ExcelProperty(value = "排序", index = 7)
@ColumnWidth(10)
private Integer sortOrder;
}

View File

@@ -10,23 +10,23 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
/** /**
* 员工实体关系信息Excel导入导出对象 * 员工亲属实体关Excel导入导出对象
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09
*/ */
@Data @Data
@Schema(description = "员工实体关系信息Excel导入导出对象") @Schema(description = "员工亲属实体关Excel导入导出对象")
public class CcdiStaffEnterpriseRelationExcel implements Serializable { public class CcdiStaffEnterpriseRelationExcel implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 身份证号 */ /** 亲属身份证号 */
@ExcelProperty(value = "身份证号", index = 0) @ExcelProperty(value = "亲属身份证号", index = 0)
@ColumnWidth(20) @ColumnWidth(20)
@Required @Required
@Schema(description = "身份证号") @Schema(description = "亲属身份证号")
private String personId; private String personId;
/** 统一社会信用代码 */ /** 统一社会信用代码 */

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

@@ -15,6 +15,14 @@ import java.math.BigDecimal;
@Schema(description = "亲属资产信息导入失败记录") @Schema(description = "亲属资产信息导入失败记录")
public class AssetImportFailureVO { public class AssetImportFailureVO {
/** Sheet名称 */
@Schema(description = "Sheet名称")
private String sheetName;
/** Excel行号 */
@Schema(description = "Excel行号")
private Integer rowNum;
/** 亲属证件号 */ /** 亲属证件号 */
@Schema(description = "亲属证件号") @Schema(description = "亲属证件号")
private String personId; private String personId;

View File

@@ -15,6 +15,14 @@ import java.math.BigDecimal;
@Schema(description = "员工资产信息导入失败记录") @Schema(description = "员工资产信息导入失败记录")
public class BaseStaffAssetImportFailureVO { public class BaseStaffAssetImportFailureVO {
/** Sheet名称 */
@Schema(description = "Sheet名称")
private String sheetName;
/** Excel行号 */
@Schema(description = "Excel行号")
private Integer rowNum;
/** 员工身份证号 */ /** 员工身份证号 */
@Schema(description = "员工身份证号") @Schema(description = "员工身份证号")
private String personId; private String personId;

View File

@@ -0,0 +1,23 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 员工双Sheet导入提交结果
*
* @author ruoyi
*/
@Data
@Schema(description = "员工双Sheet导入提交结果")
public class BaseStaffImportSubmitResultVO {
@Schema(description = "员工信息导入任务ID")
private String staffTaskId;
@Schema(description = "员工资产信息导入任务ID")
private String assetTaskId;
@Schema(description = "提交说明")
private String message;
}

View File

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

View File

@@ -21,7 +21,7 @@ public class CcdiIntermediaryRelativeVO implements Serializable {
@Schema(description = "人员ID") @Schema(description = "人员ID")
private String bizId; private String bizId;
@Schema(description = "所属中介ID") @Schema(description = "关联中介本人证件号码")
private String relatedNumId; private String relatedNumId;
@Schema(description = "姓名") @Schema(description = "姓名")

View File

@@ -0,0 +1,45 @@
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
*/
@Data
@Schema(description = "招投标供应商明细")
public class CcdiPurchaseTransactionSupplierVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
private Long id;
@Schema(description = "采购事项ID")
private String purchaseId;
@Schema(description = "供应商名称")
private String supplierName;
@Schema(description = "供应商统一信用代码")
private String supplierUscc;
@Schema(description = "供应商联系人")
private String contactPerson;
@Schema(description = "供应商联系电话")
private String contactPhone;
@Schema(description = "供应商银行账户")
private String supplierBankAccount;
@Schema(description = "是否中标1-是0-否")
private Integer isBidWinner;
@Schema(description = "排序")
private Integer sortOrder;
}

View File

@@ -8,15 +8,16 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 采购交易信息VO * 招投标信息VO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-06 * @date 2026-02-06
*/ */
@Data @Data
@Schema(description = "采购交易信息") @Schema(description = "招投标信息")
public class CcdiPurchaseTransactionVO implements Serializable { public class CcdiPurchaseTransactionVO implements Serializable {
@Serial @Serial
@@ -90,6 +91,14 @@ public class CcdiPurchaseTransactionVO implements Serializable {
@Schema(description = "供应商银行账户") @Schema(description = "供应商银行账户")
private String supplierBankAccount; private String supplierBankAccount;
/** 参与供应商数 */
@Schema(description = "参与供应商数")
private Integer supplierCount;
/** 供应商明细 */
@Schema(description = "供应商明细列表")
private List<CcdiPurchaseTransactionSupplierVO> supplierList;
/** 采购申请日期(或立项日期) */ /** 采购申请日期(或立项日期) */
@JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "采购申请日期") @Schema(description = "采购申请日期")

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-23
*/
@Data
@Schema(description = "员工亲属实体关联下拉选项")
public class CcdiStaffEnterpriseRelationOptionVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 亲属身份证号 */
@Schema(description = "亲属身份证号")
private String relationCertNo;
/** 亲属姓名 */
@Schema(description = "亲属姓名")
private String relationName;
/** 关联员工身份证号 */
@Schema(description = "关联员工身份证号")
private String staffPersonId;
/** 关联员工姓名 */
@Schema(description = "关联员工姓名")
private String staffPersonName;
}

View File

@@ -9,13 +9,13 @@ import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
* 员工实体关系信息VO * 员工亲属实体关VO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09
*/ */
@Data @Data
@Schema(description = "员工实体关系信息") @Schema(description = "员工亲属实体关")
public class CcdiStaffEnterpriseRelationVO implements Serializable { public class CcdiStaffEnterpriseRelationVO implements Serializable {
@Serial @Serial
@@ -25,13 +25,21 @@ public class CcdiStaffEnterpriseRelationVO implements Serializable {
@Schema(description = "主键ID") @Schema(description = "主键ID")
private Long id; private Long id;
/** 身份证号 */ /** 亲属身份证号 */
@Schema(description = "身份证号") @Schema(description = "亲属身份证号")
private String personId; private String personId;
/** 员工姓名 */ /** 亲属姓名 */
@Schema(description = "员工姓名") @Schema(description = "亲属姓名")
private String personName; private String relationName;
/** 关联员工身份证号 */
@Schema(description = "关联员工身份证号")
private String staffPersonId;
/** 关联员工姓名 */
@Schema(description = "关联员工姓名")
private String staffPersonName;
/** 关联人在企业的职务 */ /** 关联人在企业的职务 */
@Schema(description = "关联人在企业的职务") @Schema(description = "关联人在企业的职务")

View File

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

@@ -14,8 +14,14 @@ import java.math.BigDecimal;
@Schema(description = "导入失败记录") @Schema(description = "导入失败记录")
public class ImportFailureVO { public class ImportFailureVO {
@Schema(description = "Sheet名称")
private String sheetName;
@Schema(description = "Excel行号")
private Integer rowNum;
@Schema(description = "柜员号") @Schema(description = "柜员号")
private Long employeeId; private Long staffId;
@Schema(description = "姓名") @Schema(description = "姓名")
private String name; private String name;

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 = "姓名") @Schema(description = "姓名")
private String name; private String name;
@Schema(description = "证件号码")
private String personId;
@Schema(description = "人员类型") @Schema(description = "人员类型")
private String personType; private String personType;
@Schema(description = "人员子类型")
private String personSubType;
@Schema(description = "性别") @Schema(description = "性别")
private String gender; private String gender;
@Schema(description = "证件类型")
private String idType;
@Schema(description = "证件号码")
private String personId;
@Schema(description = "手机号码") @Schema(description = "手机号码")
private String mobile; private String mobile;
@Schema(description = "微信号")
private String wechatNo;
@Schema(description = "联系地址")
private String contactAddress;
@Schema(description = "所在公司") @Schema(description = "所在公司")
private String company; 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 = "错误信息") @Schema(description = "错误信息")
private String errorMessage; private String errorMessage;
} }

View File

@@ -6,15 +6,23 @@ import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
/** /**
* 采购交易信息导入失败记录VO * 招投标信息导入失败记录VO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-06 * @date 2026-02-06
*/ */
@Data @Data
@Schema(description = "采购交易信息导入失败记录") @Schema(description = "招投标信息导入失败记录")
public class PurchaseTransactionImportFailureVO { public class PurchaseTransactionImportFailureVO {
/** 失败来源Sheet */
@Schema(description = "失败来源Sheet")
private String sheetName;
/** 失败行号 */
@Schema(description = "失败行号")
private String sheetRowNum;
/** 采购事项ID */ /** 采购事项ID */
@Schema(description = "采购事项ID") @Schema(description = "采购事项ID")
private String purchaseId; private String purchaseId;

View File

@@ -13,12 +13,21 @@ import lombok.Data;
@Schema(description = "招聘信息导入失败记录") @Schema(description = "招聘信息导入失败记录")
public class RecruitmentImportFailureVO { public class RecruitmentImportFailureVO {
@Schema(description = "失败Sheet")
private String sheetName;
@Schema(description = "失败行号")
private String sheetRowNum;
@Schema(description = "招聘项目编号") @Schema(description = "招聘项目编号")
private String recruitId; private String recruitId;
@Schema(description = "招聘项目名称") @Schema(description = "招聘项目名称")
private String recruitName; private String recruitName;
@Schema(description = "职位名称")
private String posName;
@Schema(description = "应聘人员姓名") @Schema(description = "应聘人员姓名")
private String candName; private String candName;
@@ -28,6 +37,12 @@ public class RecruitmentImportFailureVO {
@Schema(description = "录用情况") @Schema(description = "录用情况")
private String admitStatus; private String admitStatus;
@Schema(description = "工作单位")
private String companyName;
@Schema(description = "岗位")
private String positionName;
@Schema(description = "错误信息") @Schema(description = "错误信息")
private String errorMessage; private String errorMessage;
} }

View File

@@ -7,22 +7,26 @@ import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
/** /**
* 员工实体关系信息导入失败记录VO * 员工亲属实体关导入失败记录VO
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09
*/ */
@Data @Data
@Schema(description = "员工实体关系信息导入失败记录") @Schema(description = "员工亲属实体关导入失败记录")
public class StaffEnterpriseRelationImportFailureVO implements Serializable { public class StaffEnterpriseRelationImportFailureVO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 身份证号 */ /** 亲属身份证号 */
@Schema(description = "身份证号") @Schema(description = "亲属身份证号")
private String personId; private String personId;
/** 亲属姓名 */
@Schema(description = "亲属姓名")
private String relationName;
/** 统一社会信用代码 */ /** 统一社会信用代码 */
@Schema(description = "统一社会信用代码") @Schema(description = "统一社会信用代码")
private String socialCreditCode; private String socialCreditCode;

View File

@@ -15,6 +15,14 @@ import java.math.BigDecimal;
@Schema(description = "员工亲属关系信息导入失败记录") @Schema(description = "员工亲属关系信息导入失败记录")
public class StaffFmyRelationImportFailureVO { public class StaffFmyRelationImportFailureVO {
/** Sheet名称 */
@Schema(description = "Sheet名称")
private String sheetName;
/** Excel行号 */
@Schema(description = "Excel行号")
private Integer rowNum;
/** 员工身份证号 */ /** 员工身份证号 */
@Schema(description = "员工身份证号") @Schema(description = "员工身份证号")
private String personId; private String personId;

View File

@@ -0,0 +1,24 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 员工亲属关系双Sheet导入提交结果
*
* @author ruoyi
* @date 2026-04-22
*/
@Data
@Schema(description = "员工亲属关系双Sheet导入提交结果")
public class StaffFmyRelationImportSubmitResultVO {
@Schema(description = "员工亲属关系导入任务ID")
private String relationTaskId;
@Schema(description = "亲属资产信息导入任务ID")
private String assetTaskId;
@Schema(description = "提交结果提示")
private String message;
}

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

@@ -14,10 +14,14 @@ import java.util.List;
@Mapper @Mapper
public interface CcdiIntermediaryEnterpriseRelationMapper extends BaseMapper<CcdiIntermediaryEnterpriseRelation> { public interface CcdiIntermediaryEnterpriseRelationMapper extends BaseMapper<CcdiIntermediaryEnterpriseRelation> {
int insertBatch(@Param("list") List<CcdiIntermediaryEnterpriseRelation> list);
List<CcdiIntermediaryEnterpriseRelationVO> selectByIntermediaryBizId(@Param("bizId") String bizId); List<CcdiIntermediaryEnterpriseRelationVO> selectByIntermediaryBizId(@Param("bizId") String bizId);
CcdiIntermediaryEnterpriseRelationVO selectDetailById(@Param("id") Long id); CcdiIntermediaryEnterpriseRelationVO selectDetailById(@Param("id") Long id);
boolean existsByIntermediaryBizIdAndSocialCreditCode(@Param("bizId") String bizId, boolean existsByIntermediaryBizIdAndSocialCreditCode(@Param("bizId") String bizId,
@Param("socialCreditCode") String socialCreditCode); @Param("socialCreditCode") String socialCreditCode);
List<String> batchExistsByCombinations(@Param("combinations") List<String> combinations);
} }

View File

@@ -35,6 +35,14 @@ public interface CcdiPurchaseTransactionMapper extends BaseMapper<CcdiPurchaseTr
*/ */
CcdiPurchaseTransactionVO selectTransactionById(@Param("purchaseId") String purchaseId); CcdiPurchaseTransactionVO selectTransactionById(@Param("purchaseId") String purchaseId);
/**
* 删除指定采购事项ID对应的供应商明细
*
* @param purchaseIds 采购事项ID列表
* @return 删除行数
*/
int deleteSuppliersByPurchaseIds(@Param("purchaseIds") List<String> purchaseIds);
/** /**
* 批量插入采购交易数据 * 批量插入采购交易数据
* *

View File

@@ -0,0 +1,12 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier;
import org.apache.ibatis.annotations.Mapper;
/**
* 招投标供应商明细Mapper
*/
@Mapper
public interface CcdiPurchaseTransactionSupplierMapper extends BaseMapper<CcdiPurchaseTransactionSupplier> {
}

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation; import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -38,6 +39,14 @@ public interface CcdiStaffEnterpriseRelationMapper extends BaseMapper<CcdiStaffE
*/ */
CcdiStaffEnterpriseRelationVO selectRelationById(@Param("id") Long id); CcdiStaffEnterpriseRelationVO selectRelationById(@Param("id") Long id);
/**
* 查询有效员工亲属下拉选项
*
* @param query 搜索关键词
* @return 下拉选项
*/
List<CcdiStaffEnterpriseRelationOptionVO> selectFamilyOptions(@Param("query") String query);
/** /**
* 判断身份证号和统一社会信用代码的组合是否已存在 * 判断身份证号和统一社会信用代码的组合是否已存在
* *
@@ -57,6 +66,14 @@ public interface CcdiStaffEnterpriseRelationMapper extends BaseMapper<CcdiStaffE
*/ */
Set<String> batchExistsByCombinations(@Param("combinations") List<String> combinations); Set<String> batchExistsByCombinations(@Param("combinations") List<String> combinations);
/**
* 根据亲属身份证号批量置无效
*
* @param personId 亲属身份证号
* @return 影响行数
*/
int invalidateByFamilyCertNo(@Param("personId") String personId);
/** /**
* 批量插入员工实体关系数据 * 批量插入员工实体关系数据
* *

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

@@ -16,9 +16,8 @@ public interface ICcdiBaseStaffImportService {
* 异步导入员工数据 * 异步导入员工数据
* *
* @param excelList Excel数据列表 * @param excelList Excel数据列表
* @param isUpdateSupport 是否更新已存在的数据
*/ */
void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport, String taskId); void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, String taskId);
/** /**
* 查询导入状态 * 查询导入状态

View File

@@ -79,10 +79,9 @@ public interface ICcdiBaseStaffService {
* 导入员工数据 * 导入员工数据
* *
* @param excelList Excel实体列表 * @param excelList Excel实体列表
* @param isUpdateSupport 是否更新支持
* @return 结果 * @return 结果
*/ */
String importBaseStaff(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport); String importBaseStaff(List<CcdiBaseStaffExcel> 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; import java.util.List;
/** /**
* 个人中介异步导入Service接口 * 中介信息异步导入Service接口
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-06 * @date 2026-02-06
@@ -15,7 +15,7 @@ import java.util.List;
public interface ICcdiIntermediaryPersonImportService { public interface ICcdiIntermediaryPersonImportService {
/** /**
* 异步导入个人中介数据 * 异步导入中介信息
* *
* @param excelList Excel数据列表 * @param excelList Excel数据列表
* @param taskId 任务ID * @param taskId 任务ID

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.*; 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.CcdiIntermediaryEntityExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel; import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO; import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO;
@@ -168,7 +169,7 @@ public interface ICcdiIntermediaryService {
int deleteIntermediaryByIds(String[] ids); int deleteIntermediaryByIds(String[] ids);
/** /**
* 校验人员ID唯一性 * 校验中介本人证件号码唯一性
* *
* @param personId 人员ID * @param personId 人员ID
* @param bizId 排除的人员ID * @param bizId 排除的人员ID
@@ -193,6 +194,14 @@ public interface ICcdiIntermediaryService {
*/ */
String importIntermediaryPerson(List<CcdiIntermediaryPersonExcel> list); 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; package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel; import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO; import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
@@ -17,11 +18,17 @@ public interface ICcdiPurchaseTransactionImportService {
/** /**
* 异步导入采购交易数据 * 异步导入采购交易数据
* *
* @param excelList Excel数据列表 * @param mainExcelList 主信息Excel数据列表
* @param supplierExcelList 供应商明细Excel数据列表
* @param taskId 任务ID * @param taskId 任务ID
* @param userName 当前用户名 * @param userName 当前用户名
*/ */
void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList, String taskId, String userName); void importTransactionAsync(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList,
String taskId,
String userName
);
/** /**
* 查询导入状态 * 查询导入状态

View File

@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel; import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO; import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO;
import java.util.List; import java.util.List;
@@ -77,8 +78,12 @@ public interface ICcdiPurchaseTransactionService {
/** /**
* 导入采购交易数据(异步) * 导入采购交易数据(异步)
* *
* @param excelList Excel实体列表 * @param mainExcelList 主信息Excel实体列表
* @param supplierExcelList 供应商明细Excel实体列表
* @return 任务ID * @return 任务ID
*/ */
String importTransaction(List<CcdiPurchaseTransactionExcel> excelList); String importTransaction(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
);
} }

View File

@@ -7,7 +7,7 @@ import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureV
import java.util.List; import java.util.List;
/** /**
* 员工实体关系信息异步导入服务层 * 员工亲属实体关异步导入服务层
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09

View File

@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
import java.util.List; import java.util.List;
@@ -42,6 +43,14 @@ public interface ICcdiStaffEnterpriseRelationService {
*/ */
List<CcdiStaffEnterpriseRelationExcel> selectRelationListForExport(CcdiStaffEnterpriseRelationQueryDTO queryDTO); List<CcdiStaffEnterpriseRelationExcel> selectRelationListForExport(CcdiStaffEnterpriseRelationQueryDTO queryDTO);
/**
* 查询有效员工亲属下拉选项
*
* @param query 搜索关键词
* @return 下拉选项
*/
List<CcdiStaffEnterpriseRelationOptionVO> selectFamilyOptions(String query);
/** /**
* 查询员工实体关系详情 * 查询员工实体关系详情
* *

View File

@@ -1,6 +1,7 @@
package com.ruoyi.info.collection.service; package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; 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.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO; import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO;
@@ -21,7 +22,8 @@ public interface ICcdiStaffRecruitmentImportService {
* @param taskId 任务ID * @param taskId 任务ID
* @param userName 用户名 * @param userName 用户名
*/ */
void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList, void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> recruitmentList,
List<CcdiStaffRecruitmentWorkExcel> workList,
String taskId, String taskId,
String userName); 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.CcdiStaffRecruitmentEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; 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.CcdiStaffRecruitmentVO;
import java.util.List; import java.util.List;
@@ -80,5 +81,6 @@ public interface ICcdiStaffRecruitmentService {
* @param excelList Excel实体列表 * @param excelList Excel实体列表
* @return 结果 * @return 结果
*/ */
String importRecruitment(List<CcdiStaffRecruitmentExcel> excelList); String importRecruitment(List<CcdiStaffRecruitmentExcel> recruitmentList,
List<CcdiStaffRecruitmentWorkExcel> workList);
} }

View File

@@ -39,6 +39,8 @@ import java.util.concurrent.TimeUnit;
public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportService { public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportService {
private static final String STATUS_KEY_PREFIX = "import:assetInfo:"; private static final String STATUS_KEY_PREFIX = "import:assetInfo:";
private static final String SHEET_NAME = "亲属资产信息";
private static final int EXCEL_DATA_START_ROW = 2;
@Resource @Resource
private CcdiAssetInfoMapper assetInfoMapper; private CcdiAssetInfoMapper assetInfoMapper;
@@ -91,7 +93,8 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
Map<String, Set<String>> ownerMap = buildOwnerMap(personIds); Map<String, Set<String>> ownerMap = buildOwnerMap(personIds);
for (CcdiAssetInfoExcel excel : excelList) { for (int i = 0; i < excelList.size(); i++) {
CcdiAssetInfoExcel excel = excelList.get(i);
try { try {
validateExcel(excel); validateExcel(excel);
Set<String> familyIds = ownerMap.get(excel.getPersonId()); Set<String> familyIds = ownerMap.get(excel.getPersonId());
@@ -111,6 +114,8 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
} catch (Exception e) { } catch (Exception e) {
AssetImportFailureVO failureVO = new AssetImportFailureVO(); AssetImportFailureVO failureVO = new AssetImportFailureVO();
BeanUtils.copyProperties(excel, failureVO); BeanUtils.copyProperties(excel, failureVO);
failureVO.setSheetName(SHEET_NAME);
failureVO.setRowNum(i + EXCEL_DATA_START_ROW);
failureVO.setErrorMessage(e.getMessage()); failureVO.setErrorMessage(e.getMessage());
failures.add(failureVO); failures.add(failureVO);
} }

View File

@@ -1,6 +1,7 @@
package com.ruoyi.info.collection.service.impl; package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.info.collection.domain.CcdiAssetInfo; import com.ruoyi.info.collection.domain.CcdiAssetInfo;
@@ -90,14 +91,24 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
.toList(); .toList();
Map<String, Set<String>> ownerMap = buildOwnerMap(personIds); Map<String, Set<String>> ownerMap = buildOwnerMap(personIds);
Set<String> existingAssetKeys = buildExistingAssetKeys(personIds);
Set<String> importedAssetKeys = new java.util.LinkedHashSet<>();
for (CcdiBaseStaffAssetInfoExcel excel : excelList) { for (int i = 0; i < excelList.size(); i++) {
CcdiBaseStaffAssetInfoExcel excel = excelList.get(i);
try { try {
validateExcel(excel); validateExcel(excel);
Set<String> familyIds = ownerMap.get(excel.getPersonId()); Set<String> familyIds = ownerMap.get(excel.getPersonId());
if (familyIds == null || familyIds.isEmpty()) { if (familyIds == null || familyIds.isEmpty()) {
throw new RuntimeException("员工资产导入仅支持员工本人证件号"); throw new RuntimeException("员工资产导入仅支持员工本人证件号");
} }
String assetKey = buildAssetKey(excel.getPersonId(), excel.getAssetMainType(), excel.getAssetSubType(), excel.getAssetName());
if (existingAssetKeys.contains(assetKey)) {
throw new RuntimeException("资产记录已存在");
}
if (!importedAssetKeys.add(assetKey)) {
throw new RuntimeException("资产记录在导入文件中重复");
}
CcdiAssetInfo assetInfo = new CcdiAssetInfo(); CcdiAssetInfo assetInfo = new CcdiAssetInfo();
BeanUtils.copyProperties(excel, assetInfo); BeanUtils.copyProperties(excel, assetInfo);
@@ -109,6 +120,8 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
} catch (Exception e) { } catch (Exception e) {
BaseStaffAssetImportFailureVO failureVO = new BaseStaffAssetImportFailureVO(); BaseStaffAssetImportFailureVO failureVO = new BaseStaffAssetImportFailureVO();
BeanUtils.copyProperties(excel, failureVO); BeanUtils.copyProperties(excel, failureVO);
failureVO.setSheetName("员工资产信息");
failureVO.setRowNum(i + 2);
failureVO.setErrorMessage(e.getMessage()); failureVO.setErrorMessage(e.getMessage());
failures.add(failureVO); failures.add(failureVO);
} }
@@ -168,6 +181,18 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
return result; return result;
} }
private Set<String> buildExistingAssetKeys(List<String> personIds) {
if (personIds == null || personIds.isEmpty()) {
return Set.of();
}
LambdaQueryWrapper<CcdiAssetInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CcdiAssetInfo::getPersonId, personIds);
return assetInfoMapper.selectList(wrapper).stream()
.filter(asset -> StringUtils.equals(asset.getFamilyId(), asset.getPersonId()))
.map(asset -> buildAssetKey(asset.getPersonId(), asset.getAssetMainType(), asset.getAssetSubType(), asset.getAssetName()))
.collect(java.util.stream.Collectors.toCollection(java.util.LinkedHashSet::new));
}
private void mergeOwnerMappings(Map<String, Set<String>> result, List<Map<String, String>> mappings) { private void mergeOwnerMappings(Map<String, Set<String>> result, List<Map<String, String>> mappings) {
if (mappings == null) { if (mappings == null) {
return; return;
@@ -203,6 +228,14 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
} }
} }
private String buildAssetKey(String personId, String assetMainType, String assetSubType, String assetName) {
return String.join("|",
StringUtils.nvl(personId, ""),
StringUtils.nvl(assetMainType, ""),
StringUtils.nvl(assetSubType, ""),
StringUtils.nvl(assetName, ""));
}
private void updateImportStatus(String taskId, String status, ImportResult result) { private void updateImportStatus(String taskId, String status, ImportResult result) {
Map<String, Object> statusData = new HashMap<>(); Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status); statusData.put("status", status);

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.info.collection.domain.CcdiBaseStaff; import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel; import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
@@ -13,6 +14,7 @@ import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils; import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.IdCardUtil; import com.ruoyi.common.utils.IdCardUtil;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.mapper.SysDeptMapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -43,16 +45,18 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
@Resource @Resource
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@Resource
private SysDeptMapper deptMapper;
@Override @Override
@Async @Async
public void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport, String taskId) { public void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, String taskId) {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// 记录导入开始 // 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "员工基础信息", excelList.size(), "系统"); ImportLogUtils.logImportStart(log, taskId, "员工基础信息", excelList.size(), "系统");
List<CcdiBaseStaff> newRecords = new ArrayList<>(); List<CcdiBaseStaff> newRecords = new ArrayList<>();
List<CcdiBaseStaff> updateRecords = new ArrayList<>();
List<ImportFailureVO> failures = new ArrayList<>(); List<ImportFailureVO> failures = new ArrayList<>();
// 批量查询已存在的员工ID和身份证号 // 批量查询已存在的员工ID和身份证号
@@ -75,13 +79,12 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
CcdiBaseStaffAddDTO addDTO = new CcdiBaseStaffAddDTO(); CcdiBaseStaffAddDTO addDTO = new CcdiBaseStaffAddDTO();
BeanUtils.copyProperties(excel, addDTO); BeanUtils.copyProperties(excel, addDTO);
// 验证数据(支持更新模式) validateStaffData(addDTO, existingIds, existingIdCards);
validateStaffData(addDTO, isUpdateSupport, existingIds, existingIdCards);
CcdiBaseStaff staff = new CcdiBaseStaff(); CcdiBaseStaff staff = new CcdiBaseStaff();
BeanUtils.copyProperties(excel, staff); BeanUtils.copyProperties(excel, staff);
// 统一检查Excel内重复(更新和新增两个分支都需要检查) // 统一检查Excel内重复
if (processedStaffIds.contains(excel.getStaffId())) { if (processedStaffIds.contains(excel.getStaffId())) {
throw new RuntimeException(String.format("员工ID[%d]在导入文件中重复,已跳过此条记录", excel.getStaffId())); throw new RuntimeException(String.format("员工ID[%d]在导入文件中重复,已跳过此条记录", excel.getStaffId()));
} }
@@ -90,20 +93,7 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
throw new RuntimeException(String.format("身份证号[%s]在导入文件中重复,已跳过此条记录", excel.getIdCard())); throw new RuntimeException(String.format("身份证号[%s]在导入文件中重复,已跳过此条记录", excel.getIdCard()));
} }
// 检查员工ID是否在数据库中已存在
if (existingIds.contains(excel.getStaffId())) {
// 员工ID已存在于数据库
if (!isUpdateSupport) {
throw new RuntimeException("员工ID已存在且未启用更新支持");
}
// 通过检查,添加到更新列表
updateRecords.add(staff);
} else {
// 员工ID不存在,添加到新增列表
newRecords.add(staff); newRecords.add(staff);
}
// 统一标记为已处理(只有成功添加到列表后才会执行到这里) // 统一标记为已处理(只有成功添加到列表后才会执行到这里)
if (excel.getStaffId() != null) { if (excel.getStaffId() != null) {
@@ -115,11 +105,13 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
// 记录进度 // 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(), ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size() + updateRecords.size(), failures.size()); newRecords.size(), failures.size());
} catch (Exception e) { } catch (Exception e) {
ImportFailureVO failure = new ImportFailureVO(); ImportFailureVO failure = new ImportFailureVO();
BeanUtils.copyProperties(excel, failure); BeanUtils.copyProperties(excel, failure);
failure.setSheetName("员工信息");
failure.setRowNum(i + 2);
failure.setErrorMessage(e.getMessage()); failure.setErrorMessage(e.getMessage());
failures.add(failure); failures.add(failure);
@@ -137,13 +129,6 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
saveBatch(newRecords, 500); saveBatch(newRecords, 500);
} }
// 批量更新已有数据(先删除再插入)
if (!updateRecords.isEmpty() && isUpdateSupport) {
ImportLogUtils.logBatchOperationStart(log, taskId, "更新",
(updateRecords.size() + 499) / 500, 500);
baseStaffMapper.insertOrUpdateBatch(updateRecords);
}
// 保存失败记录到Redis // 保存失败记录到Redis
if (!failures.isEmpty()) { if (!failures.isEmpty()) {
try { try {
@@ -157,7 +142,7 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
ImportResult result = new ImportResult(); ImportResult result = new ImportResult();
result.setTotalCount(excelList.size()); result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size() + updateRecords.size()); result.setSuccessCount(newRecords.size());
result.setFailureCount(failures.size()); result.setFailureCount(failures.size());
// 更新最终状态 // 更新最终状态
@@ -299,11 +284,10 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
* 验证员工数据 * 验证员工数据
* *
* @param addDTO 新增DTO * @param addDTO 新增DTO
* @param isUpdateSupport 是否支持更新
* @param existingIds 已存在的员工ID集合(导入场景使用,传null表示单条新增) * @param existingIds 已存在的员工ID集合(导入场景使用,传null表示单条新增)
* @param existingIdCards 已存在的身份证号集合(导入场景使用,传null表示单条新增) * @param existingIdCards 已存在的身份证号集合(导入场景使用,传null表示单条新增)
*/ */
public void validateStaffData(CcdiBaseStaffAddDTO addDTO, Boolean isUpdateSupport, Set<Long> existingIds, Set<String> existingIdCards) { public void validateStaffData(CcdiBaseStaffAddDTO addDTO, Set<Long> existingIds, Set<String> existingIdCards) {
// 验证必填字段 // 验证必填字段
if (StringUtils.isEmpty(addDTO.getName())) { if (StringUtils.isEmpty(addDTO.getName())) {
throw new RuntimeException("姓名不能为空"); throw new RuntimeException("姓名不能为空");
@@ -326,6 +310,7 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
if (StringUtils.isEmpty(addDTO.getStatus())) { if (StringUtils.isEmpty(addDTO.getStatus())) {
throw new RuntimeException("状态不能为空"); throw new RuntimeException("状态不能为空");
} }
validateDeptId(addDTO.getDeptId());
// 验证身份证号格式 // 验证身份证号格式
String idCardError = IdCardUtil.getErrorMessage(addDTO.getIdCard()); String idCardError = IdCardUtil.getErrorMessage(addDTO.getIdCard());
@@ -347,14 +332,13 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
throw new RuntimeException("该身份证号已存在"); throw new RuntimeException("该身份证号已存在");
} }
} else { } else {
// 导入场景:如果员工ID不存在,才检查身份证号唯一性 if (existingIds.contains(addDTO.getStaffId())) {
if (!existingIds.contains(addDTO.getStaffId())) { throw new RuntimeException("该员工ID已存在");
// 使用批量查询的结果检查身份证号唯一性 }
if (existingIdCards != null && existingIdCards.contains(addDTO.getIdCard())) { if (existingIdCards != null && existingIdCards.contains(addDTO.getIdCard())) {
throw new RuntimeException("该身份证号已存在"); throw new RuntimeException("该身份证号已存在");
} }
} }
}
// 验证状态 // 验证状态
if (!"0".equals(addDTO.getStatus()) && !"1".equals(addDTO.getStatus())) { if (!"0".equals(addDTO.getStatus()) && !"1".equals(addDTO.getStatus())) {
@@ -378,4 +362,11 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
throw new RuntimeException(fieldLabel + "最多保留2位小数"); throw new RuntimeException(fieldLabel + "最多保留2位小数");
} }
} }
private void validateDeptId(Long deptId) {
SysDept dept = deptMapper.selectDeptById(deptId);
if (dept == null || !"0".equals(dept.getStatus()) || !"0".equals(dept.getDelFlag())) {
throw new RuntimeException(String.format("所属部门ID[%d]不存在或已停用/删除,请检查机构号", deptId));
}
}
} }

View File

@@ -212,12 +212,11 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
* 导入员工数据 * 导入员工数据
* *
* @param excelList Excel实体列表 * @param excelList Excel实体列表
* @param isUpdateSupport 是否更新支持
* @return 结果 * @return 结果
*/ */
@Override @Override
@Transactional @Transactional
public String importBaseStaff(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport) { public String importBaseStaff(List<CcdiBaseStaffExcel> excelList) {
String taskId = UUID.randomUUID().toString(); String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
@@ -236,7 +235,7 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
redisTemplate.opsForHash().putAll(statusKey, statusData); redisTemplate.opsForHash().putAll(statusKey, statusData);
redisTemplate.expire(statusKey, 7, java.util.concurrent.TimeUnit.DAYS); redisTemplate.expire(statusKey, 7, java.util.concurrent.TimeUnit.DAYS);
importAsyncService.importBaseStaffAsync(excelList, isUpdateSupport, taskId); importAsyncService.importBaseStaffAsync(excelList, taskId);
return taskId; return taskId;
} }

View File

@@ -131,10 +131,6 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
if (!excel.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) { if (!excel.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
throw new RuntimeException("统一社会信用代码格式不正确"); throw new RuntimeException("统一社会信用代码格式不正确");
} }
if (StringUtils.isEmpty(excel.getStatus())) {
throw new RuntimeException("经营状态不能为空");
}
String riskLevel = EnterpriseRiskLevel.resolveCode(StringUtils.trim(excel.getRiskLevel())); String riskLevel = EnterpriseRiskLevel.resolveCode(StringUtils.trim(excel.getRiskLevel()));
if (riskLevel == null) { if (riskLevel == null) {
throw new RuntimeException("风险等级不在允许范围内"); throw new RuntimeException("风险等级不在允许范围内");
@@ -143,10 +139,6 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
if (entSource == null) { if (entSource == null) {
throw new RuntimeException("企业来源不在允许范围内"); throw new RuntimeException("企业来源不在允许范围内");
} }
String dataSource = resolveDataSourceCode(StringUtils.trim(excel.getDataSource()));
if (dataSource == null) {
throw new RuntimeException("数据来源不在允许范围内");
}
if (existingCreditCodes.contains(excel.getSocialCreditCode())) { if (existingCreditCodes.contains(excel.getSocialCreditCode())) {
throw new RuntimeException(String.format("统一社会信用代码[%s]已存在,请勿重复导入", excel.getSocialCreditCode())); throw new RuntimeException(String.format("统一社会信用代码[%s]已存在,请勿重复导入", excel.getSocialCreditCode()));
@@ -159,8 +151,8 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
BeanUtils.copyProperties(excel, entity); BeanUtils.copyProperties(excel, entity);
entity.setRiskLevel(riskLevel); entity.setRiskLevel(riskLevel);
entity.setEntSource(entSource); entity.setEntSource(entSource);
entity.setDataSource(dataSource); entity.setDataSource(DataSource.IMPORT.getCode());
entity.setStatus(StringUtils.trim(excel.getStatus())); entity.setStatus(trimToNull(excel.getStatus()));
entity.setCreatedBy(userName); entity.setCreatedBy(userName);
entity.setUpdatedBy(userName); entity.setUpdatedBy(userName);
return entity; return entity;
@@ -206,15 +198,6 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
redisTemplate.opsForHash().putAll(buildStatusKey(taskId), statusData); 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) { private String buildStatusKey(String taskId) {
return "import:enterpriseBaseInfo:" + taskId; return "import:enterpriseBaseInfo:" + taskId;
} }
@@ -222,4 +205,11 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
private String buildFailuresKey(String taskId) { private String buildFailuresKey(String taskId) {
return "import:enterpriseBaseInfo:" + taskId + ":failures"; return "import:enterpriseBaseInfo:" + taskId + ":failures";
} }
private String trimToNull(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
return value.trim();
}
} }

View File

@@ -4,7 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; 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.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.CcdiEnterpriseBaseInfoAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoQueryDTO;
@@ -14,6 +17,9 @@ import com.ruoyi.info.collection.enums.DataSource;
import com.ruoyi.info.collection.enums.EnterpriseRiskLevel; import com.ruoyi.info.collection.enums.EnterpriseRiskLevel;
import com.ruoyi.info.collection.enums.EnterpriseSource; import com.ruoyi.info.collection.enums.EnterpriseSource;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper; 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.ICcdiEnterpriseBaseInfoImportService;
import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoService; import com.ruoyi.info.collection.service.ICcdiEnterpriseBaseInfoService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -25,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.StringJoiner;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -40,6 +47,15 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
@Resource @Resource
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper; private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
@Resource
private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper;
@Resource
private CcdiCustEnterpriseRelationMapper custEnterpriseRelationMapper;
@Resource
private CcdiIntermediaryEnterpriseRelationMapper intermediaryEnterpriseRelationMapper;
@Resource @Resource
private ICcdiEnterpriseBaseInfoImportService enterpriseBaseInfoImportService; private ICcdiEnterpriseBaseInfoImportService enterpriseBaseInfoImportService;
@@ -69,10 +85,12 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
if (enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode()) != null) { if (enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode()) != null) {
throw new RuntimeException("该统一社会信用代码已存在"); throw new RuntimeException("该统一社会信用代码已存在");
} }
validateEnumFields(addDTO.getStatus(), addDTO.getRiskLevel(), addDTO.getEntSource(), addDTO.getDataSource()); validateRiskLevelAndEnterpriseSource(addDTO.getRiskLevel(), addDTO.getEntSource());
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo(); CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
BeanUtils.copyProperties(addDTO, entity); BeanUtils.copyProperties(addDTO, entity);
entity.setStatus(trimToNull(addDTO.getStatus()));
entity.setDataSource(DataSource.MANUAL.getCode());
return enterpriseBaseInfoMapper.insert(entity); return enterpriseBaseInfoMapper.insert(entity);
} }
@@ -87,6 +105,8 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo(); CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
BeanUtils.copyProperties(editDTO, entity); BeanUtils.copyProperties(editDTO, entity);
entity.setStatus(trimToNull(editDTO.getStatus()));
entity.setDataSource(existing.getDataSource());
return enterpriseBaseInfoMapper.updateById(entity); return enterpriseBaseInfoMapper.updateById(entity);
} }
@@ -96,6 +116,9 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
if (socialCreditCodes == null || socialCreditCodes.length == 0) { if (socialCreditCodes == null || socialCreditCodes.length == 0) {
return 0; return 0;
} }
for (String socialCreditCode : socialCreditCodes) {
validateDeleteRelations(socialCreditCode);
}
return enterpriseBaseInfoMapper.deleteBatchIds(List.of(socialCreditCodes)); return enterpriseBaseInfoMapper.deleteBatchIds(List.of(socialCreditCodes));
} }
@@ -157,18 +180,22 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
} }
private void validateEnumFields(String status, String riskLevel, String entSource, String dataSource) { private void validateEnumFields(String status, String riskLevel, String entSource, String dataSource) {
if (StringUtils.isEmpty(status)) { validateRiskLevelAndEnterpriseSource(riskLevel, entSource);
throw new RuntimeException("经营状态不能为空"); if (StringUtils.isNotEmpty(status) && StringUtils.trim(status).length() > 50) {
throw new RuntimeException("经营状态长度不能超过50个字符");
} }
if (!containsDataSource(dataSource)) {
throw new RuntimeException("数据来源不在允许范围内");
}
}
private void validateRiskLevelAndEnterpriseSource(String riskLevel, String entSource) {
if (!EnterpriseRiskLevel.contains(riskLevel)) { if (!EnterpriseRiskLevel.contains(riskLevel)) {
throw new RuntimeException("风险等级不在允许范围内"); throw new RuntimeException("风险等级不在允许范围内");
} }
if (!EnterpriseSource.contains(entSource)) { if (!EnterpriseSource.contains(entSource)) {
throw new RuntimeException("企业来源不在允许范围内"); throw new RuntimeException("企业来源不在允许范围内");
} }
if (!containsDataSource(dataSource)) {
throw new RuntimeException("数据来源不在允许范围内");
}
} }
private boolean containsDataSource(String code) { private boolean containsDataSource(String code) {
@@ -179,4 +206,30 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
} }
return false; return false;
} }
private String trimToNull(String value) {
if (StringUtils.isEmpty(value)) {
return null;
}
return value.trim();
}
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; 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.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* 个人中介异步导入Service实现 * 中介信息异步导入实现
*
* @author ruoyi
* @date 2026-02-06
*/ */
@Service @Service
@EnableAsync @EnableAsync
@@ -38,6 +41,8 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
private static final Logger log = LoggerFactory.getLogger(CcdiIntermediaryPersonImportServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(CcdiIntermediaryPersonImportServiceImpl.class);
private static final String STATUS_KEY_PREFIX = "import:intermediary:";
@Resource @Resource
private CcdiBizIntermediaryMapper intermediaryMapper; private CcdiBizIntermediaryMapper intermediaryMapper;
@@ -47,110 +52,104 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
@Override @Override
@Async @Async
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList, public void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList, String taskId, String userName) {
String taskId,
String userName) {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
ImportLogUtils.logImportStart(log, taskId, "中介信息", excelList.size(), userName);
// 记录导入开始 List<CcdiIntermediaryPersonExcel> ownerRows = new ArrayList<>();
ImportLogUtils.logImportStart(log, taskId, "个人中介", excelList.size(), userName); List<CcdiIntermediaryPersonExcel> relativeRows = new ArrayList<>();
List<CcdiBizIntermediary> newRecords = new ArrayList<>();
List<IntermediaryPersonImportFailureVO> failures = 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++) { for (int i = 0; i < excelList.size(); i++) {
CcdiIntermediaryPersonExcel excel = excelList.get(i); CcdiIntermediaryPersonExcel excel = excelList.get(i);
try { try {
// 验证数据 validateCommonRow(excel);
validatePersonData(excel, existingPersonIds); if (isOwnerRow(excel)) {
validateOwnerRow(excel);
CcdiBizIntermediary intermediary = new CcdiBizIntermediary(); ownerRows.add(excel);
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()));
} else { } else {
newRecords.add(intermediary); validateRelativeRow(excel);
excelProcessedIds.add(excel.getPersonId()); // 标记为已处理 relativeRows.add(excel);
} }
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
} catch (Exception e) { } catch (Exception e) {
failures.add(createFailureVO(excel, e.getMessage())); failures.add(createFailureVO(excel, e.getMessage()));
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(),
// 记录验证失败日志 String.format("姓名=%s, 证件号码=%s", excel.getName(), excel.getPersonId()));
String keyData = String.format("姓名=%s, 证件号码=%s",
excel.getName(), excel.getPersonId());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
} }
} }
// 批量插入新数据 Set<String> existingOwnerPersonIds = getExistingOwnerPersonIds(ownerRows);
if (!newRecords.isEmpty()) { Set<String> existingOwnerRefs = getExistingOwnerRefs(relativeRows);
ImportLogUtils.logBatchOperationStart(log, taskId, "插入", Set<String> existingRelativeCombinations = getExistingRelativeCombinations(relativeRows);
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 保存失败记录到Redis List<CcdiBizIntermediary> successRecords = new ArrayList<>();
if (!failures.isEmpty()) { Set<String> importedOwnerPersonIds = new HashSet<>();
for (CcdiIntermediaryPersonExcel ownerExcel : ownerRows) {
try { try {
String failuresKey = "import:intermediary:" + taskId + ":failures"; String ownerPersonId = ownerExcel.getPersonId();
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); if (existingOwnerPersonIds.contains(ownerPersonId)) {
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size()); throw new RuntimeException(String.format("中介本人证件号码[%s]已存在,请勿重复导入", ownerPersonId));
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
} }
if (!importedOwnerPersonIds.add(ownerPersonId)) {
throw new RuntimeException(String.format("中介本人证件号码[%s]在导入文件中重复", ownerPersonId));
}
successRecords.add(buildRecord(ownerExcel, userName, null));
} catch (Exception 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(); ImportResult result = new ImportResult();
result.setTotalCount(excelList.size()); result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size()); result.setSuccessCount(successRecords.size());
result.setFailureCount(failures.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; long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "个人中介", ImportLogUtils.logImportComplete(log, taskId, "中介信息",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
} }
@Override @Override
public ImportStatusVO getImportStatus(String taskId) { public ImportStatusVO getImportStatus(String taskId) {
String key = "import:intermediary:" + taskId; String key = statusKey(taskId);
Boolean hasKey = redisTemplate.hasKey(key); if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期"); throw new RuntimeException("任务不存在或已过期");
} }
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key); Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO(); ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId")); statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status")); statusVO.setStatus((String) statusMap.get("status"));
@@ -161,83 +160,120 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
statusVO.setStartTime((Long) statusMap.get("startTime")); statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime")); statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message")); statusVO.setMessage((String) statusMap.get("message"));
return statusVO; return statusVO;
} }
@Override @Override
public List<IntermediaryPersonImportFailureVO> getImportFailures(String taskId) { public List<IntermediaryPersonImportFailureVO> getImportFailures(String taskId) {
String key = "import:intermediary:" + taskId + ":failures"; Object failuresObj = redisTemplate.opsForValue().get(failureKey(taskId));
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) { if (failuresObj == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
return JSON.parseArray(JSON.toJSONString(failuresObj), IntermediaryPersonImportFailureVO.class); return JSON.parseArray(JSON.toJSONString(failuresObj), IntermediaryPersonImportFailureVO.class);
} }
/** private boolean isOwnerRow(CcdiIntermediaryPersonExcel excel) {
* 批量查询已存在的证件号 return "本人".equals(excel.getPersonSubType());
*/ }
private Set<String> getExistingPersonIds(List<CcdiIntermediaryPersonExcel> excelList) {
List<String> personIds = excelList.stream() 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) .map(CcdiIntermediaryPersonExcel::getPersonId)
.filter(StringUtils::isNotEmpty) .filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList()); .collect(Collectors.toList());
if (ownerPersonIds.isEmpty()) {
if (personIds.isEmpty()) {
return Collections.emptySet(); return Collections.emptySet();
} }
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CcdiBizIntermediary::getPersonId, personIds); wrapper.eq(CcdiBizIntermediary::getPersonSubType, "本人")
List<CcdiBizIntermediary> existingIntermediaries = intermediaryMapper.selectList(wrapper); .in(CcdiBizIntermediary::getPersonId, ownerPersonIds);
return intermediaryMapper.selectList(wrapper).stream()
return existingIntermediaries.stream()
.map(CcdiBizIntermediary::getPersonId) .map(CcdiBizIntermediary::getPersonId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
/** private Set<String> getExistingOwnerRefs(List<CcdiIntermediaryPersonExcel> relativeRows) {
* 批量保存(使用ON DUPLICATE KEY UPDATE) List<String> ownerRefs = relativeRows.stream()
*/ .map(CcdiIntermediaryPersonExcel::getRelatedNumId)
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) .filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList()); .collect(Collectors.toList());
if (ownerRefs.isEmpty()) {
if (personIds.isEmpty()) {
return Collections.emptySet(); return Collections.emptySet();
} }
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CcdiBizIntermediary::getPersonId, personIds); wrapper.eq(CcdiBizIntermediary::getPersonSubType, "本人")
List<CcdiBizIntermediary> existing = intermediaryMapper.selectList(wrapper); .in(CcdiBizIntermediary::getPersonId, ownerRefs);
return intermediaryMapper.selectList(wrapper).stream()
return existing.stream()
.map(CcdiBizIntermediary::getPersonId) .map(CcdiBizIntermediary::getPersonId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
/** private Set<String> getExistingRelativeCombinations(List<CcdiIntermediaryPersonExcel> relativeRows) {
* 创建失败记录VO 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;
}
private IntermediaryPersonImportFailureVO createFailureVO(CcdiIntermediaryPersonExcel excel, String errorMsg) { private IntermediaryPersonImportFailureVO createFailureVO(CcdiIntermediaryPersonExcel excel, String errorMsg) {
IntermediaryPersonImportFailureVO failure = new IntermediaryPersonImportFailureVO(); IntermediaryPersonImportFailureVO failure = new IntermediaryPersonImportFailureVO();
BeanUtils.copyProperties(excel, failure); BeanUtils.copyProperties(excel, failure);
@@ -245,73 +281,31 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
return failure; return failure;
} }
/** private void saveBatch(List<CcdiBizIntermediary> list, int batchSize) {
* 创建失败记录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;
for (int i = 0; i < list.size(); i += batchSize) { for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size()); int end = Math.min(i + batchSize, list.size());
List<CcdiBizIntermediary> subList = list.subList(i, end); intermediaryMapper.insertBatch(list.subList(i, end));
int count = intermediaryMapper.insertBatch(subList);
totalCount += count;
} }
return totalCount;
} }
/** private void updateImportStatus(String taskId, ImportResult result) {
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:intermediary:" + taskId;
Map<String, Object> statusData = new HashMap<>(); 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("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount()); statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100); statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis()); statusData.put("endTime", System.currentTimeMillis());
statusData.put("message", result.getFailureCount() == 0
if ("SUCCESS".equals(status)) { ? "全部成功!共导入" + result.getTotalCount() + "条数据"
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "数据"); : "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
} else { redisTemplate.opsForHash().putAll(statusKey(taskId), statusData);
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
} }
redisTemplate.opsForHash().putAll(key, statusData); private String statusKey(String taskId) {
return STATUS_KEY_PREFIX + taskId;
} }
/** private String failureKey(String taskId) {
* 验证个人中介数据 return statusKey(taskId) + ":failures";
*
* @param excel Excel数据
* @param existingPersonIds 已存在的证件号集合
*/
private void validatePersonData(CcdiIntermediaryPersonExcel excel,
Set<String> existingPersonIds) {
// 验证必填字段:姓名
if (StringUtils.isEmpty(excel.getName())) {
throw new RuntimeException("姓名不能为空");
}
// 验证必填字段:证件号码
if (StringUtils.isEmpty(excel.getPersonId())) {
throw new RuntimeException("证件号码不能为空");
}
// 验证证件号码格式
String idCardError = IdCardUtil.getErrorMessage(excel.getPersonId());
if (idCardError != null) {
throw new RuntimeException("证件号码" + idCardError);
}
} }
} }

View File

@@ -6,6 +6,7 @@ import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo; import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation; import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.*; 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.CcdiIntermediaryEntityExcel;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel; import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO; import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO;
@@ -17,6 +18,7 @@ import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper; import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper; import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiIntermediaryMapper; 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.ICcdiIntermediaryEntityImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService; import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService;
import com.ruoyi.info.collection.service.ICcdiIntermediaryService; import com.ruoyi.info.collection.service.ICcdiIntermediaryService;
@@ -61,6 +63,9 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
@Resource @Resource
private ICcdiIntermediaryEntityImportService entityImportService; private ICcdiIntermediaryEntityImportService entityImportService;
@Resource
private ICcdiIntermediaryEnterpriseRelationImportService enterpriseRelationImportService;
@Resource @Resource
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@@ -101,8 +106,9 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
@Override @Override
public List<CcdiIntermediaryRelativeVO> selectIntermediaryRelativeList(String bizId) { public List<CcdiIntermediaryRelativeVO> selectIntermediaryRelativeList(String bizId) {
CcdiBizIntermediary owner = requireIntermediaryPerson(bizId);
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBizIntermediary::getRelatedNumId, bizId) wrapper.eq(CcdiBizIntermediary::getRelatedNumId, owner.getPersonId())
.ne(CcdiBizIntermediary::getPersonSubType, "本人") .ne(CcdiBizIntermediary::getPersonSubType, "本人")
.orderByDesc(CcdiBizIntermediary::getCreateTime); .orderByDesc(CcdiBizIntermediary::getCreateTime);
return bizIntermediaryMapper.selectList(wrapper).stream().map(this::buildRelativeVo).toList(); return bizIntermediaryMapper.selectList(wrapper).stream().map(this::buildRelativeVo).toList();
@@ -187,8 +193,9 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
BeanUtils.copyProperties(editDTO, person); BeanUtils.copyProperties(editDTO, person);
person.setPersonSubType("本人"); person.setPersonSubType("本人");
person.setRelatedNumId(null); person.setRelatedNumId(null);
int updated = bizIntermediaryMapper.updateById(person);
return bizIntermediaryMapper.updateById(person); syncRelativeOwnerPersonId(existing.getPersonId(), editDTO.getPersonId());
return updated;
} }
@Override @Override
@@ -196,13 +203,13 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
public int insertIntermediaryRelative(String bizId, CcdiIntermediaryRelativeAddDTO addDTO) { public int insertIntermediaryRelative(String bizId, CcdiIntermediaryRelativeAddDTO addDTO) {
CcdiBizIntermediary owner = requireIntermediaryPerson(bizId); CcdiBizIntermediary owner = requireIntermediaryPerson(bizId);
validateRelativePersonSubType(addDTO.getPersonSubType()); validateRelativePersonSubType(addDTO.getPersonSubType());
if (!checkPersonIdUnique(addDTO.getPersonId(), null)) { if (!checkRelativePersonUnique(owner.getPersonId(), addDTO.getPersonId(), null)) {
throw new RuntimeException("证件号已存在"); throw new RuntimeException("中介本人下已存在相同证件号亲属");
} }
CcdiBizIntermediary relative = new CcdiBizIntermediary(); CcdiBizIntermediary relative = new CcdiBizIntermediary();
BeanUtils.copyProperties(addDTO, relative); BeanUtils.copyProperties(addDTO, relative);
relative.setRelatedNumId(owner.getBizId()); relative.setRelatedNumId(owner.getPersonId());
relative.setDataSource("MANUAL"); relative.setDataSource("MANUAL");
return bizIntermediaryMapper.insert(relative); return bizIntermediaryMapper.insert(relative);
} }
@@ -216,8 +223,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
} }
validateRelativePersonSubType(editDTO.getPersonSubType()); validateRelativePersonSubType(editDTO.getPersonSubType());
if (StringUtils.isNotEmpty(editDTO.getPersonId()) if (StringUtils.isNotEmpty(editDTO.getPersonId())
&& !checkPersonIdUnique(editDTO.getPersonId(), editDTO.getBizId())) { && !checkRelativePersonUnique(existing.getRelatedNumId(), editDTO.getPersonId(), editDTO.getBizId())) {
throw new RuntimeException("证件号已存在"); throw new RuntimeException("中介本人下已存在相同证件号亲属");
} }
CcdiBizIntermediary relative = new CcdiBizIntermediary(); CcdiBizIntermediary relative = new CcdiBizIntermediary();
@@ -334,7 +341,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
if (intermediary != null) { if (intermediary != null) {
if (isIntermediaryPerson(intermediary)) { if (isIntermediaryPerson(intermediary)) {
bizIntermediaryMapper.delete(new LambdaQueryWrapper<CcdiBizIntermediary>() bizIntermediaryMapper.delete(new LambdaQueryWrapper<CcdiBizIntermediary>()
.eq(CcdiBizIntermediary::getRelatedNumId, id)); .eq(CcdiBizIntermediary::getRelatedNumId, intermediary.getPersonId())
.ne(CcdiBizIntermediary::getPersonSubType, "本人"));
enterpriseRelationMapper.delete(new LambdaQueryWrapper<CcdiIntermediaryEnterpriseRelation>() enterpriseRelationMapper.delete(new LambdaQueryWrapper<CcdiIntermediaryEnterpriseRelation>()
.eq(CcdiIntermediaryEnterpriseRelation::getIntermediaryBizId, id)); .eq(CcdiIntermediaryEnterpriseRelation::getIntermediaryBizId, id));
} }
@@ -359,7 +367,8 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
@Override @Override
public boolean checkPersonIdUnique(String personId, String bizId) { public boolean checkPersonIdUnique(String personId, String bizId) {
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBizIntermediary::getPersonId, personId); wrapper.eq(CcdiBizIntermediary::getPersonId, personId)
.eq(CcdiBizIntermediary::getPersonSubType, "本人");
if (StringUtils.isNotEmpty(bizId)) { if (StringUtils.isNotEmpty(bizId)) {
wrapper.ne(CcdiBizIntermediary::getBizId, bizId); wrapper.ne(CcdiBizIntermediary::getBizId, bizId);
} }
@@ -419,6 +428,31 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
return taskId; 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;
}
/** /**
* 导入实体中介数据(异步) * 导入实体中介数据(异步)
* *
@@ -473,6 +507,17 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
} }
} }
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) { private void validateEnterpriseRelation(String bizId, String socialCreditCode, Long excludeId) {
requireIntermediaryPerson(bizId); requireIntermediaryPerson(bizId);
if (enterpriseBaseInfoMapper.selectById(socialCreditCode) == null) { if (enterpriseBaseInfoMapper.selectById(socialCreditCode) == null) {
@@ -490,6 +535,20 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
} }
} }
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) { private CcdiIntermediaryRelativeVO buildRelativeVo(CcdiBizIntermediary relative) {
CcdiIntermediaryRelativeVO vo = new CcdiIntermediaryRelativeVO(); CcdiIntermediaryRelativeVO vo = new CcdiIntermediaryRelativeVO();
BeanUtils.copyProperties(relative, vo); BeanUtils.copyProperties(relative, vo);

View File

@@ -2,12 +2,15 @@ package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransaction; import com.ruoyi.info.collection.domain.CcdiPurchaseTransaction;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel; import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult; import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO; import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper; import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService; import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils; import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
@@ -37,83 +40,155 @@ import java.util.stream.Collectors;
public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTransactionImportService { public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTransactionImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiPurchaseTransactionImportServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(CcdiPurchaseTransactionImportServiceImpl.class);
private static final String MAIN_SHEET_NAME = "招投标主信息";
private static final String SUPPLIER_SHEET_NAME = "供应商明细";
private static final int EXCEL_DATA_START_ROW = 2;
@Resource @Resource
private CcdiPurchaseTransactionMapper transactionMapper; private CcdiPurchaseTransactionMapper transactionMapper;
@Resource
private CcdiPurchaseTransactionSupplierMapper supplierMapper;
@Resource @Resource
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@Override @Override
@Async @Async
@Transactional @Transactional
public void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList, String taskId, String userName) { public void importTransactionAsync(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList,
String taskId,
String userName
) {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
List<CcdiPurchaseTransactionExcel> safeMainList = mainExcelList == null ? List.of() : mainExcelList;
List<CcdiPurchaseTransactionSupplierExcel> safeSupplierList = supplierExcelList == null ? List.of() : supplierExcelList;
List<MainImportRow> indexedMainRows = buildMainImportRows(safeMainList);
List<SupplierImportRow> indexedSupplierRows = buildSupplierImportRows(safeSupplierList);
int totalCount = countImportUnits(safeMainList, safeSupplierList);
// 记录导入开始 // 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "采购交易信息", excelList.size(), userName); ImportLogUtils.logImportStart(log, taskId, "招投标信息维护", totalCount, userName);
List<CcdiPurchaseTransaction> newRecords = new ArrayList<>(); List<CcdiPurchaseTransaction> newTransactions = new ArrayList<>();
List<CcdiPurchaseTransactionSupplier> newSuppliers = new ArrayList<>();
List<PurchaseTransactionImportFailureVO> failures = new ArrayList<>(); List<PurchaseTransactionImportFailureVO> failures = new ArrayList<>();
// 批量查询已存在的采购事项ID // 批量查询已存在的采购事项ID
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", excelList.size()); ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", safeMainList.size());
Set<String> existingIds = getExistingPurchaseIds(excelList); Set<String> existingIds = getExistingPurchaseIds(safeMainList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "采购事项ID", existingIds.size()); ImportLogUtils.logBatchQueryComplete(log, taskId, "采购事项ID", existingIds.size());
// 用于跟踪Excel文件内已处理的采购事项ID Map<String, List<MainImportRow>> mainGroupMap = indexedMainRows.stream()
Set<String> processedIds = new HashSet<>(); .filter(item -> StringUtils.isNotEmpty(item.data().getPurchaseId()))
.collect(Collectors.groupingBy(
item -> item.data().getPurchaseId(),
LinkedHashMap::new,
Collectors.toList()
));
Map<String, List<SupplierImportRow>> supplierGroupMap = indexedSupplierRows.stream()
.filter(item -> StringUtils.isNotEmpty(item.data().getPurchaseId()))
.collect(Collectors.groupingBy(
item -> item.data().getPurchaseId(),
LinkedHashMap::new,
Collectors.toList()
));
LinkedHashSet<String> purchaseIds = new LinkedHashSet<>();
purchaseIds.addAll(mainGroupMap.keySet());
purchaseIds.addAll(supplierGroupMap.keySet());
// 分类数据 for (SupplierImportRow supplierExcel : indexedSupplierRows) {
for (int i = 0; i < excelList.size(); i++) { if (StringUtils.isEmpty(supplierExcel.data().getPurchaseId())) {
CcdiPurchaseTransactionExcel excel = excelList.get(i); failures.add(buildFailure(
null,
try { null,
// 转换为AddDTO进行验证 SUPPLIER_SHEET_NAME,
CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO(); String.valueOf(supplierExcel.sheetRowNum()),
BeanUtils.copyProperties(excel, addDTO); "供应商明细Sheet中的采购事项ID不能为空"
));
// 验证数据 }
validateTransactionData(addDTO, existingIds);
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(excel, transaction);
if (existingIds.contains(excel.getPurchaseId())) {
// 采购事项ID已存在直接报错
throw new RuntimeException(String.format("采购事项ID[%s]已存在,请勿重复导入", excel.getPurchaseId()));
} else if (processedIds.contains(excel.getPurchaseId())) {
// Excel文件内部重复
throw new RuntimeException(String.format("采购事项ID[%s]在导入文件中重复,已跳过此条记录", excel.getPurchaseId()));
} else {
transaction.setCreatedBy(userName);
transaction.setUpdatedBy(userName);
newRecords.add(transaction);
processedIds.add(excel.getPurchaseId()); // 标记为已处理
} }
int index = 0;
for (String purchaseId : purchaseIds) {
index++;
List<MainImportRow> mainRows = mainGroupMap.getOrDefault(purchaseId, List.of());
List<SupplierImportRow> supplierRows = supplierGroupMap.getOrDefault(purchaseId, List.of());
try {
if (existingIds.contains(purchaseId)) {
throw buildValidationException(
MAIN_SHEET_NAME,
extractMainRowNums(mainRows),
String.format("采购事项ID[%s]已存在,请勿重复导入", purchaseId)
);
}
if (mainRows.isEmpty()) {
throw buildValidationException(
SUPPLIER_SHEET_NAME,
extractSupplierRowNums(supplierRows),
String.format("采购事项ID[%s]缺少招投标主信息", purchaseId)
);
}
if (mainRows.size() > 1) {
throw buildValidationException(
MAIN_SHEET_NAME,
extractMainRowNums(mainRows),
String.format("采购事项ID[%s]在招投标主信息Sheet中重复", purchaseId)
);
}
MainImportRow mainRow = mainRows.getFirst();
CcdiPurchaseTransactionExcel mainExcel = mainRow.data();
CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO();
BeanUtils.copyProperties(mainExcel, addDTO);
validateTransactionData(addDTO, mainRow.sheetRowNum());
List<CcdiPurchaseTransactionSupplier> suppliers = buildSupplierEntities(purchaseId, supplierRows, userName);
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(mainExcel, transaction);
fillWinnerSummary(transaction, suppliers);
transaction.setCreatedBy(userName);
transaction.setUpdatedBy(userName);
newTransactions.add(transaction);
newSuppliers.addAll(suppliers);
// 记录进度 // 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(), ImportLogUtils.logProgress(log, taskId, index, Math.max(totalCount, purchaseIds.size()),
newRecords.size(), failures.size()); newTransactions.size(), failures.size());
} catch (Exception e) { } catch (Exception e) {
PurchaseTransactionImportFailureVO failure = new PurchaseTransactionImportFailureVO(); MainImportRow mainRow = mainRows.isEmpty() ? null : mainRows.getFirst();
BeanUtils.copyProperties(excel, failure); CcdiPurchaseTransactionExcel mainExcel = mainRow == null ? null : mainRow.data();
failure.setErrorMessage(e.getMessage()); FailureMeta failureMeta = resolveFailureMeta(e, mainRows, supplierRows);
failures.add(failure); failures.add(buildFailure(
mainExcel,
purchaseId,
failureMeta.sheetName(),
failureMeta.sheetRowNum(),
e.getMessage()
));
// 记录验证失败日志 // 记录验证失败日志
String keyData = String.format("采购事项ID=%s, 采购类别=%s, 标的物=%s", String keyData = String.format("采购事项ID=%s, 采购类别=%s, 标的物=%s",
excel.getPurchaseId(), excel.getPurchaseCategory(), excel.getSubjectName()); purchaseId,
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData); mainExcel == null ? "" : mainExcel.getPurchaseCategory(),
mainExcel == null ? "" : mainExcel.getSubjectName());
ImportLogUtils.logValidationError(log, taskId, index, e.getMessage(), keyData);
} }
} }
// 批量插入新数据 // 批量插入新数据
if (!newRecords.isEmpty()) { if (!newTransactions.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入", ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500); (newTransactions.size() + 499) / 500, 500);
saveBatch(newRecords, 500); saveBatch(newTransactions, 500);
}
if (!newSuppliers.isEmpty()) {
saveSupplierBatch(newSuppliers, 500);
} }
// 保存失败记录到Redis // 保存失败记录到Redis
@@ -128,8 +203,8 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
} }
ImportResult result = new ImportResult(); ImportResult result = new ImportResult();
result.setTotalCount(excelList.size()); result.setTotalCount(totalCount);
result.setSuccessCount(newRecords.size()); result.setSuccessCount(newTransactions.size());
result.setFailureCount(failures.size()); result.setFailureCount(failures.size());
// 更新最终状态 // 更新最终状态
@@ -138,8 +213,8 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
// 记录导入完成 // 记录导入完成
long duration = System.currentTimeMillis() - startTime; long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "采购交易信息", ImportLogUtils.logImportComplete(log, taskId, "招投标信息维护",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); totalCount, result.getSuccessCount(), result.getFailureCount(), duration);
} }
/** /**
@@ -243,71 +318,338 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
} }
} }
private void saveSupplierBatch(List<CcdiPurchaseTransactionSupplier> list, int batchSize) {
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiPurchaseTransactionSupplier> subList = list.subList(i, end);
for (CcdiPurchaseTransactionSupplier supplier : subList) {
supplierMapper.insert(supplier);
}
}
}
/** /**
* 验证采购交易数据 * 验证采购交易数据
* *
* @param addDTO 新增DTO * @param addDTO 新增DTO
* @param existingIds 已存在的采购事项ID集合
*/ */
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, Set<String> existingIds) { private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, int sheetRowNum) {
// 验证必填字段 // 验证必填字段
if (StringUtils.isEmpty(addDTO.getPurchaseId())) { if (StringUtils.isEmpty(addDTO.getPurchaseId())) {
throw new RuntimeException("采购事项ID不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购事项ID不能为空");
} }
if (StringUtils.isEmpty(addDTO.getPurchaseCategory())) { if (StringUtils.isEmpty(addDTO.getPurchaseCategory())) {
throw new RuntimeException("采购类别不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购类别不能为空");
} }
if (StringUtils.isEmpty(addDTO.getSubjectName())) { if (StringUtils.isEmpty(addDTO.getSubjectName())) {
throw new RuntimeException("标的物名称不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "标的物名称不能为空");
} }
if (addDTO.getPurchaseQty() == null) { if (addDTO.getPurchaseQty() == null) {
throw new RuntimeException("采购数量不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购数量不能为空");
} }
if (addDTO.getBudgetAmount() == null) { if (addDTO.getBudgetAmount() == null) {
throw new RuntimeException("预算金额不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "预算金额不能为空");
} }
if (StringUtils.isEmpty(addDTO.getPurchaseMethod())) { if (StringUtils.isEmpty(addDTO.getPurchaseMethod())) {
throw new RuntimeException("采购方式不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购方式不能为空");
} }
if (addDTO.getApplyDate() == null) { if (addDTO.getApplyDate() == null) {
throw new RuntimeException("采购申请日期不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购申请日期不能为空");
} }
if (StringUtils.isEmpty(addDTO.getApplicantId())) { if (StringUtils.isEmpty(addDTO.getApplicantId())) {
throw new RuntimeException("申请人工号不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请人工号不能为空");
} }
if (StringUtils.isEmpty(addDTO.getApplicantName())) { if (StringUtils.isEmpty(addDTO.getApplicantName())) {
throw new RuntimeException("申请人姓名不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请人姓名不能为空");
} }
if (StringUtils.isEmpty(addDTO.getApplyDepartment())) { if (StringUtils.isEmpty(addDTO.getApplyDepartment())) {
throw new RuntimeException("申请部门不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请部门不能为空");
} }
// 验证工号格式7位数字 // 验证工号格式7位数字
if (!addDTO.getApplicantId().matches("^\\d{7}$")) { if (!addDTO.getApplicantId().matches("^\\d{7}$")) {
throw new RuntimeException("申请人工号必须为7位数字"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请人工号必须为7位数字");
} }
if (StringUtils.isNotEmpty(addDTO.getPurchaseLeaderId()) && !addDTO.getPurchaseLeaderId().matches("^\\d{7}$")) { if (StringUtils.isNotEmpty(addDTO.getPurchaseLeaderId()) && !addDTO.getPurchaseLeaderId().matches("^\\d{7}$")) {
throw new RuntimeException("采购负责人工号必须为7位数字"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购负责人工号必须为7位数字");
} }
// 验证金额非负 // 验证金额非负
if (addDTO.getPurchaseQty().compareTo(BigDecimal.ZERO) <= 0) { if (addDTO.getPurchaseQty().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("采购数量必须大于0"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购数量必须大于0");
} }
if (addDTO.getBudgetAmount().compareTo(BigDecimal.ZERO) <= 0) { if (addDTO.getBudgetAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("预算金额必须大于0"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "预算金额必须大于0");
} }
if (addDTO.getBidAmount() != null && addDTO.getBidAmount().compareTo(BigDecimal.ZERO) <= 0) { if (addDTO.getBidAmount() != null && addDTO.getBidAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("中标金额必须大于0"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "中标金额必须大于0");
} }
if (addDTO.getActualAmount() != null && addDTO.getActualAmount().compareTo(BigDecimal.ZERO) <= 0) { if (addDTO.getActualAmount() != null && addDTO.getActualAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("实际采购金额必须大于0"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "实际采购金额必须大于0");
} }
if (addDTO.getContractAmount() != null && addDTO.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) { if (addDTO.getContractAmount() != null && addDTO.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("合同金额必须大于0"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "合同金额必须大于0");
} }
if (addDTO.getSettlementAmount() != null && addDTO.getSettlementAmount().compareTo(BigDecimal.ZERO) <= 0) { if (addDTO.getSettlementAmount() != null && addDTO.getSettlementAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("结算金额必须大于0"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "结算金额必须大于0");
}
}
private List<CcdiPurchaseTransactionSupplier> buildSupplierEntities(
String purchaseId,
List<SupplierImportRow> supplierRows,
String userName
) {
List<SupplierImportRow> normalizedRows = supplierRows == null
? List.of()
: supplierRows.stream()
.filter(Objects::nonNull)
.filter(item -> hasAnySupplierValue(item.data()))
.toList();
List<Integer> winnerRowNums = new ArrayList<>();
Map<String, Integer> supplierKeyRowMap = new LinkedHashMap<>();
List<CcdiPurchaseTransactionSupplier> result = new ArrayList<>();
for (SupplierImportRow supplierImportRow : normalizedRows) {
CcdiPurchaseTransactionSupplierExcel supplierRow = supplierImportRow.data();
int isBidWinner = validateSupplierRow(supplierImportRow);
if (isBidWinner == 1) {
winnerRowNums.add(supplierImportRow.sheetRowNum());
}
String duplicateKey = StringUtils.trimToEmpty(supplierRow.getSupplierName()) + "|"
+ StringUtils.trimToEmpty(supplierRow.getSupplierUscc());
Integer firstRowNum = supplierKeyRowMap.putIfAbsent(duplicateKey, supplierImportRow.sheetRowNum());
if (firstRowNum != null) {
throw buildValidationException(
SUPPLIER_SHEET_NAME,
List.of(firstRowNum, supplierImportRow.sheetRowNum()),
String.format("采购事项ID[%s]存在重复供应商", purchaseId)
);
}
CcdiPurchaseTransactionSupplier supplier = new CcdiPurchaseTransactionSupplier();
supplier.setPurchaseId(purchaseId);
supplier.setSupplierName(StringUtils.trim(supplierRow.getSupplierName()));
supplier.setSupplierUscc(StringUtils.trimToNull(supplierRow.getSupplierUscc()));
supplier.setContactPerson(StringUtils.trimToNull(supplierRow.getContactPerson()));
supplier.setContactPhone(StringUtils.trimToNull(supplierRow.getContactPhone()));
supplier.setSupplierBankAccount(StringUtils.trimToNull(supplierRow.getSupplierBankAccount()));
supplier.setIsBidWinner(isBidWinner);
supplier.setSortOrder(supplierRow.getSortOrder() == null ? result.size() + 1 : supplierRow.getSortOrder());
supplier.setCreatedBy(userName);
supplier.setUpdatedBy(userName);
result.add(supplier);
}
if (winnerRowNums.size() > 1) {
throw buildValidationException(
SUPPLIER_SHEET_NAME,
winnerRowNums,
String.format("采购事项ID[%s]存在多条中标供应商", purchaseId)
);
}
return result;
}
private int validateSupplierRow(SupplierImportRow supplierImportRow) {
CcdiPurchaseTransactionSupplierExcel supplierRow = supplierImportRow.data();
if (StringUtils.isEmpty(supplierRow.getSupplierName())) {
throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商名称不能为空");
}
if (StringUtils.length(supplierRow.getSupplierName()) > 200) {
throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商名称长度不能超过200个字符");
}
if (StringUtils.length(supplierRow.getContactPerson()) > 50) {
throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商联系人长度不能超过50个字符");
}
if (StringUtils.length(supplierRow.getSupplierBankAccount()) > 50) {
throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商银行账户长度不能超过50个字符");
}
if (StringUtils.isNotEmpty(supplierRow.getContactPhone())
&& !supplierRow.getContactPhone().matches("^1[3-9]\\d{9}$|^0\\d{2,3}-?\\d{7,8}$")) {
throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商联系电话格式不正确");
}
if (StringUtils.isNotEmpty(supplierRow.getSupplierUscc())
&& !supplierRow.getSupplierUscc().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
throw buildValidationException(SUPPLIER_SHEET_NAME, List.of(supplierImportRow.sheetRowNum()), "供应商统一信用代码格式不正确");
}
return parseIsBidWinner(supplierRow.getIsBidWinner(), supplierImportRow.sheetRowNum());
}
private boolean hasAnySupplierValue(CcdiPurchaseTransactionSupplierExcel supplierRow) {
return StringUtils.isNotEmpty(supplierRow.getPurchaseId())
|| StringUtils.isNotEmpty(supplierRow.getSupplierName())
|| StringUtils.isNotEmpty(supplierRow.getSupplierUscc())
|| StringUtils.isNotEmpty(supplierRow.getContactPerson())
|| StringUtils.isNotEmpty(supplierRow.getContactPhone())
|| StringUtils.isNotEmpty(supplierRow.getSupplierBankAccount())
|| StringUtils.isNotEmpty(supplierRow.getIsBidWinner())
|| supplierRow.getSortOrder() != null;
}
private int parseIsBidWinner(String rawValue, Integer sheetRowNum) {
if (StringUtils.isEmpty(rawValue)) {
return 0;
}
String normalized = StringUtils.trim(rawValue);
if ("1".equals(normalized) || "".equals(normalized) || "Y".equalsIgnoreCase(normalized)
|| "TRUE".equalsIgnoreCase(normalized)) {
return 1;
}
if ("0".equals(normalized) || "".equals(normalized) || "N".equalsIgnoreCase(normalized)
|| "FALSE".equalsIgnoreCase(normalized)) {
return 0;
}
throw buildValidationException(
SUPPLIER_SHEET_NAME,
sheetRowNum == null ? List.of() : List.of(sheetRowNum),
"是否中标仅支持填写“是/否”或“1/0”"
);
}
private void fillWinnerSummary(
CcdiPurchaseTransaction transaction,
List<CcdiPurchaseTransactionSupplier> supplierList
) {
CcdiPurchaseTransactionSupplier winner = supplierList.stream()
.filter(item -> Objects.equals(item.getIsBidWinner(), 1))
.findFirst()
.orElse(null);
if (winner == null) {
transaction.setSupplierName(null);
transaction.setSupplierUscc(null);
transaction.setContactPerson(null);
transaction.setContactPhone(null);
transaction.setSupplierBankAccount(null);
return;
}
transaction.setSupplierName(winner.getSupplierName());
transaction.setSupplierUscc(winner.getSupplierUscc());
transaction.setContactPerson(winner.getContactPerson());
transaction.setContactPhone(winner.getContactPhone());
transaction.setSupplierBankAccount(winner.getSupplierBankAccount());
}
private PurchaseTransactionImportFailureVO buildFailure(
CcdiPurchaseTransactionExcel mainExcel,
String purchaseId,
String sheetName,
String sheetRowNum,
String errorMessage
) {
PurchaseTransactionImportFailureVO failure = new PurchaseTransactionImportFailureVO();
if (mainExcel != null) {
BeanUtils.copyProperties(mainExcel, failure);
}
failure.setSheetName(sheetName);
failure.setSheetRowNum(sheetRowNum);
if (StringUtils.isNotEmpty(purchaseId)) {
failure.setPurchaseId(purchaseId);
}
failure.setErrorMessage(errorMessage);
return failure;
}
private int countImportUnits(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
) {
LinkedHashSet<String> purchaseIds = new LinkedHashSet<>();
purchaseIds.addAll(
mainExcelList.stream()
.map(CcdiPurchaseTransactionExcel::getPurchaseId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
purchaseIds.addAll(
supplierExcelList.stream()
.map(CcdiPurchaseTransactionSupplierExcel::getPurchaseId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
return purchaseIds.size();
}
private List<MainImportRow> buildMainImportRows(List<CcdiPurchaseTransactionExcel> mainExcelList) {
List<MainImportRow> rows = new ArrayList<>();
for (int i = 0; i < mainExcelList.size(); i++) {
rows.add(new MainImportRow(mainExcelList.get(i), i + EXCEL_DATA_START_ROW));
}
return rows;
}
private List<SupplierImportRow> buildSupplierImportRows(List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList) {
List<SupplierImportRow> rows = new ArrayList<>();
for (int i = 0; i < supplierExcelList.size(); i++) {
rows.add(new SupplierImportRow(supplierExcelList.get(i), i + EXCEL_DATA_START_ROW));
}
return rows;
}
private List<Integer> extractMainRowNums(List<MainImportRow> rows) {
return rows.stream().map(MainImportRow::sheetRowNum).toList();
}
private List<Integer> extractSupplierRowNums(List<SupplierImportRow> rows) {
return rows.stream().map(SupplierImportRow::sheetRowNum).toList();
}
private ImportValidationException buildValidationException(String sheetName, List<Integer> rowNums, String message) {
return new ImportValidationException(sheetName, formatSheetRowNum(rowNums), message);
}
private FailureMeta resolveFailureMeta(
Exception exception,
List<MainImportRow> mainRows,
List<SupplierImportRow> supplierRows
) {
if (exception instanceof ImportValidationException validationException) {
return new FailureMeta(validationException.getSheetName(), validationException.getSheetRowNum());
}
if (!mainRows.isEmpty()) {
return new FailureMeta(MAIN_SHEET_NAME, formatSheetRowNum(extractMainRowNums(mainRows)));
}
if (!supplierRows.isEmpty()) {
return new FailureMeta(SUPPLIER_SHEET_NAME, formatSheetRowNum(extractSupplierRowNums(supplierRows)));
}
return new FailureMeta("", "");
}
private String formatSheetRowNum(List<Integer> rowNums) {
if (rowNums == null || rowNums.isEmpty()) {
return "";
}
return rowNums.stream()
.filter(Objects::nonNull)
.distinct()
.sorted()
.map(String::valueOf)
.collect(Collectors.joining(""));
}
private record MainImportRow(CcdiPurchaseTransactionExcel data, int sheetRowNum) {}
private record SupplierImportRow(CcdiPurchaseTransactionSupplierExcel data, int sheetRowNum) {}
private record FailureMeta(String sheetName, String sheetRowNum) {}
private static class ImportValidationException extends RuntimeException {
private final String sheetName;
private final String sheetRowNum;
private ImportValidationException(String sheetName, String sheetRowNum, String message) {
super(message);
this.sheetName = sheetName;
this.sheetRowNum = sheetRowNum;
}
public String getSheetName() {
return sheetName;
}
public String getSheetRowNum() {
return sheetRowNum;
} }
} }
} }

View File

@@ -2,24 +2,34 @@ package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransaction; import com.ruoyi.info.collection.domain.CcdiPurchaseTransaction;
import com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionQueryDTO;
import com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionSupplierDTO;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel; import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO; import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO;
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionSupplierVO;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper; import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService; import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionService; import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionService;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -39,6 +49,9 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
@Resource @Resource
private ICcdiPurchaseTransactionImportService transactionImportService; private ICcdiPurchaseTransactionImportService transactionImportService;
@Resource
private CcdiPurchaseTransactionSupplierMapper supplierMapper;
@Resource @Resource
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@@ -93,7 +106,14 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
*/ */
@Override @Override
public CcdiPurchaseTransactionVO selectTransactionById(String purchaseId) { public CcdiPurchaseTransactionVO selectTransactionById(String purchaseId) {
return transactionMapper.selectTransactionById(purchaseId); CcdiPurchaseTransactionVO detail = transactionMapper.selectTransactionById(purchaseId);
if (detail == null) {
return null;
}
List<CcdiPurchaseTransactionSupplierVO> supplierList = selectSupplierListByPurchaseId(purchaseId);
detail.setSupplierList(supplierList);
detail.setSupplierCount(supplierList.size());
return detail;
} }
/** /**
@@ -110,9 +130,12 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
throw new RuntimeException("该采购事项ID已存在"); throw new RuntimeException("该采购事项ID已存在");
} }
List<CcdiPurchaseTransactionSupplier> supplierList = buildSupplierEntities(addDTO.getPurchaseId(), addDTO.getSupplierList());
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction(); CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(addDTO, transaction); BeanUtils.copyProperties(addDTO, transaction);
fillWinnerSummary(transaction, supplierList);
int result = transactionMapper.insert(transaction); int result = transactionMapper.insert(transaction);
saveSuppliers(supplierList);
return result; return result;
} }
@@ -126,9 +149,13 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
@Override @Override
@Transactional @Transactional
public int updateTransaction(CcdiPurchaseTransactionEditDTO editDTO) { public int updateTransaction(CcdiPurchaseTransactionEditDTO editDTO) {
List<CcdiPurchaseTransactionSupplier> supplierList = buildSupplierEntities(editDTO.getPurchaseId(), editDTO.getSupplierList());
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction(); CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(editDTO, transaction); BeanUtils.copyProperties(editDTO, transaction);
fillWinnerSummary(transaction, supplierList);
int result = transactionMapper.updateById(transaction); int result = transactionMapper.updateById(transaction);
transactionMapper.deleteSuppliersByPurchaseIds(List.of(editDTO.getPurchaseId()));
saveSuppliers(supplierList);
return result; return result;
} }
@@ -142,19 +169,24 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
@Override @Override
@Transactional @Transactional
public int deleteTransactionByIds(String[] purchaseIds) { public int deleteTransactionByIds(String[] purchaseIds) {
transactionMapper.deleteSuppliersByPurchaseIds(List.of(purchaseIds));
return transactionMapper.deleteBatchIds(java.util.List.of(purchaseIds)); return transactionMapper.deleteBatchIds(java.util.List.of(purchaseIds));
} }
/** /**
* 导入采购交易数据(异步) * 导入采购交易数据(异步)
* *
* @param excelList Excel实体列表 * @param mainExcelList 主信息Excel实体列表
* @param supplierExcelList 供应商明细Excel实体列表
* @return 任务ID * @return 任务ID
*/ */
@Override @Override
@Transactional @Transactional
public String importTransaction(java.util.List<CcdiPurchaseTransactionExcel> excelList) { public String importTransaction(
if (StringUtils.isNull(excelList) || excelList.isEmpty()) { List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
) {
if ((mainExcelList == null || mainExcelList.isEmpty()) && (supplierExcelList == null || supplierExcelList.isEmpty())) {
throw new RuntimeException("至少需要一条数据"); throw new RuntimeException("至少需要一条数据");
} }
@@ -170,7 +202,7 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
Map<String, Object> statusData = new HashMap<>(); Map<String, Object> statusData = new HashMap<>();
statusData.put("taskId", taskId); statusData.put("taskId", taskId);
statusData.put("status", "PROCESSING"); statusData.put("status", "PROCESSING");
statusData.put("totalCount", excelList.size()); statusData.put("totalCount", countImportUnits(mainExcelList, supplierExcelList));
statusData.put("successCount", 0); statusData.put("successCount", 0);
statusData.put("failureCount", 0); statusData.put("failureCount", 0);
statusData.put("progress", 0); statusData.put("progress", 0);
@@ -181,8 +213,134 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS); redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
// 调用异步导入服务 // 调用异步导入服务
transactionImportService.importTransactionAsync(excelList, taskId, userName); transactionImportService.importTransactionAsync(mainExcelList, supplierExcelList, taskId, userName);
return taskId; return taskId;
} }
private int countImportUnits(
List<CcdiPurchaseTransactionExcel> mainExcelList,
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
) {
LinkedHashSet<String> purchaseIds = new LinkedHashSet<>();
if (mainExcelList != null) {
purchaseIds.addAll(
mainExcelList.stream()
.map(CcdiPurchaseTransactionExcel::getPurchaseId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
}
if (supplierExcelList != null) {
purchaseIds.addAll(
supplierExcelList.stream()
.map(CcdiPurchaseTransactionSupplierExcel::getPurchaseId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toCollection(LinkedHashSet::new))
);
}
return purchaseIds.isEmpty() ? 0 : purchaseIds.size();
}
private List<CcdiPurchaseTransactionSupplier> buildSupplierEntities(
String purchaseId,
List<CcdiPurchaseTransactionSupplierDTO> supplierDTOList
) {
List<CcdiPurchaseTransactionSupplierDTO> normalizedList = normalizeSupplierList(supplierDTOList);
validateSupplierList(normalizedList);
List<CcdiPurchaseTransactionSupplier> supplierList = new ArrayList<>();
for (int i = 0; i < normalizedList.size(); i++) {
CcdiPurchaseTransactionSupplierDTO dto = normalizedList.get(i);
CcdiPurchaseTransactionSupplier supplier = new CcdiPurchaseTransactionSupplier();
BeanUtils.copyProperties(dto, supplier);
supplier.setPurchaseId(purchaseId);
supplier.setIsBidWinner(Objects.equals(dto.getIsBidWinner(), 1) ? 1 : 0);
supplier.setSortOrder(dto.getSortOrder() == null ? i + 1 : dto.getSortOrder());
supplierList.add(supplier);
}
return supplierList;
}
private List<CcdiPurchaseTransactionSupplierDTO> normalizeSupplierList(
List<CcdiPurchaseTransactionSupplierDTO> supplierDTOList
) {
if (supplierDTOList == null) {
return List.of();
}
return supplierDTOList.stream()
.filter(Objects::nonNull)
.filter(this::hasAnySupplierValue)
.toList();
}
private boolean hasAnySupplierValue(CcdiPurchaseTransactionSupplierDTO supplierDTO) {
return StringUtils.isNotEmpty(supplierDTO.getSupplierName())
|| StringUtils.isNotEmpty(supplierDTO.getSupplierUscc())
|| StringUtils.isNotEmpty(supplierDTO.getContactPerson())
|| StringUtils.isNotEmpty(supplierDTO.getContactPhone())
|| StringUtils.isNotEmpty(supplierDTO.getSupplierBankAccount())
|| supplierDTO.getIsBidWinner() != null
|| supplierDTO.getSortOrder() != null;
}
private void validateSupplierList(List<CcdiPurchaseTransactionSupplierDTO> supplierList) {
long winnerCount = supplierList.stream()
.filter(item -> Objects.equals(item.getIsBidWinner(), 1))
.count();
if (winnerCount > 1) {
throw new RuntimeException("同一招投标事项仅允许维护一条中标供应商");
}
LinkedHashSet<String> duplicateKeys = new LinkedHashSet<>();
for (CcdiPurchaseTransactionSupplierDTO supplier : supplierList) {
String duplicateKey = StringUtils.trimToEmpty(supplier.getSupplierName()) + "|"
+ StringUtils.trimToEmpty(supplier.getSupplierUscc());
if (!duplicateKeys.add(duplicateKey)) {
throw new RuntimeException("同一招投标事项存在重复供应商,请检查供应商名称和统一信用代码");
}
}
}
private void fillWinnerSummary(
CcdiPurchaseTransaction transaction,
List<CcdiPurchaseTransactionSupplier> supplierList
) {
CcdiPurchaseTransactionSupplier winnerSupplier = supplierList.stream()
.filter(item -> Objects.equals(item.getIsBidWinner(), 1))
.findFirst()
.orElse(null);
if (winnerSupplier == null) {
transaction.setSupplierName(null);
transaction.setSupplierUscc(null);
transaction.setContactPerson(null);
transaction.setContactPhone(null);
transaction.setSupplierBankAccount(null);
return;
}
transaction.setSupplierName(winnerSupplier.getSupplierName());
transaction.setSupplierUscc(winnerSupplier.getSupplierUscc());
transaction.setContactPerson(winnerSupplier.getContactPerson());
transaction.setContactPhone(winnerSupplier.getContactPhone());
transaction.setSupplierBankAccount(winnerSupplier.getSupplierBankAccount());
}
private void saveSuppliers(List<CcdiPurchaseTransactionSupplier> supplierList) {
for (CcdiPurchaseTransactionSupplier supplier : supplierList) {
supplierMapper.insert(supplier);
}
}
private List<CcdiPurchaseTransactionSupplierVO> selectSupplierListByPurchaseId(String purchaseId) {
return supplierMapper.selectList(
new LambdaQueryWrapper<CcdiPurchaseTransactionSupplier>()
.eq(CcdiPurchaseTransactionSupplier::getPurchaseId, purchaseId)
.orderByAsc(CcdiPurchaseTransactionSupplier::getSortOrder)
.orderByAsc(CcdiPurchaseTransactionSupplier::getId)
).stream().map(entity -> {
CcdiPurchaseTransactionSupplierVO vo = new CcdiPurchaseTransactionSupplierVO();
BeanUtils.copyProperties(entity, vo);
return vo;
}).collect(Collectors.toList());
}
} }

View File

@@ -2,14 +2,14 @@ package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation; import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult; import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureVO; import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper; import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper; import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService; import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils; import com.ruoyi.info.collection.utils.ImportLogUtils;
@@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* 员工实体关系信息异步导入服务层处理 * 员工亲属实体关异步导入服务层处理
* *
* @author ruoyi * @author ruoyi
* @date 2026-02-09 * @date 2026-02-09
@@ -47,7 +47,7 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@Resource @Resource
private CcdiBaseStaffMapper baseStaffMapper; private CcdiStaffFmyRelationMapper familyRelationMapper;
@Override @Override
@Async @Async
@@ -56,37 +56,48 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// 记录导入开始 // 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "员工实体关", excelList.size(), userName); ImportLogUtils.logImportStart(log, taskId, "员工亲属实体关", excelList.size(), userName);
List<CcdiStaffEnterpriseRelation> newRecords = new ArrayList<>(); List<CcdiStaffEnterpriseRelation> newRecords = new ArrayList<>();
List<StaffEnterpriseRelationImportFailureVO> failures = new ArrayList<>(); List<StaffEnterpriseRelationImportFailureVO> failures = new ArrayList<>();
// 批量验证员工身份证号是否存在
Set<String> excelPersonIds = excelList.stream() Set<String> excelPersonIds = excelList.stream()
.map(CcdiStaffEnterpriseRelationExcel::getPersonId) .map(CcdiStaffEnterpriseRelationExcel::getPersonId)
.filter(StringUtils::isNotEmpty) .filter(StringUtils::isNotEmpty)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
Set<String> existingPersonIds = new HashSet<>(); Map<String, CcdiStaffFmyRelation> validFamilies = new HashMap<>();
Set<String> knownFamilyCertNos = new HashSet<>();
Map<String, String> familyNameMap = new HashMap<>();
if (!excelPersonIds.isEmpty()) { if (!excelPersonIds.isEmpty()) {
ImportLogUtils.logBatchQueryStart(log, taskId, "员工身份证号", excelPersonIds.size()); ImportLogUtils.logBatchQueryStart(log, taskId, "员工亲属关系", excelPersonIds.size());
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CcdiStaffFmyRelation> wrapper = new LambdaQueryWrapper<>();
wrapper.select(CcdiBaseStaff::getIdCard) wrapper.select(
.in(CcdiBaseStaff::getIdCard, excelPersonIds); CcdiStaffFmyRelation::getRelationCertNo,
CcdiStaffFmyRelation::getRelationName,
CcdiStaffFmyRelation::getPersonId,
CcdiStaffFmyRelation::getStatus,
CcdiStaffFmyRelation::getIsEmpFamily
)
.in(CcdiStaffFmyRelation::getRelationCertNo, excelPersonIds);
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper); List<CcdiStaffFmyRelation> familyRelations = familyRelationMapper.selectList(wrapper);
existingPersonIds = existingStaff.stream() for (CcdiStaffFmyRelation familyRelation : familyRelations) {
.map(CcdiBaseStaff::getIdCard) knownFamilyCertNos.add(familyRelation.getRelationCertNo());
.collect(Collectors.toSet()); familyNameMap.putIfAbsent(familyRelation.getRelationCertNo(), familyRelation.getRelationName());
if (Boolean.TRUE.equals(familyRelation.getIsEmpFamily()) && Integer.valueOf(1).equals(familyRelation.getStatus())) {
validFamilies.putIfAbsent(familyRelation.getRelationCertNo(), familyRelation);
}
}
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工身份证号", existingPersonIds.size()); ImportLogUtils.logBatchQueryComplete(log, taskId, "员工亲属关系", familyRelations.size());
} }
// 批量查询已存在的person_id + social_credit_code组合 // 批量查询已存在的person_id + social_credit_code组合
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的员工企业关系组合", excelList.size()); ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的员工亲属实体关联组合", excelList.size());
Set<String> existingCombinations = getExistingCombinations(excelList); Set<String> existingCombinations = getExistingCombinations(excelList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工企业关系组合", existingCombinations.size()); ImportLogUtils.logBatchQueryComplete(log, taskId, "员工亲属实体关联组合", existingCombinations.size());
// 用于跟踪Excel文件内已处理的组合 // 用于跟踪Excel文件内已处理的组合
Set<String> processedCombinations = new HashSet<>(); Set<String> processedCombinations = new HashSet<>();
@@ -103,41 +114,18 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
// 验证数据 // 验证数据
validateRelationData(addDTO); validateRelationData(addDTO);
// 身份证号存在性检查(在基本验证之后) CcdiStaffFmyRelation familyRelation = validFamilies.get(excel.getPersonId());
if (!existingPersonIds.contains(excel.getPersonId())) { CcdiStaffEnterpriseRelation relation = validateAndBuildEntity(
throw new RuntimeException(String.format( excel,
"第%d行: 身份证号[%s]不存在于员工信息表中,请先添加员工信息", familyRelation,
i + 1, excel.getPersonId())); knownFamilyCertNos,
} existingCombinations,
processedCombinations,
String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode(); userName
);
CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation();
BeanUtils.copyProperties(excel, relation);
if (existingCombinations.contains(combination)) {
// 组合已存在,直接报错
throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合已存在,请勿重复导入",
excel.getPersonId(), excel.getSocialCreditCode()));
} else if (processedCombinations.contains(combination)) {
// Excel文件内部重复
throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合在导入文件中重复,已跳过此条记录",
excel.getPersonId(), excel.getSocialCreditCode()));
} else {
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
// 设置默认值
relation.setStatus(1);
relation.setIsEmployee(0);
relation.setIsEmpFamily(1);
relation.setIsCustomer(0);
relation.setIsCustFamily(0);
relation.setDataSource("IMPORT");
newRecords.add(relation); newRecords.add(relation);
processedCombinations.add(combination); // 标记为已处理 processedCombinations.add(excel.getPersonId() + "|" + excel.getSocialCreditCode());
}
// 记录进度 // 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(), ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
@@ -146,11 +134,12 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
} catch (Exception e) { } catch (Exception e) {
StaffEnterpriseRelationImportFailureVO failure = new StaffEnterpriseRelationImportFailureVO(); StaffEnterpriseRelationImportFailureVO failure = new StaffEnterpriseRelationImportFailureVO();
BeanUtils.copyProperties(excel, failure); BeanUtils.copyProperties(excel, failure);
failure.setRelationName(familyNameMap.get(excel.getPersonId()));
failure.setErrorMessage(e.getMessage()); failure.setErrorMessage(e.getMessage());
failures.add(failure); failures.add(failure);
// 记录验证失败日志 // 记录验证失败日志
String keyData = String.format("身份证号=%s, 统一社会信用代码=%s, 企业名称=%s", String keyData = String.format("亲属身份证号=%s, 统一社会信用代码=%s, 企业名称=%s",
excel.getPersonId(), excel.getSocialCreditCode(), excel.getEnterpriseName()); excel.getPersonId(), excel.getSocialCreditCode(), excel.getEnterpriseName());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData); ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
} }
@@ -185,7 +174,7 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
// 记录导入完成 // 记录导入完成
long duration = System.currentTimeMillis() - startTime; long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "员工实体关", ImportLogUtils.logImportComplete(log, taskId, "员工亲属实体关",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
} }
@@ -251,9 +240,9 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
statusData.put("endTime", System.currentTimeMillis()); statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) { if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据"); statusData.put("message", "员工亲属实体关联导入全部成功!共导入" + result.getTotalCount() + "条数据");
} else { } else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + ""); statusData.put("message", "员工亲属实体关联导入成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
} }
redisTemplate.opsForHash().putAll(key, statusData); redisTemplate.opsForHash().putAll(key, statusData);
@@ -297,14 +286,14 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
} }
/** /**
* 验证员工实体关数据 * 验证员工亲属实体关联基础数据
* *
* @param addDTO 新增DTO * @param addDTO 新增DTO
*/ */
private void validateRelationData(CcdiStaffEnterpriseRelationAddDTO addDTO) { private void validateRelationData(CcdiStaffEnterpriseRelationAddDTO addDTO) {
// 验证必填字段 // 验证必填字段
if (StringUtils.isEmpty(addDTO.getPersonId())) { if (StringUtils.isEmpty(addDTO.getPersonId())) {
throw new RuntimeException("身份证号不能为空"); throw new RuntimeException("亲属身份证号不能为空");
} }
if (StringUtils.isEmpty(addDTO.getSocialCreditCode())) { if (StringUtils.isEmpty(addDTO.getSocialCreditCode())) {
throw new RuntimeException("统一社会信用代码不能为空"); throw new RuntimeException("统一社会信用代码不能为空");
@@ -313,9 +302,9 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
throw new RuntimeException("企业名称不能为空"); throw new RuntimeException("企业名称不能为空");
} }
// 验证身份证号格式18位 // 验证亲属身份证号格式18位
if (!addDTO.getPersonId().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$")) { if (!addDTO.getPersonId().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$")) {
throw new RuntimeException("身份证号格式不正确必须为18位有效身份证号"); throw new RuntimeException("亲属身份证号格式不正确必须为18位有效身份证号");
} }
// 验证统一社会信用代码格式18位 // 验证统一社会信用代码格式18位
@@ -331,4 +320,38 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
throw new RuntimeException("企业名称长度不能超过200个字符"); throw new RuntimeException("企业名称长度不能超过200个字符");
} }
} }
CcdiStaffEnterpriseRelation validateAndBuildEntity(CcdiStaffEnterpriseRelationExcel excel,
CcdiStaffFmyRelation familyRelation,
Set<String> knownFamilyCertNos,
Set<String> existingCombinations,
Set<String> processedCombinations,
String userName) {
String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode();
if (familyRelation == null) {
if (knownFamilyCertNos.contains(excel.getPersonId())) {
throw new RuntimeException("亲属身份证号[" + excel.getPersonId() + "]不是有效员工亲属,请先维护有效的员工亲属关系");
}
throw new RuntimeException("亲属身份证号[" + excel.getPersonId() + "]不存在,请先维护员工亲属关系");
}
if (existingCombinations.contains(combination)) {
throw new RuntimeException("亲属身份证号[" + excel.getPersonId() + "]和统一社会信用代码[" + excel.getSocialCreditCode() + "]的组合已存在,请勿重复导入");
}
if (processedCombinations.contains(combination)) {
throw new RuntimeException("亲属身份证号[" + excel.getPersonId() + "]和统一社会信用代码[" + excel.getSocialCreditCode() + "]的组合在导入文件中重复,已跳过此条记录");
}
CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation();
BeanUtils.copyProperties(excel, relation);
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
relation.setStatus(1);
relation.setIsEmployee(0);
relation.setIsEmpFamily(1);
relation.setIsCustomer(0);
relation.setIsCustFamily(0);
relation.setDataSource("IMPORT");
return relation;
}
} }

View File

@@ -3,11 +3,14 @@ package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation; import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper; import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService; import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationService; import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationService;
@@ -37,6 +40,9 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
@Resource @Resource
private CcdiStaffEnterpriseRelationMapper relationMapper; private CcdiStaffEnterpriseRelationMapper relationMapper;
@Resource
private CcdiStaffFmyRelationMapper familyRelationMapper;
@Resource @Resource
private ICcdiStaffEnterpriseRelationImportService relationImportService; private ICcdiStaffEnterpriseRelationImportService relationImportService;
@@ -86,6 +92,11 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
@Override
public java.util.List<CcdiStaffEnterpriseRelationOptionVO> selectFamilyOptions(String query) {
return relationMapper.selectFamilyOptions(query);
}
/** /**
* 查询员工实体关系详情 * 查询员工实体关系详情
* *
@@ -106,16 +117,15 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
@Override @Override
@Transactional @Transactional
public int insertRelation(CcdiStaffEnterpriseRelationAddDTO addDTO) { public int insertRelation(CcdiStaffEnterpriseRelationAddDTO addDTO) {
// 检查身份证号+统一社会信用代码唯一性 validateEffectiveFamily(addDTO.getPersonId());
if (relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())) { if (relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())) {
throw new RuntimeException("该身份证号和统一社会信用代码组合已存在"); throw new RuntimeException("亲属身份证号和统一社会信用代码组合已存在");
} }
CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation(); CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation();
BeanUtils.copyProperties(addDTO, relation); BeanUtils.copyProperties(addDTO, relation);
// 设置默认值
// 新增时强制设置状态为有效
relation.setStatus(1); relation.setStatus(1);
if (relation.getIsEmployee() == null) { if (relation.getIsEmployee() == null) {
@@ -159,7 +169,7 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
updateWrapper.set(editDTO.getRemark() != null, CcdiStaffEnterpriseRelation::getRemark, editDTO.getRemark()); updateWrapper.set(editDTO.getRemark() != null, CcdiStaffEnterpriseRelation::getRemark, editDTO.getRemark());
// 注意:以下字段不可修改 // 注意:以下字段不可修改
// - personId身份证号业务主键 // - personId亲属身份证号,业务主键)
// - socialCreditCode统一社会信用代码业务主键 // - socialCreditCode统一社会信用代码业务主键
// - dataSource数据来源系统字段 // - dataSource数据来源系统字段
// - isEmployee是否为员工系统字段 // - isEmployee是否为员工系统字段
@@ -224,4 +234,28 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
return taskId; return taskId;
} }
private CcdiStaffFmyRelation validateEffectiveFamily(String familyCertNo) {
CcdiStaffFmyRelation validFamily = familyRelationMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CcdiStaffFmyRelation>()
.eq(CcdiStaffFmyRelation::getRelationCertNo, familyCertNo)
.eq(CcdiStaffFmyRelation::getIsEmpFamily, Boolean.TRUE)
.eq(CcdiStaffFmyRelation::getStatus, 1)
.last("LIMIT 1")
);
if (validFamily != null) {
return validFamily;
}
CcdiStaffFmyRelation existingFamily = familyRelationMapper.selectOne(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CcdiStaffFmyRelation>()
.select(CcdiStaffFmyRelation::getId)
.eq(CcdiStaffFmyRelation::getRelationCertNo, familyCertNo)
.last("LIMIT 1")
);
if (existingFamily == null) {
throw new RuntimeException("亲属身份证号[" + familyCertNo + "]不存在,请先维护员工亲属关系");
}
throw new RuntimeException("亲属身份证号[" + familyCertNo + "]不是有效员工亲属,请先维护有效的员工亲属关系");
}
} }

View File

@@ -41,6 +41,8 @@ import java.util.stream.Collectors;
public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelationImportService { public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelationImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiStaffFmyRelationImportServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(CcdiStaffFmyRelationImportServiceImpl.class);
private static final String SHEET_NAME = "员工亲属关系信息";
private static final int EXCEL_DATA_START_ROW = 2;
@Resource @Resource
private CcdiStaffFmyRelationMapper relationMapper; private CcdiStaffFmyRelationMapper relationMapper;
@@ -168,6 +170,8 @@ public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelat
} catch (Exception e) { } catch (Exception e) {
StaffFmyRelationImportFailureVO failure = new StaffFmyRelationImportFailureVO(); StaffFmyRelationImportFailureVO failure = new StaffFmyRelationImportFailureVO();
BeanUtils.copyProperties(excel, failure); BeanUtils.copyProperties(excel, failure);
failure.setSheetName(SHEET_NAME);
failure.setRowNum(i + EXCEL_DATA_START_ROW);
failure.setErrorMessage(e.getMessage()); failure.setErrorMessage(e.getMessage());
failures.add(failure); failures.add(failure);

View File

@@ -3,12 +3,13 @@ package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiAssetInfo; import com.ruoyi.info.collection.domain.CcdiAssetInfo;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation; import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper; import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.service.ICcdiAssetInfoService; import com.ruoyi.info.collection.service.ICcdiAssetInfoService;
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService; import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
@@ -50,6 +51,9 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer
@Resource @Resource
private ICcdiAssetInfoService assetInfoService; private ICcdiAssetInfoService assetInfoService;
@Resource
private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper;
/** /**
* 查询员工亲属关系列表 * 查询员工亲属关系列表
* *
@@ -161,6 +165,9 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer
CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation(); CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation();
BeanUtils.copyProperties(editDTO, relation); BeanUtils.copyProperties(editDTO, relation);
int result = relationMapper.updateById(relation); int result = relationMapper.updateById(relation);
if (Integer.valueOf(1).equals(existing.getStatus()) && Integer.valueOf(0).equals(editDTO.getStatus())) {
staffEnterpriseRelationMapper.invalidateByFamilyCertNo(existing.getRelationCertNo());
}
assetInfoService.replaceByFamilyIdAndPersonId(editDTO.getPersonId(), editDTO.getRelationCertNo(), editDTO.getAssetInfoList()); assetInfoService.replaceByFamilyIdAndPersonId(editDTO.getPersonId(), editDTO.getRelationCertNo(), editDTO.getAssetInfoList());
return result; return result;
} }

View File

@@ -2,19 +2,35 @@ package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.utils.IdCardUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment; 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.CcdiStaffRecruitmentAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; 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.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO; import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO;
import com.ruoyi.info.collection.enums.AdmitStatus; 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.CcdiStaffRecruitmentMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper;
import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService; import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils; import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.IdCardUtil;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
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;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
@@ -24,10 +40,6 @@ import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** /**
* 招聘信息异步导入Service实现 * 招聘信息异步导入Service实现
* *
@@ -40,110 +52,72 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
private static final Logger log = LoggerFactory.getLogger(CcdiStaffRecruitmentImportServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(CcdiStaffRecruitmentImportServiceImpl.class);
private static final String MAIN_SHEET_NAME = "招聘信息";
private static final String WORK_SHEET_NAME = "历史工作经历";
private static final int EXCEL_DATA_START_ROW = 2;
@Resource @Resource
private CcdiStaffRecruitmentMapper recruitmentMapper; private CcdiStaffRecruitmentMapper recruitmentMapper;
@Resource
private CcdiStaffRecruitmentWorkMapper recruitmentWorkMapper;
@Resource @Resource
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@Override @Override
@Async @Async
@Transactional @Transactional
public void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList, public void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> recruitmentList,
List<CcdiStaffRecruitmentWorkExcel> workList,
String taskId, String taskId,
String userName) { String userName) {
List<CcdiStaffRecruitmentExcel> safeRecruitmentList = recruitmentList == null
? Collections.emptyList()
: recruitmentList;
List<CcdiStaffRecruitmentWorkExcel> safeWorkList = workList == null
? Collections.emptyList()
: workList;
int totalCount = safeRecruitmentList.size() + safeWorkList.size();
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// 记录导入开始 ImportLogUtils.logImportStart(log, taskId, "招聘信息双Sheet", totalCount, userName);
ImportLogUtils.logImportStart(log, taskId, "招聘信息", excelList.size(), userName);
List<CcdiStaffRecruitment> newRecords = new ArrayList<>();
List<RecruitmentImportFailureVO> failures = new ArrayList<>(); List<RecruitmentImportFailureVO> failures = new ArrayList<>();
List<MainImportRow> indexedMainRows = buildMainImportRows(safeRecruitmentList);
List<WorkImportRow> indexedWorkRows = buildWorkImportRows(safeWorkList);
// 批量查询已存在的招聘项目编号 MainImportResult mainImportResult = importMainSheet(indexedMainRows, failures, userName, taskId);
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的招聘项目编号", excelList.size()); int workSuccessCount = importWorkSheet(
Set<String> existingRecruitIds = getExistingRecruitIds(excelList); indexedWorkRows,
ImportLogUtils.logBatchQueryComplete(log, taskId, "招聘项目编号", existingRecruitIds.size()); mainImportResult.importedRecruitmentMap(),
failures,
userName,
taskId
);
// 用于检测Excel内部的重复ID
Set<String> excelProcessedIds = new HashSet<>();
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiStaffRecruitmentExcel excel = excelList.get(i);
try {
// 转换为AddDTO进行验证
CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据
validateRecruitmentData(addDTO, existingRecruitIds);
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
BeanUtils.copyProperties(excel, recruitment);
if (existingRecruitIds.contains(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()));
} else {
recruitment.setCreatedBy(userName);
recruitment.setUpdatedBy(userName);
newRecords.add(recruitment);
excelProcessedIds.add(excel.getRecruitId()); // 标记为已处理
}
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
} catch (Exception e) {
RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("招聘项目编号=%s, 项目名称=%s, 应聘人员=%s",
excel.getRecruitId(), excel.getRecruitName(), excel.getCandName());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) { if (!failures.isEmpty()) {
try { saveFailures(taskId, failures);
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(); ImportResult result = new ImportResult();
result.setTotalCount(excelList.size()); result.setTotalCount(totalCount);
result.setSuccessCount(newRecords.size()); result.setSuccessCount(mainImportResult.successCount() + workSuccessCount);
result.setFailureCount(failures.size()); result.setFailureCount(Math.max(totalCount - result.getSuccessCount(), 0));
// 更新最终状态 String finalStatus = resolveFinalStatus(result);
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus(taskId, finalStatus, result); updateImportStatus(taskId, finalStatus, result);
// 记录导入完成
long duration = System.currentTimeMillis() - startTime; long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "招聘信息", ImportLogUtils.logImportComplete(
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); log,
taskId,
"招聘信息双Sheet",
totalCount,
result.getSuccessCount(),
result.getFailureCount(),
duration
);
} }
@Override @Override
@@ -183,14 +157,188 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
return JSON.parseArray(JSON.toJSONString(failuresObj), RecruitmentImportFailureVO.class); return JSON.parseArray(JSON.toJSONString(failuresObj), RecruitmentImportFailureVO.class);
} }
/** private MainImportResult importMainSheet(List<MainImportRow> mainRows,
* 批量查询已存在的招聘项目编号 List<RecruitmentImportFailureVO> failures,
*/ String userName,
private Set<String> getExistingRecruitIds(List<CcdiStaffRecruitmentExcel> excelList) { String taskId) {
List<String> recruitIds = excelList.stream() if (mainRows.isEmpty()) {
.map(CcdiStaffRecruitmentExcel::getRecruitId) return new MainImportResult(Collections.emptyMap(), 0);
}
Set<String> existingRecruitIds = getExistingRecruitIds(
mainRows.stream().map(MainImportRow::data).toList()
);
Set<String> processedRecruitIds = new HashSet<>();
List<CcdiStaffRecruitment> newRecords = new ArrayList<>();
Map<String, CcdiStaffRecruitment> importedRecruitmentMap = new LinkedHashMap<>();
for (int index = 0; index < mainRows.size(); index++) {
MainImportRow mainRow = mainRows.get(index);
CcdiStaffRecruitmentExcel excel = mainRow.data();
try {
CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO();
BeanUtils.copyProperties(excel, addDTO);
addDTO.setRecruitType(RecruitType.inferCode(addDTO.getRecruitName()));
validateRecruitmentData(addDTO, mainRow.sheetRowNum());
String recruitId = trim(excel.getRecruitId());
if (existingRecruitIds.contains(recruitId)) {
throw buildValidationException(
MAIN_SHEET_NAME,
List.of(mainRow.sheetRowNum()),
String.format("招聘记录编号[%s]已存在,请勿重复导入", recruitId)
);
}
if (!processedRecruitIds.add(recruitId)) {
throw buildValidationException(
MAIN_SHEET_NAME,
List.of(mainRow.sheetRowNum()),
String.format("招聘记录编号[%s]在导入文件中重复,已跳过此条记录", recruitId)
);
}
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
BeanUtils.copyProperties(excel, recruitment);
recruitment.setRecruitId(recruitId);
recruitment.setRecruitType(addDTO.getRecruitType());
recruitment.setCreatedBy(userName);
recruitment.setUpdatedBy(userName);
newRecords.add(recruitment);
importedRecruitmentMap.put(recruitId, recruitment);
ImportLogUtils.logProgress(log, taskId, index + 1, mainRows.size(), newRecords.size(), failures.size());
} catch (Exception exception) {
FailureMeta failureMeta = resolveFailureMeta(exception, List.of(mainRow.sheetRowNum()), MAIN_SHEET_NAME);
failures.add(buildFailure(excel, failureMeta.sheetName(), failureMeta.sheetRowNum(), exception.getMessage()));
ImportLogUtils.logValidationError(
log,
taskId,
index + 1,
exception.getMessage(),
String.format("招聘记录编号=%s, 项目名称=%s, 应聘人员=%s", excel.getRecruitId(), excel.getRecruitName(), excel.getCandName())
);
}
}
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入招聘信息", (newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
return new MainImportResult(importedRecruitmentMap, newRecords.size());
}
private int importWorkSheet(List<WorkImportRow> workRows,
Map<String, CcdiStaffRecruitment> importedRecruitmentMap,
List<RecruitmentImportFailureVO> failures,
String userName,
String taskId) {
if (workRows.isEmpty()) {
return 0;
}
Map<String, CcdiStaffRecruitment> existingRecruitmentMap =
getExistingRecruitmentMap(workRows, importedRecruitmentMap);
Map<String, List<WorkImportRow>> groupedRows = groupWorkRows(workRows);
int successCount = 0;
int processedGroups = 0;
for (List<WorkImportRow> recruitWorkRows : groupedRows.values()) {
processedGroups++;
WorkImportRow firstRow = recruitWorkRows.get(0);
String recruitId = trim(firstRow.data().getRecruitId());
CcdiStaffRecruitment recruitment = importedRecruitmentMap.get(recruitId);
if (recruitment == null) {
recruitment = existingRecruitmentMap.get(recruitId);
}
try {
validateWorkGroup(recruitWorkRows, recruitment);
if (StringUtils.isNotEmpty(recruitId) && hasExistingWorkHistory(recruitId)) {
throw buildValidationException(
WORK_SHEET_NAME,
extractWorkRowNums(recruitWorkRows),
String.format("招聘记录编号[%s]已存在历史工作经历,不允许重复导入", recruitId)
);
}
List<CcdiStaffRecruitmentWork> entities = buildWorkEntities(recruitWorkRows, userName);
entities.forEach(entity -> recruitmentWorkMapper.insert(entity));
successCount += recruitWorkRows.size();
ImportLogUtils.logProgress(log, taskId, processedGroups, groupedRows.size(), successCount, failures.size());
} catch (Exception exception) {
FailureMeta failureMeta = resolveFailureMeta(exception, extractWorkRowNums(recruitWorkRows), WORK_SHEET_NAME);
failures.add(buildFailure(firstRow.data(), failureMeta.sheetName(), failureMeta.sheetRowNum(), exception.getMessage()));
ImportLogUtils.logValidationError(
log,
taskId,
processedGroups,
exception.getMessage(),
String.format(
"招聘记录编号=%s, 候选人=%s, 工作单位=%s",
firstRow.data().getRecruitId(),
firstRow.data().getCandName(),
firstRow.data().getCompanyName()
)
);
}
}
return successCount;
}
private Map<String, List<WorkImportRow>> groupWorkRows(List<WorkImportRow> workRows) {
Map<String, List<WorkImportRow>> groupedRows = new LinkedHashMap<>();
for (WorkImportRow workRow : workRows) {
groupedRows.computeIfAbsent(buildWorkGroupKey(workRow), key -> new ArrayList<>()).add(workRow);
}
return groupedRows;
}
private String buildWorkGroupKey(WorkImportRow workRow) {
String recruitId = trim(workRow.data().getRecruitId());
if (StringUtils.isNotEmpty(recruitId)) {
return recruitId;
}
return "__ROW__" + workRow.sheetRowNum();
}
private Map<String, CcdiStaffRecruitment> getExistingRecruitmentMap(List<WorkImportRow> workRows,
Map<String, CcdiStaffRecruitment> importedRecruitmentMap) {
LinkedHashSet<String> recruitIds = workRows.stream()
.map(row -> trim(row.data().getRecruitId()))
.filter(StringUtils::isNotEmpty) .filter(StringUtils::isNotEmpty)
.collect(Collectors.toList()); .filter(recruitId -> !importedRecruitmentMap.containsKey(recruitId))
.collect(Collectors.toCollection(LinkedHashSet::new));
if (recruitIds.isEmpty()) {
return Collections.emptyMap();
}
List<CcdiStaffRecruitment> recruitments = recruitmentMapper.selectBatchIds(recruitIds);
return recruitments.stream().collect(Collectors.toMap(CcdiStaffRecruitment::getRecruitId, item -> item));
}
private List<CcdiStaffRecruitmentWork> buildWorkEntities(List<WorkImportRow> workRows, String userName) {
List<CcdiStaffRecruitmentWork> entities = new ArrayList<>();
for (WorkImportRow workRow : workRows) {
CcdiStaffRecruitmentWork entity = new CcdiStaffRecruitmentWork();
BeanUtils.copyProperties(workRow.data(), entity);
entity.setRecruitId(trim(workRow.data().getRecruitId()));
entity.setCreatedBy(userName);
entity.setUpdatedBy(userName);
entities.add(entity);
}
return entities;
}
private Set<String> getExistingRecruitIds(List<CcdiStaffRecruitmentExcel> recruitmentList) {
List<String> recruitIds = recruitmentList.stream()
.map(CcdiStaffRecruitmentExcel::getRecruitId)
.map(this::trim)
.filter(StringUtils::isNotEmpty)
.toList();
if (recruitIds.isEmpty()) { if (recruitIds.isEmpty()) {
return Collections.emptySet(); return Collections.emptySet();
@@ -205,69 +353,189 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
/** private boolean hasExistingWorkHistory(String recruitId) {
* 验证招聘信息数据 LambdaQueryWrapper<CcdiStaffRecruitmentWork> wrapper = new LambdaQueryWrapper<>();
*/ wrapper.eq(CcdiStaffRecruitmentWork::getRecruitId, recruitId);
private void validateRecruitmentData(CcdiStaffRecruitmentAddDTO addDTO, return recruitmentWorkMapper.selectCount(wrapper) > 0;
Set<String> existingRecruitIds) { }
// 验证必填字段
private void validateRecruitmentData(CcdiStaffRecruitmentAddDTO addDTO, int sheetRowNum) {
if (StringUtils.isEmpty(addDTO.getRecruitId())) { if (StringUtils.isEmpty(addDTO.getRecruitId())) {
throw new RuntimeException("招聘项目编号不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不能为空");
} }
if (StringUtils.isEmpty(addDTO.getRecruitName())) { if (StringUtils.isEmpty(addDTO.getRecruitName())) {
throw new RuntimeException("招聘项目名称不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘项目名称不能为空");
} }
if (StringUtils.isEmpty(addDTO.getPosName())) { if (StringUtils.isEmpty(addDTO.getPosName())) {
throw new RuntimeException("职位名称不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位名称不能为空");
} }
if (StringUtils.isEmpty(addDTO.getPosCategory())) { if (StringUtils.isEmpty(addDTO.getPosCategory())) {
throw new RuntimeException("职位类别不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位类别不能为空");
} }
if (StringUtils.isEmpty(addDTO.getPosDesc())) { if (StringUtils.isEmpty(addDTO.getPosDesc())) {
throw new RuntimeException("职位描述不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位描述不能为空");
} }
if (StringUtils.isEmpty(addDTO.getCandName())) { if (StringUtils.isEmpty(addDTO.getCandName())) {
throw new RuntimeException("应聘人员姓名不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员姓名不能为空");
} }
if (StringUtils.isEmpty(addDTO.getCandEdu())) { if (StringUtils.isEmpty(addDTO.getCandEdu())) {
throw new RuntimeException("应聘人员学历不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员学历不能为空");
} }
if (StringUtils.isEmpty(addDTO.getCandId())) { if (StringUtils.isEmpty(addDTO.getCandId())) {
throw new RuntimeException("证件号码不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "证件号码不能为空");
} }
if (StringUtils.isEmpty(addDTO.getCandSchool())) { if (StringUtils.isEmpty(addDTO.getCandSchool())) {
throw new RuntimeException("应聘人员毕业院校不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员毕业院校不能为空");
} }
if (StringUtils.isEmpty(addDTO.getCandMajor())) { if (StringUtils.isEmpty(addDTO.getCandMajor())) {
throw new RuntimeException("应聘人员专业不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员专业不能为空");
} }
if (StringUtils.isEmpty(addDTO.getCandGrad())) { if (StringUtils.isEmpty(addDTO.getCandGrad())) {
throw new RuntimeException("应聘人员毕业年月不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员毕业年月不能为空");
} }
if (StringUtils.isEmpty(addDTO.getAdmitStatus())) { if (StringUtils.isEmpty(addDTO.getAdmitStatus())) {
throw new RuntimeException("录用情况不能为空"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "录用情况不能为空");
}
if (StringUtils.isEmpty(addDTO.getRecruitType())) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘类型不能为空");
} }
// 验证证件号码格式
String idCardError = IdCardUtil.getErrorMessage(addDTO.getCandId()); String idCardError = IdCardUtil.getErrorMessage(addDTO.getCandId());
if (idCardError != null) { if (idCardError != null) {
throw new RuntimeException("证件号码" + idCardError); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "证件号码" + idCardError);
} }
// 验证毕业年月格式(YYYYMM)
if (!addDTO.getCandGrad().matches("^((19|20)\\d{2})(0[1-9]|1[0-2])$")) { if (!addDTO.getCandGrad().matches("^((19|20)\\d{2})(0[1-9]|1[0-2])$")) {
throw new RuntimeException("毕业年月格式不正确,应为YYYYMM"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "毕业年月格式不正确,应为YYYYMM");
} }
// 验证录用状态
if (AdmitStatus.getDescByCode(addDTO.getAdmitStatus()) == null) { if (AdmitStatus.getDescByCode(addDTO.getAdmitStatus()) == null) {
throw new RuntimeException("录用情况只能填写'录用'、'未录用'或'放弃'"); throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "录用情况只能填写'录用'、'未录用'或'放弃'");
}
if (RecruitType.getDescByCode(addDTO.getRecruitType()) == null) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘类型只能填写'SOCIAL'或'CAMPUS'");
} }
} }
/** private void validateWorkGroup(List<WorkImportRow> workRows, CcdiStaffRecruitment recruitment) {
* 更新导入状态 Set<Integer> processedSortOrders = new HashSet<>();
*/ for (WorkImportRow workRow : workRows) {
validateRecruitmentWorkData(workRow.data(), recruitment, processedSortOrders, workRow.sheetRowNum());
}
}
private void validateRecruitmentWorkData(CcdiStaffRecruitmentWorkExcel excel,
CcdiStaffRecruitment recruitment,
Set<Integer> processedSortOrders,
int sheetRowNum) {
if (StringUtils.isEmpty(trim(excel.getRecruitId()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不能为空");
}
if (StringUtils.isEmpty(trim(excel.getCandName()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "候选人姓名不能为空");
}
if (StringUtils.isEmpty(trim(excel.getRecruitName()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘项目名称不能为空");
}
if (StringUtils.isEmpty(trim(excel.getPosName()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "职位名称不能为空");
}
if (excel.getSortOrder() == null || excel.getSortOrder() <= 0) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "排序号不能为空且必须大于0");
}
if (!processedSortOrders.add(excel.getSortOrder())) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "同一招聘记录编号下排序号重复");
}
if (StringUtils.isEmpty(trim(excel.getCompanyName()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "工作单位不能为空");
}
if (StringUtils.isEmpty(trim(excel.getPositionName()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "岗位不能为空");
}
if (StringUtils.isEmpty(trim(excel.getJobStartMonth()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "入职年月不能为空");
}
validateMonth(excel.getJobStartMonth(), "入职年月", sheetRowNum);
if (StringUtils.isNotEmpty(trim(excel.getJobEndMonth()))) {
validateMonth(excel.getJobEndMonth(), "离职年月", sheetRowNum);
}
if (recruitment == null) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不存在,请先维护招聘主信息");
}
if (!"SOCIAL".equals(recruitment.getRecruitType())) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "该招聘记录不是社招,不允许导入历史工作经历");
}
if (!sameText(excel.getCandName(), recruitment.getCandName())) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号与候选人姓名不匹配");
}
if (!sameText(excel.getRecruitName(), recruitment.getRecruitName())) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号与招聘项目名称不匹配");
}
if (!sameText(excel.getPosName(), recruitment.getPosName())) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号与职位名称不匹配");
}
}
private void validateMonth(String value, String fieldName, int sheetRowNum) {
String month = trim(value);
if (!month.matches("^((19|20)\\d{2})-(0[1-9]|1[0-2])$")) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), 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 void saveFailures(String taskId, List<RecruitmentImportFailureVO> failures) {
try {
String failuresKey = "import:recruitment:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception exception) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", exception);
}
}
private RecruitmentImportFailureVO buildFailure(CcdiStaffRecruitmentExcel excel,
String sheetName,
String sheetRowNum,
String errorMessage) {
RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setSheetName(sheetName);
failure.setSheetRowNum(sheetRowNum);
failure.setErrorMessage(errorMessage);
return failure;
}
private RecruitmentImportFailureVO buildFailure(CcdiStaffRecruitmentWorkExcel excel,
String sheetName,
String sheetRowNum,
String errorMessage) {
RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setSheetName(sheetName);
failure.setSheetRowNum(sheetRowNum);
failure.setErrorMessage(errorMessage);
return failure;
}
private String resolveFinalStatus(ImportResult result) {
if (result.getFailureCount() == 0) {
return "SUCCESS";
}
if (result.getSuccessCount() == 0) {
return "FAILED";
}
return "PARTIAL_SUCCESS";
}
private void updateImportStatus(String taskId, String status, ImportResult result) { private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:recruitment:" + taskId; String key = "import:recruitment:" + taskId;
Map<String, Object> statusData = new HashMap<>(); Map<String, Object> statusData = new HashMap<>();
@@ -287,35 +555,100 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
redisTemplate.opsForHash().putAll(key, statusData); redisTemplate.opsForHash().putAll(key, statusData);
} }
/**
* 批量保存
*/
private void saveBatch(List<CcdiStaffRecruitment> list, int batchSize) { private void saveBatch(List<CcdiStaffRecruitment> list, int batchSize) {
// 使用真正的批量插入,分批次执行以提高性能
for (int i = 0; i < list.size(); i += batchSize) { for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size()); int end = Math.min(i + batchSize, list.size());
List<CcdiStaffRecruitment> subList = list.subList(i, end); List<CcdiStaffRecruitment> subList = list.subList(i, end);
// 过滤掉已存在的记录,防止主键冲突
List<String> recruitIds = subList.stream() List<String> recruitIds = subList.stream()
.map(CcdiStaffRecruitment::getRecruitId) .map(CcdiStaffRecruitment::getRecruitId)
.collect(Collectors.toList()); .toList();
if (recruitIds.isEmpty()) {
continue;
}
if (!recruitIds.isEmpty()) {
List<CcdiStaffRecruitment> existingRecords = recruitmentMapper.selectBatchIds(recruitIds); List<CcdiStaffRecruitment> existingRecords = recruitmentMapper.selectBatchIds(recruitIds);
Set<String> existingIds = existingRecords.stream() Set<String> existingIds = existingRecords.stream()
.map(CcdiStaffRecruitment::getRecruitId) .map(CcdiStaffRecruitment::getRecruitId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 只插入不存在的记录
List<CcdiStaffRecruitment> toInsert = subList.stream() List<CcdiStaffRecruitment> toInsert = subList.stream()
.filter(r -> !existingIds.contains(r.getRecruitId())) .filter(record -> !existingIds.contains(record.getRecruitId()))
.collect(Collectors.toList()); .toList();
if (!toInsert.isEmpty()) { if (!toInsert.isEmpty()) {
recruitmentMapper.insertBatch(toInsert); recruitmentMapper.insertBatch(toInsert);
} }
} }
} }
private List<MainImportRow> buildMainImportRows(List<CcdiStaffRecruitmentExcel> recruitmentList) {
List<MainImportRow> rows = new ArrayList<>();
for (int i = 0; i < recruitmentList.size(); i++) {
rows.add(new MainImportRow(recruitmentList.get(i), i + EXCEL_DATA_START_ROW));
}
return rows;
}
private List<WorkImportRow> buildWorkImportRows(List<CcdiStaffRecruitmentWorkExcel> workList) {
List<WorkImportRow> rows = new ArrayList<>();
for (int i = 0; i < workList.size(); i++) {
rows.add(new WorkImportRow(workList.get(i), i + EXCEL_DATA_START_ROW));
}
return rows;
}
private List<Integer> extractWorkRowNums(List<WorkImportRow> rows) {
return rows.stream().map(WorkImportRow::sheetRowNum).toList();
}
private FailureMeta resolveFailureMeta(Exception exception, List<Integer> rowNums, String defaultSheetName) {
if (exception instanceof ImportValidationException validationException) {
return new FailureMeta(validationException.getSheetName(), validationException.getSheetRowNum());
}
return new FailureMeta(defaultSheetName, formatSheetRowNum(rowNums));
}
private ImportValidationException buildValidationException(String sheetName, List<Integer> rowNums, String message) {
return new ImportValidationException(sheetName, formatSheetRowNum(rowNums), message);
}
private String formatSheetRowNum(List<Integer> rowNums) {
if (rowNums == null || rowNums.isEmpty()) {
return "";
}
return rowNums.stream()
.filter(Objects::nonNull)
.distinct()
.sorted()
.map(String::valueOf)
.collect(Collectors.joining(""));
}
private record MainImportRow(CcdiStaffRecruitmentExcel data, int sheetRowNum) {}
private record WorkImportRow(CcdiStaffRecruitmentWorkExcel data, int sheetRowNum) {}
private record MainImportResult(Map<String, CcdiStaffRecruitment> importedRecruitmentMap, int successCount) {}
private record FailureMeta(String sheetName, String sheetRowNum) {}
private static class ImportValidationException extends RuntimeException {
private final String sheetName;
private final String sheetRowNum;
private ImportValidationException(String sheetName, String sheetRowNum, String message) {
super(message);
this.sheetName = sheetName;
this.sheetRowNum = sheetRowNum;
}
public String getSheetName() {
return sheetName;
}
public String getSheetRowNum() {
return sheetRowNum;
}
} }
} }

View File

@@ -1,13 +1,20 @@
package com.ruoyi.info.collection.service.impl; 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.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment; 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.CcdiStaffRecruitmentAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentWorkEditDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; 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.CcdiStaffRecruitmentVO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentWorkVO;
import com.ruoyi.info.collection.enums.AdmitStatus; import com.ruoyi.info.collection.enums.AdmitStatus;
import com.ruoyi.info.collection.enums.RecruitType;
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper; import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper;
import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService; import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService;
import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentService; import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentService;
@@ -19,9 +26,11 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -37,6 +46,9 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
@Resource @Resource
private CcdiStaffRecruitmentMapper recruitmentMapper; private CcdiStaffRecruitmentMapper recruitmentMapper;
@Resource
private CcdiStaffRecruitmentWorkMapper recruitmentWorkMapper;
@Resource @Resource
private ICcdiStaffRecruitmentImportService recruitmentImportService; private ICcdiStaffRecruitmentImportService recruitmentImportService;
@@ -96,7 +108,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
/** /**
* 查询招聘信息详情 * 查询招聘信息详情
* *
* @param recruitId 招聘项目编号 * @param recruitId 招聘记录编号
* @return 招聘信息VO * @return 招聘信息VO
*/ */
@Override @Override
@@ -104,6 +116,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
CcdiStaffRecruitmentVO vo = recruitmentMapper.selectRecruitmentById(recruitId); CcdiStaffRecruitmentVO vo = recruitmentMapper.selectRecruitmentById(recruitId);
if (vo != null) { if (vo != null) {
vo.setAdmitStatusDesc(AdmitStatus.getDescByCode(vo.getAdmitStatus())); vo.setAdmitStatusDesc(AdmitStatus.getDescByCode(vo.getAdmitStatus()));
vo.setWorkExperienceList(selectWorkExperienceList(recruitId));
} }
return vo; return vo;
} }
@@ -117,9 +130,9 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
@Override @Override
@Transactional @Transactional
public int insertRecruitment(CcdiStaffRecruitmentAddDTO addDTO) { public int insertRecruitment(CcdiStaffRecruitmentAddDTO addDTO) {
// 检查招聘项目编号唯一性 // 检查招聘记录编号唯一性
if (recruitmentMapper.selectById(addDTO.getRecruitId()) != null) { if (recruitmentMapper.selectById(addDTO.getRecruitId()) != null) {
throw new RuntimeException("该招聘项目编号已存在"); throw new RuntimeException("该招聘记录编号已存在");
} }
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment(); CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
@@ -141,6 +154,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment(); CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
BeanUtils.copyProperties(editDTO, recruitment); BeanUtils.copyProperties(editDTO, recruitment);
int result = recruitmentMapper.updateById(recruitment); int result = recruitmentMapper.updateById(recruitment);
replaceWorkExperienceList(editDTO);
return result; return result;
} }
@@ -148,12 +162,15 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
/** /**
* 批量删除招聘信息 * 批量删除招聘信息
* *
* @param recruitIds 需要删除的招聘项目编号 * @param recruitIds 需要删除的招聘记录编号
* @return 结果 * @return 结果
*/ */
@Override @Override
@Transactional @Transactional
public int deleteRecruitmentByIds(String[] recruitIds) { 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)); return recruitmentMapper.deleteBatchIds(List.of(recruitIds));
} }
@@ -165,24 +182,26 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
*/ */
@Override @Override
@Transactional @Transactional
public String importRecruitment(java.util.List<CcdiStaffRecruitmentExcel> excelList) { public String importRecruitment(List<CcdiStaffRecruitmentExcel> recruitmentList,
if (StringUtils.isNull(excelList) || excelList.isEmpty()) { List<CcdiStaffRecruitmentWorkExcel> workList) {
recruitmentList = recruitmentList == null ? List.of() : recruitmentList;
workList = workList == null ? List.of() : workList;
boolean noRecruitmentRows = StringUtils.isNull(recruitmentList) || recruitmentList.isEmpty();
boolean noWorkRows = StringUtils.isNull(workList) || workList.isEmpty();
if (noRecruitmentRows && noWorkRows) {
throw new RuntimeException("至少需要一条数据"); throw new RuntimeException("至少需要一条数据");
} }
// 生成任务ID
String taskId = UUID.randomUUID().toString(); String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// 获取当前用户名
String userName = SecurityUtils.getUsername(); String userName = SecurityUtils.getUsername();
int totalCount = recruitmentList.size() + workList.size();
// 初始化Redis状态
String statusKey = "import:recruitment:" + taskId; String statusKey = "import:recruitment:" + taskId;
Map<String, Object> statusData = new HashMap<>(); Map<String, Object> statusData = new HashMap<>();
statusData.put("taskId", taskId); statusData.put("taskId", taskId);
statusData.put("status", "PROCESSING"); statusData.put("status", "PROCESSING");
statusData.put("totalCount", excelList.size()); statusData.put("totalCount", totalCount);
statusData.put("successCount", 0); statusData.put("successCount", 0);
statusData.put("failureCount", 0); statusData.put("failureCount", 0);
statusData.put("progress", 0); statusData.put("progress", 0);
@@ -192,9 +211,63 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
redisTemplate.opsForHash().putAll(statusKey, statusData); redisTemplate.opsForHash().putAll(statusKey, statusData);
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS); redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
// 调用异步导入服务 recruitmentImportService.importRecruitmentAsync(recruitmentList, workList, taskId, userName);
recruitmentImportService.importRecruitmentAsync(excelList, taskId, userName);
return taskId; 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();
}
private void replaceWorkExperienceList(CcdiStaffRecruitmentEditDTO editDTO) {
LambdaQueryWrapper<CcdiStaffRecruitmentWork> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(CcdiStaffRecruitmentWork::getRecruitId, editDTO.getRecruitId());
if (!Objects.equals(RecruitType.SOCIAL.getCode(), editDTO.getRecruitType())) {
recruitmentWorkMapper.delete(deleteWrapper);
return;
}
if (editDTO.getWorkExperienceList() == null) {
return;
}
recruitmentWorkMapper.delete(deleteWrapper);
List<CcdiStaffRecruitmentWork> workList = buildWorkExperienceEntities(editDTO);
workList.forEach(recruitmentWorkMapper::insert);
}
private List<CcdiStaffRecruitmentWork> buildWorkExperienceEntities(CcdiStaffRecruitmentEditDTO editDTO) {
List<CcdiStaffRecruitmentWorkEditDTO> workExperienceList = editDTO.getWorkExperienceList();
if (workExperienceList == null || workExperienceList.isEmpty()) {
return new ArrayList<>();
}
List<CcdiStaffRecruitmentWork> entityList = new ArrayList<>();
for (int i = 0; i < workExperienceList.size(); i++) {
CcdiStaffRecruitmentWorkEditDTO item = workExperienceList.get(i);
if (item == null || StringUtils.isBlank(item.getCompanyName()) || StringUtils.isBlank(item.getJobStartMonth())) {
continue;
}
CcdiStaffRecruitmentWork work = new CcdiStaffRecruitmentWork();
BeanUtils.copyProperties(item, work);
work.setRecruitId(editDTO.getRecruitId());
work.setSortOrder(i + 1);
entityList.add(work);
}
return entityList;
}
} }

View File

@@ -1,6 +1,8 @@
package com.ruoyi.info.collection.utils; package com.ruoyi.info.collection.utils;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.handler.WriteHandler; import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.ruoyi.info.collection.handler.DictDropdownWriteHandler; import com.ruoyi.info.collection.handler.DictDropdownWriteHandler;
@@ -98,6 +100,23 @@ public class EasyExcelUtil {
} }
} }
/**
* 导入Excel按指定Sheet名称
*
* @param inputStream 输入流
* @param clazz 实体类
* @param sheetName 工作表名称
* @param <T> 泛型
* @return 数据列表
*/
public static <T> List<T> importExcel(java.io.InputStream inputStream, Class<T> clazz, String sheetName) {
try {
return EasyExcel.read(inputStream).head(clazz).sheet(sheetName).doReadSync();
} catch (Exception e) {
throw new RuntimeException("导入Excel失败", e);
}
}
/** /**
* 下载导入模板 * 下载导入模板
* *
@@ -210,6 +229,45 @@ public class EasyExcelUtil {
} }
} }
/**
* 下载双Sheet导入模板带字典下拉框
*
* @param response 响应对象
* @param firstClazz 第一张Sheet实体类
* @param firstSheetName 第一张Sheet名称
* @param secondClazz 第二张Sheet实体类
* @param secondSheetName 第二张Sheet名称
* @param fileName 文件名称
* @param <T1> 第一张Sheet泛型
* @param <T2> 第二张Sheet泛型
*/
public static <T1, T2> void importTemplateWithDictDropdown(
HttpServletResponse response,
Class<T1> firstClazz,
String firstSheetName,
Class<T2> secondClazz,
String secondSheetName,
String fileName
) {
setResponseHeader(response, fileName);
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) {
writer.write(List.of(), buildTemplateSheet(0, firstClazz, firstSheetName));
writer.write(List.of(), buildTemplateSheet(1, secondClazz, secondSheetName));
} catch (IOException e) {
throw new RuntimeException("下载双Sheet导入模板失败", e);
}
}
private static <T> WriteSheet buildTemplateSheet(int sheetNo, Class<T> clazz, String sheetName) {
return EasyExcel.writerSheet(sheetNo, sheetName)
.head(clazz)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
.registerWriteHandler(new TextFormatWriteHandler(clazz))
.registerWriteHandler(new RequiredFieldWriteHandler(clazz))
.build();
}
/** /**
* 导出Excel带字典下拉框 * 导出Excel带字典下拉框
* 导出的数据包含实际值,但模板中有下拉框供后续编辑使用 * 导出的数据包含实际值,但模板中有下拉框供后续编辑使用

View File

@@ -4,6 +4,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper"> <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" <resultMap id="CcdiIntermediaryEnterpriseRelationVOResult"
type="com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO"> type="com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEnterpriseRelationVO">
<id property="id" column="id"/> <id property="id" column="id"/>
@@ -63,4 +76,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND social_credit_code = #{socialCreditCode} AND social_credit_code = #{socialCreditCode}
</select> </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> </mapper>

View File

@@ -32,7 +32,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
child.create_time child.create_time
FROM ccdi_biz_intermediary child FROM ccdi_biz_intermediary child
INNER JOIN ccdi_biz_intermediary parent INNER JOIN ccdi_biz_intermediary parent
ON child.related_num_id COLLATE utf8mb4_general_ci = parent.biz_id COLLATE utf8mb4_general_ci 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 AND parent.person_sub_type COLLATE utf8mb4_general_ci = '本人' COLLATE utf8mb4_general_ci
WHERE child.person_sub_type IS NOT NULL WHERE child.person_sub_type IS NOT NULL
AND child.person_sub_type COLLATE utf8mb4_general_ci != '本人' COLLATE utf8mb4_general_ci AND child.person_sub_type COLLATE utf8mb4_general_ci != '本人' COLLATE utf8mb4_general_ci

View File

@@ -23,6 +23,7 @@
<result property="contactPhone" column="contact_phone"/> <result property="contactPhone" column="contact_phone"/>
<result property="supplierUscc" column="supplier_uscc"/> <result property="supplierUscc" column="supplier_uscc"/>
<result property="supplierBankAccount" column="supplier_bank_account"/> <result property="supplierBankAccount" column="supplier_bank_account"/>
<result property="supplierCount" column="supplier_count"/>
<result property="applyDate" column="apply_date"/> <result property="applyDate" column="apply_date"/>
<result property="planApproveDate" column="plan_approve_date"/> <result property="planApproveDate" column="plan_approve_date"/>
<result property="announceDate" column="announce_date"/> <result property="announceDate" column="announce_date"/>
@@ -47,49 +48,61 @@
<!-- 分页查询采购交易列表 --> <!-- 分页查询采购交易列表 -->
<select id="selectTransactionPage" resultMap="CcdiPurchaseTransactionVOResult"> <select id="selectTransactionPage" resultMap="CcdiPurchaseTransactionVOResult">
SELECT SELECT
purchase_id, purchase_category, project_name, subject_name, subject_desc, t.purchase_id, t.purchase_category, t.project_name, t.subject_name, t.subject_desc,
purchase_qty, budget_amount, bid_amount, actual_amount, contract_amount, settlement_amount, t.purchase_qty, t.budget_amount, t.bid_amount, t.actual_amount, t.contract_amount, t.settlement_amount,
purchase_method, supplier_name, contact_person, contact_phone, supplier_uscc, supplier_bank_account, t.purchase_method, t.supplier_name, t.contact_person, t.contact_phone, t.supplier_uscc, t.supplier_bank_account,
apply_date, plan_approve_date, announce_date, bid_open_date, contract_sign_date, IFNULL(supplier_stats.supplier_count, 0) AS supplier_count,
expected_delivery_date, actual_delivery_date, acceptance_date, settlement_date, t.apply_date, t.plan_approve_date, t.announce_date, t.bid_open_date, t.contract_sign_date,
applicant_id, applicant_name, apply_department, purchase_leader_id, purchase_leader_name, purchase_department, t.expected_delivery_date, t.actual_delivery_date, t.acceptance_date, t.settlement_date,
created_by, create_time, updated_by, update_time t.applicant_id, t.applicant_name, t.apply_department, t.purchase_leader_id, t.purchase_leader_name, t.purchase_department,
FROM ccdi_purchase_transaction t.created_by, t.create_time, t.updated_by, t.update_time
FROM ccdi_purchase_transaction t
LEFT JOIN (
SELECT purchase_id, COUNT(*) AS supplier_count
FROM ccdi_purchase_transaction_supplier
GROUP BY purchase_id
) supplier_stats ON supplier_stats.purchase_id = t.purchase_id
<where> <where>
<if test="query.projectName != null and query.projectName != ''"> <if test="query.projectName != null and query.projectName != ''">
AND project_name LIKE CONCAT('%', #{query.projectName}, '%') AND t.project_name LIKE CONCAT('%', #{query.projectName}, '%')
</if> </if>
<if test="query.subjectName != null and query.subjectName != ''"> <if test="query.subjectName != null and query.subjectName != ''">
AND subject_name LIKE CONCAT('%', #{query.subjectName}, '%') AND t.subject_name LIKE CONCAT('%', #{query.subjectName}, '%')
</if> </if>
<if test="query.applicantName != null and query.applicantName != ''"> <if test="query.applicantName != null and query.applicantName != ''">
AND applicant_name LIKE CONCAT('%', #{query.applicantName}, '%') AND t.applicant_name LIKE CONCAT('%', #{query.applicantName}, '%')
</if> </if>
<if test="query.applicantId != null and query.applicantId != ''"> <if test="query.applicantId != null and query.applicantId != ''">
AND applicant_id = #{query.applicantId} AND t.applicant_id = #{query.applicantId}
</if> </if>
<if test="query.applyDateStart != null"> <if test="query.applyDateStart != null">
AND apply_date &gt;= #{query.applyDateStart} AND t.apply_date &gt;= #{query.applyDateStart}
</if> </if>
<if test="query.applyDateEnd != null"> <if test="query.applyDateEnd != null">
AND apply_date &lt;= #{query.applyDateEnd} AND t.apply_date &lt;= #{query.applyDateEnd}
</if> </if>
</where> </where>
ORDER BY create_time DESC ORDER BY t.create_time DESC
</select> </select>
<!-- 查询采购交易详情 --> <!-- 查询采购交易详情 -->
<select id="selectTransactionById" resultMap="CcdiPurchaseTransactionVOResult"> <select id="selectTransactionById" resultMap="CcdiPurchaseTransactionVOResult">
SELECT SELECT
purchase_id, purchase_category, project_name, subject_name, subject_desc, t.purchase_id, t.purchase_category, t.project_name, t.subject_name, t.subject_desc,
purchase_qty, budget_amount, bid_amount, actual_amount, contract_amount, settlement_amount, t.purchase_qty, t.budget_amount, t.bid_amount, t.actual_amount, t.contract_amount, t.settlement_amount,
purchase_method, supplier_name, contact_person, contact_phone, supplier_uscc, supplier_bank_account, t.purchase_method, t.supplier_name, t.contact_person, t.contact_phone, t.supplier_uscc, t.supplier_bank_account,
apply_date, plan_approve_date, announce_date, bid_open_date, contract_sign_date, IFNULL(supplier_stats.supplier_count, 0) AS supplier_count,
expected_delivery_date, actual_delivery_date, acceptance_date, settlement_date, t.apply_date, t.plan_approve_date, t.announce_date, t.bid_open_date, t.contract_sign_date,
applicant_id, applicant_name, apply_department, purchase_leader_id, purchase_leader_name, purchase_department, t.expected_delivery_date, t.actual_delivery_date, t.acceptance_date, t.settlement_date,
created_by, create_time, updated_by, update_time t.applicant_id, t.applicant_name, t.apply_department, t.purchase_leader_id, t.purchase_leader_name, t.purchase_department,
FROM ccdi_purchase_transaction t.created_by, t.create_time, t.updated_by, t.update_time
WHERE purchase_id = #{purchaseId} FROM ccdi_purchase_transaction t
LEFT JOIN (
SELECT purchase_id, COUNT(*) AS supplier_count
FROM ccdi_purchase_transaction_supplier
GROUP BY purchase_id
) supplier_stats ON supplier_stats.purchase_id = t.purchase_id
WHERE t.purchase_id = #{purchaseId}
</select> </select>
<!-- 批量插入采购交易数据 --> <!-- 批量插入采购交易数据 -->
@@ -137,4 +150,12 @@
</foreach> </foreach>
</update> </update>
<delete id="deleteSuppliersByPurchaseIds">
DELETE FROM ccdi_purchase_transaction_supplier
WHERE purchase_id IN
<foreach collection="purchaseIds" item="purchaseId" open="(" separator="," close=")">
#{purchaseId}
</foreach>
</delete>
</mapper> </mapper>

View File

@@ -8,7 +8,9 @@
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO" id="CcdiStaffEnterpriseRelationVOResult"> <resultMap type="com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO" id="CcdiStaffEnterpriseRelationVOResult">
<id property="id" column="id"/> <id property="id" column="id"/>
<result property="personId" column="person_id"/> <result property="personId" column="person_id"/>
<result property="personName" column="person_name"/> <result property="relationName" column="relation_name"/>
<result property="staffPersonId" column="staff_person_id"/>
<result property="staffPersonName" column="staff_person_name"/>
<result property="relationPersonPost" column="relation_person_post"/> <result property="relationPersonPost" column="relation_person_post"/>
<result property="socialCreditCode" column="social_credit_code"/> <result property="socialCreditCode" column="social_credit_code"/>
<result property="enterpriseName" column="enterprise_name"/> <result property="enterpriseName" column="enterprise_name"/>
@@ -28,17 +30,28 @@
<!-- 分页查询员工实体关系列表 --> <!-- 分页查询员工实体关系列表 -->
<select id="selectRelationPage" resultMap="CcdiStaffEnterpriseRelationVOResult"> <select id="selectRelationPage" resultMap="CcdiStaffEnterpriseRelationVOResult">
SELECT SELECT
ser.id, ser.person_id, bs.name as person_name, ser.relation_person_post, ser.id, ser.person_id, sfr.relation_name, sfr.person_id AS staff_person_id, bs.name AS staff_person_name,
ser.relation_person_post,
ser.social_credit_code, ser.enterprise_name, ser.status, ser.remark, ser.social_credit_code, ser.enterprise_name, ser.status, ser.remark,
ser.data_source, ser.is_employee, ser.is_emp_family, ser.is_customer, ser.data_source, ser.is_employee, ser.is_emp_family, ser.is_customer,
ser.is_cust_family, ser.created_by, ser.create_time, ser.updated_by, ser.is_cust_family, ser.created_by, ser.create_time, ser.updated_by,
ser.update_time ser.update_time
FROM ccdi_staff_enterprise_relation ser FROM ccdi_staff_enterprise_relation ser
LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card LEFT JOIN ccdi_staff_fmy_relation sfr
ON ser.person_id = sfr.relation_cert_no
AND sfr.is_emp_family = 1
LEFT JOIN ccdi_base_staff bs ON sfr.person_id = bs.id_card
<where> <where>
<if test="query.personId != null and query.personId != ''"> <if test="query.personId != null and query.personId != ''">
AND ser.person_id LIKE CONCAT('%', #{query.personId}, '%') AND ser.person_id LIKE CONCAT('%', #{query.personId}, '%')
</if> </if>
<if test="query.relationName != null and query.relationName != ''">
AND sfr.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
</if>
<if test="query.staffPersonName != null and query.staffPersonName != ''">
AND (sfr.person_id LIKE CONCAT('%', #{query.staffPersonName}, '%')
OR bs.name LIKE CONCAT('%', #{query.staffPersonName}, '%'))
</if>
<if test="query.socialCreditCode != null and query.socialCreditCode != ''"> <if test="query.socialCreditCode != null and query.socialCreditCode != ''">
AND ser.social_credit_code LIKE CONCAT('%', #{query.socialCreditCode}, '%') AND ser.social_credit_code LIKE CONCAT('%', #{query.socialCreditCode}, '%')
</if> </if>
@@ -55,16 +68,40 @@
<!-- 查询员工实体关系详情 --> <!-- 查询员工实体关系详情 -->
<select id="selectRelationById" resultMap="CcdiStaffEnterpriseRelationVOResult"> <select id="selectRelationById" resultMap="CcdiStaffEnterpriseRelationVOResult">
SELECT SELECT
ser.id, ser.person_id, bs.name as person_name, ser.relation_person_post, ser.id, ser.person_id, sfr.relation_name, sfr.person_id AS staff_person_id, bs.name AS staff_person_name,
ser.relation_person_post,
ser.social_credit_code, ser.enterprise_name, ser.status, ser.remark, ser.social_credit_code, ser.enterprise_name, ser.status, ser.remark,
ser.data_source, ser.is_employee, ser.is_emp_family, ser.is_customer, ser.data_source, ser.is_employee, ser.is_emp_family, ser.is_customer,
ser.is_cust_family, ser.created_by, ser.create_time, ser.updated_by, ser.is_cust_family, ser.created_by, ser.create_time, ser.updated_by,
ser.update_time ser.update_time
FROM ccdi_staff_enterprise_relation ser FROM ccdi_staff_enterprise_relation ser
LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card LEFT JOIN ccdi_staff_fmy_relation sfr
ON ser.person_id = sfr.relation_cert_no
AND sfr.is_emp_family = 1
LEFT JOIN ccdi_base_staff bs ON sfr.person_id = bs.id_card
WHERE ser.id = #{id} WHERE ser.id = #{id}
</select> </select>
<!-- 查询有效员工亲属下拉选项 -->
<select id="selectFamilyOptions" resultType="com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO">
SELECT
sfr.relation_cert_no AS relationCertNo,
sfr.relation_name AS relationName,
sfr.person_id AS staffPersonId,
bs.name AS staffPersonName
FROM ccdi_staff_fmy_relation sfr
LEFT JOIN ccdi_base_staff bs ON sfr.person_id = bs.id_card
<where>
sfr.is_emp_family = 1
AND sfr.status = 1
<if test="query != null and query != ''">
AND sfr.relation_cert_no LIKE CONCAT('%', #{query}, '%')
</if>
</where>
ORDER BY sfr.create_time DESC
LIMIT 100
</select>
<!-- 判断身份证号和统一社会信用代码的组合是否已存在 --> <!-- 判断身份证号和统一社会信用代码的组合是否已存在 -->
<select id="existsByPersonIdAndSocialCreditCode" resultType="boolean"> <select id="existsByPersonIdAndSocialCreditCode" resultType="boolean">
SELECT COUNT(1) > 0 SELECT COUNT(1) > 0
@@ -84,6 +121,14 @@
</foreach> </foreach>
</select> </select>
<update id="invalidateByFamilyCertNo">
UPDATE ccdi_staff_enterprise_relation
SET status = 0,
update_time = NOW()
WHERE person_id = #{personId}
AND status != 0
</update>
<!-- 批量插入员工实体关系数据 --> <!-- 批量插入员工实体关系数据 -->
<insert id="insertBatch"> <insert id="insertBatch">
INSERT INTO ccdi_staff_enterprise_relation INSERT INTO ccdi_staff_enterprise_relation

View File

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

View File

@@ -96,8 +96,12 @@ class CcdiAssetInfoControllerTest {
@Test @Test
void getImportFailures_shouldReturnPagedRows() { void getImportFailures_shouldReturnPagedRows() {
AssetImportFailureVO failure1 = new AssetImportFailureVO(); AssetImportFailureVO failure1 = new AssetImportFailureVO();
failure1.setSheetName("亲属资产信息");
failure1.setRowNum(2);
failure1.setPersonId("A1"); failure1.setPersonId("A1");
AssetImportFailureVO failure2 = new AssetImportFailureVO(); AssetImportFailureVO failure2 = new AssetImportFailureVO();
failure2.setSheetName("亲属资产信息");
failure2.setRowNum(3);
failure2.setPersonId("A2"); failure2.setPersonId("A2");
when(assetInfoImportService.getImportFailures("task-3")).thenReturn(List.of(failure1, failure2)); when(assetInfoImportService.getImportFailures("task-3")).thenReturn(List.of(failure1, failure2));
@@ -105,7 +109,10 @@ class CcdiAssetInfoControllerTest {
assertEquals(2, result.getTotal()); assertEquals(2, result.getTotal());
assertEquals(1, result.getRows().size()); assertEquals(1, result.getRows().size());
assertEquals("A2", ((AssetImportFailureVO) result.getRows().get(0)).getPersonId()); AssetImportFailureVO row = (AssetImportFailureVO) result.getRows().get(0);
assertEquals("亲属资产信息", row.getSheetName());
assertEquals(3, row.getRowNum());
assertEquals("A2", row.getPersonId());
} }
@Test @Test

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