Compare commits
21 Commits
fc6af5234d
...
aa08ab4711
| Author | SHA1 | Date | |
|---|---|---|---|
| aa08ab4711 | |||
| b7db711906 | |||
| b7d020c0b2 | |||
| d444eafd5f | |||
| c9398881f3 | |||
| d79a60ab8c | |||
| e9403662e2 | |||
| fd79bfe62f | |||
| c7f4982451 | |||
| 0b2571b962 | |||
| 129e44c808 | |||
| 50c177da78 | |||
| c660025bcc | |||
| bd51991248 | |||
| a2ba044ebe | |||
| 110817abba | |||
| ff9627d0d9 | |||
| 2d1b02474c | |||
| 5a9b79d4ee | |||
| 0c5fa6b2c8 | |||
| 94507e3747 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -89,4 +89,8 @@ ruoyi-ui/vue.config.js
|
||||
|
||||
tests/
|
||||
|
||||
tongweb_62318.properties
|
||||
tongweb_62318.properties
|
||||
|
||||
.superpowers/
|
||||
|
||||
tmp/
|
||||
15
AGENTS.md
15
AGENTS.md
@@ -80,7 +80,7 @@
|
||||
mvn clean compile
|
||||
|
||||
# 启动主应用(Jar)
|
||||
cd ruoyi-admin/target && java -jar ruoyi-admin.jar
|
||||
sh bin/restart_java_backend.sh
|
||||
|
||||
# 打包全部模块
|
||||
mvn clean package
|
||||
@@ -225,6 +225,18 @@ return AjaxResult.success(result);
|
||||
- 返回结果仅展示失败数据
|
||||
- 大数据量导入优先采用 EasyExcel + 异步处理
|
||||
|
||||
### 导入页面测试规范
|
||||
|
||||
- 导入功能测试必须进入真实业务页面执行,先在页面内下载当前导入模板,再基于该模板生成测试文件,禁止手工凭记忆新建表头或脱离页面直接构造上传文件
|
||||
- 双 Sheet 模板的导入测试必须覆盖两个 Sheet 的联动关系;除“缺少 Sheet / 空 Sheet”专项场景外,默认两个 Sheet 都要准备测试数据
|
||||
- 导入测试文件优先放在 `output/spreadsheet/` 或 `output/playwright/`,不提交到 git
|
||||
- 需要按场景拆分测试文件,避免多个互斥校验互相覆盖;至少覆盖空模板、主信息必填、主信息格式与金额、主从关系异常、供应商校验、缺少/空 Sheet、成功导入、成功与失败混合、失败记录查看、导入后清理回滚
|
||||
- 主从关系异常测试至少覆盖:已存在主键、供应商有数据但主信息缺失、主信息重复、供应商 Sheet 中采购事项 ID 为空
|
||||
- 供应商校验测试至少覆盖:重复供应商、多条中标、供应商名称为空、名称超长、联系人超长、银行账户超长、联系电话非法、统一信用代码非法、是否中标枚举非法
|
||||
- 页面上传后必须核对页面提示、导入状态、失败记录弹窗和列表总数变化;异步导入场景还要核对任务状态从 `PROCESSING` 到最终状态的变化
|
||||
- 对“成功导入 + 异常数据混合”的样本,必须额外核对成功数据是否真正入库、异常数据是否被拦截,以及是否存在被静默忽略的行
|
||||
- 导入测试结束后,必须删除本轮成功写入的测试数据,清理页面本地导入任务缓存,并关闭测试过程中启动的前后端进程
|
||||
|
||||
---
|
||||
|
||||
## 当前仓库结构
|
||||
@@ -348,4 +360,3 @@ ccdi/
|
||||
- `sql/migration/` 用于增量迁移脚本,新增修复脚本优先按日期或功能命名
|
||||
- 启动前后端或 Mock 服务做验证后,结束测试时要主动停止进程,避免残留占用端口
|
||||
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ follow_logs() {
|
||||
start_action() {
|
||||
running_pids=$(collect_pids)
|
||||
if [ -n "${running_pids:-}" ]; then
|
||||
log_error "检测到已有后端进程在运行: $running_pids,请先执行 stop 或 restart"
|
||||
log_error "检测到已有后端进程在运行: ${running_pids},请先执行 stop 或 restart"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -80,18 +80,6 @@ public class CcdiAccountInfoController extends BaseController {
|
||||
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, "账户库管理");
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增账户
|
||||
*/
|
||||
|
||||
@@ -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.CcdiBaseStaffEditDTO;
|
||||
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.vo.*;
|
||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService;
|
||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
|
||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
|
||||
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||
@@ -45,6 +47,9 @@ public class CcdiBaseStaffController extends BaseController {
|
||||
@Resource
|
||||
private ICcdiBaseStaffImportService importAsyncService;
|
||||
|
||||
@Resource
|
||||
private ICcdiBaseStaffAssetImportService baseStaffAssetImportService;
|
||||
|
||||
/**
|
||||
* 查询员工列表
|
||||
*/
|
||||
@@ -70,18 +75,6 @@ public class CcdiBaseStaffController extends BaseController {
|
||||
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 = "下载导入模板")
|
||||
@PostMapping("/importTemplate")
|
||||
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')")
|
||||
@Log(title = "员工信息", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
|
||||
List<CcdiBaseStaffExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiBaseStaffExcel.class);
|
||||
public AjaxResult importData(MultipartFile file) throws Exception {
|
||||
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("至少需要一条数据");
|
||||
}
|
||||
|
||||
// 提交异步任务
|
||||
String taskId = baseStaffService.importBaseStaff(list, updateSupport);
|
||||
|
||||
// 立即返回,不等待后台任务完成
|
||||
ImportResultVO result = new ImportResultVO();
|
||||
result.setTaskId(taskId);
|
||||
result.setStatus("PROCESSING");
|
||||
result.setMessage("导入任务已提交,正在后台处理");
|
||||
BaseStaffImportSubmitResultVO result = new BaseStaffImportSubmitResultVO();
|
||||
if (hasStaffRows) {
|
||||
result.setStaffTaskId(baseStaffService.importBaseStaff(staffList));
|
||||
}
|
||||
if (hasAssetRows) {
|
||||
result.setAssetTaskId(baseStaffAssetImportService.importAssetInfo(assetList));
|
||||
}
|
||||
result.setMessage(buildImportSubmitMessage(hasStaffRows, hasAssetRows));
|
||||
|
||||
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
||||
}
|
||||
@@ -202,4 +214,14 @@ public class CcdiBaseStaffController extends BaseController {
|
||||
|
||||
return getDataTable(pageData, failures.size());
|
||||
}
|
||||
|
||||
private String buildImportSubmitMessage(boolean hasStaffRows, boolean hasAssetRows) {
|
||||
if (hasStaffRows && hasAssetRows) {
|
||||
return "已提交员工信息和员工资产信息导入任务";
|
||||
}
|
||||
if (hasStaffRows) {
|
||||
return "已提交员工信息导入任务";
|
||||
}
|
||||
return "已提交员工资产信息导入任务";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,18 +63,6 @@ public class CcdiCustEnterpriseRelationController extends BaseController {
|
||||
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, "信贷客户实体关联信息");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信贷客户实体关联详细信息
|
||||
*/
|
||||
|
||||
@@ -103,17 +103,6 @@ public class CcdiCustFmyRelationController extends BaseController {
|
||||
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注解自动添加下拉框
|
||||
|
||||
@@ -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.CcdiPurchaseTransactionQueryDTO;
|
||||
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.ImportResultVO;
|
||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||
@@ -33,12 +34,12 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 采购交易信息Controller
|
||||
* 招投标信息维护Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-06
|
||||
*/
|
||||
@Tag(name = "采购交易信息管理")
|
||||
@Tag(name = "招投标信息维护")
|
||||
@RestController
|
||||
@RequestMapping("/ccdi/purchaseTransaction")
|
||||
public class CcdiPurchaseTransactionController extends BaseController {
|
||||
@@ -50,9 +51,9 @@ public class CcdiPurchaseTransactionController extends BaseController {
|
||||
private ICcdiPurchaseTransactionImportService transactionImportService;
|
||||
|
||||
/**
|
||||
* 查询采购交易列表
|
||||
* 查询招投标信息列表
|
||||
*/
|
||||
@Operation(summary = "查询采购交易列表")
|
||||
@Operation(summary = "查询招投标信息列表")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(CcdiPurchaseTransactionQueryDTO queryDTO) {
|
||||
@@ -64,21 +65,9 @@ public class CcdiPurchaseTransactionController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出采购交易列表
|
||||
* 获取招投标信息详细信息
|
||||
*/
|
||||
@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 = "获取采购交易详细信息")
|
||||
@Operation(summary = "获取招投标信息详细信息")
|
||||
@Parameter(name = "purchaseId", description = "采购事项ID", required = true)
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:query')")
|
||||
@GetMapping(value = "/{purchaseId}")
|
||||
@@ -87,66 +76,81 @@ public class CcdiPurchaseTransactionController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增采购交易
|
||||
* 新增招投标信息
|
||||
*/
|
||||
@Operation(summary = "新增采购交易")
|
||||
@Operation(summary = "新增招投标信息")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:add')")
|
||||
@Log(title = "采购交易信息", businessType = BusinessType.INSERT)
|
||||
@Log(title = "招投标信息维护", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody CcdiPurchaseTransactionAddDTO addDTO) {
|
||||
return toAjax(transactionService.insertTransaction(addDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改采购交易
|
||||
* 修改招投标信息
|
||||
*/
|
||||
@Operation(summary = "修改采购交易")
|
||||
@Operation(summary = "修改招投标信息")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:edit')")
|
||||
@Log(title = "采购交易信息", businessType = BusinessType.UPDATE)
|
||||
@Log(title = "招投标信息维护", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody CcdiPurchaseTransactionEditDTO editDTO) {
|
||||
return toAjax(transactionService.updateTransaction(editDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除采购交易
|
||||
* 删除招投标信息
|
||||
*/
|
||||
@Operation(summary = "删除采购交易")
|
||||
@Operation(summary = "删除招投标信息")
|
||||
@Parameter(name = "purchaseIds", description = "采购事项ID数组", required = true)
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:remove')")
|
||||
@Log(title = "采购交易信息", businessType = BusinessType.DELETE)
|
||||
@Log(title = "招投标信息维护", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{purchaseIds}")
|
||||
public AjaxResult remove(@PathVariable String[] purchaseIds) {
|
||||
return toAjax(transactionService.deleteTransactionByIds(purchaseIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载带字典下拉框的导入模板
|
||||
* 使用@DictDropdown注解自动添加下拉框
|
||||
* 下载双Sheet导入模板
|
||||
*/
|
||||
@Operation(summary = "下载导入模板")
|
||||
@PostMapping("/importTemplate")
|
||||
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)
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:purchaseTransaction:import')")
|
||||
@Log(title = "采购交易信息", businessType = BusinessType.IMPORT)
|
||||
@Log(title = "招投标信息维护", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importData")
|
||||
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("至少需要一条数据");
|
||||
}
|
||||
|
||||
// 提交异步任务
|
||||
String taskId = transactionService.importTransaction(list);
|
||||
String taskId = transactionService.importTransaction(mainList, supplierList);
|
||||
|
||||
// 立即返回,不等待后台任务完成
|
||||
ImportResultVO result = new ImportResultVO();
|
||||
|
||||
@@ -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.CcdiStaffEnterpriseRelationQueryDTO;
|
||||
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.ImportResultVO;
|
||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||
@@ -33,12 +34,12 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息Controller
|
||||
* 员工亲属实体关联Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
*/
|
||||
@Tag(name = "员工实体关系信息管理")
|
||||
@Tag(name = "员工亲属实体关联管理")
|
||||
@RestController
|
||||
@RequestMapping("/ccdi/staffEnterpriseRelation")
|
||||
public class CcdiStaffEnterpriseRelationController extends BaseController {
|
||||
@@ -50,9 +51,9 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
|
||||
private ICcdiStaffEnterpriseRelationImportService relationImportService;
|
||||
|
||||
/**
|
||||
* 查询员工实体关系列表
|
||||
* 查询员工亲属实体关联列表
|
||||
*/
|
||||
@Operation(summary = "查询员工实体关系列表")
|
||||
@Operation(summary = "查询员工亲属实体关联列表")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(CcdiStaffEnterpriseRelationQueryDTO queryDTO) {
|
||||
@@ -64,21 +65,20 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出员工实体关系列表
|
||||
* 查询有效员工亲属下拉列表
|
||||
*/
|
||||
@Operation(summary = "导出员工实体关系列表")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:export')")
|
||||
@Log(title = "员工实体关系信息", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, CcdiStaffEnterpriseRelationQueryDTO queryDTO) {
|
||||
List<CcdiStaffEnterpriseRelationExcel> list = relationService.selectRelationListForExport(queryDTO);
|
||||
EasyExcelUtil.exportExcel(response, list, CcdiStaffEnterpriseRelationExcel.class, "员工实体关系信息");
|
||||
@Operation(summary = "查询有效员工亲属下拉列表")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:list')")
|
||||
@GetMapping("/familyOptions")
|
||||
public AjaxResult familyOptions(@RequestParam(required = false) String query) {
|
||||
List<CcdiStaffEnterpriseRelationOptionVO> list = relationService.selectFamilyOptions(query);
|
||||
return success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取员工实体关系详细信息
|
||||
* 获取员工亲属实体关联详细信息
|
||||
*/
|
||||
@Operation(summary = "获取员工实体关系详细信息")
|
||||
@Operation(summary = "获取员工亲属实体关联详细信息")
|
||||
@Parameter(name = "id", description = "主键ID", required = true)
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:query')")
|
||||
@GetMapping(value = "/{id}")
|
||||
@@ -87,34 +87,34 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增员工实体关系
|
||||
* 新增员工亲属实体关联
|
||||
*/
|
||||
@Operation(summary = "新增员工实体关系")
|
||||
@Operation(summary = "新增员工亲属实体关联")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:add')")
|
||||
@Log(title = "员工实体关系信息", businessType = BusinessType.INSERT)
|
||||
@Log(title = "员工亲属实体关联", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody CcdiStaffEnterpriseRelationAddDTO addDTO) {
|
||||
return toAjax(relationService.insertRelation(addDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改员工实体关系
|
||||
* 修改员工亲属实体关联
|
||||
*/
|
||||
@Operation(summary = "修改员工实体关系")
|
||||
@Operation(summary = "修改员工亲属实体关联")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:edit')")
|
||||
@Log(title = "员工实体关系信息", businessType = BusinessType.UPDATE)
|
||||
@Log(title = "员工亲属实体关联", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody CcdiStaffEnterpriseRelationEditDTO editDTO) {
|
||||
return toAjax(relationService.updateRelation(editDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除员工实体关系
|
||||
* 删除员工亲属实体关联
|
||||
*/
|
||||
@Operation(summary = "删除员工实体关系")
|
||||
@Operation(summary = "删除员工亲属实体关联")
|
||||
@Parameter(name = "ids", description = "主键ID数组", required = true)
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:remove')")
|
||||
@Log(title = "员工实体关系信息", businessType = BusinessType.DELETE)
|
||||
@Log(title = "员工亲属实体关联", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(relationService.deleteRelationByIds(ids));
|
||||
@@ -127,16 +127,16 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
|
||||
@Operation(summary = "下载导入模板")
|
||||
@PostMapping("/importTemplate")
|
||||
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)
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:import')")
|
||||
@Log(title = "员工实体关系信息", businessType = BusinessType.IMPORT)
|
||||
@Log(title = "员工亲属实体关联", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
|
||||
List<CcdiStaffEnterpriseRelationExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffEnterpriseRelationExcel.class);
|
||||
@@ -152,9 +152,9 @@ public class CcdiStaffEnterpriseRelationController extends BaseController {
|
||||
ImportResultVO result = new ImportResultVO();
|
||||
result.setTaskId(taskId);
|
||||
result.setStatus("PROCESSING");
|
||||
result.setMessage("导入任务已提交,正在后台处理");
|
||||
result.setMessage("员工亲属实体关联导入任务已提交,正在后台处理");
|
||||
|
||||
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
||||
return AjaxResult.success("员工亲属实体关联导入任务已提交,正在后台处理", result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.CcdiStaffFmyRelationEditDTO;
|
||||
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.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.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.ICcdiStaffFmyRelationService;
|
||||
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||
@@ -49,6 +51,9 @@ public class CcdiStaffFmyRelationController extends BaseController {
|
||||
@Resource
|
||||
private ICcdiStaffFmyRelationImportService relationImportService;
|
||||
|
||||
@Resource
|
||||
private ICcdiAssetInfoImportService assetInfoImportService;
|
||||
|
||||
/**
|
||||
* 查询员工亲属关系列表
|
||||
*/
|
||||
@@ -63,18 +68,6 @@ public class CcdiStaffFmyRelationController extends BaseController {
|
||||
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 = "下载导入模板")
|
||||
@PostMapping("/importTemplate")
|
||||
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)
|
||||
@PostMapping("/importData")
|
||||
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("至少需要一条数据");
|
||||
}
|
||||
|
||||
// 提交异步任务
|
||||
String taskId = relationService.importRelation(list);
|
||||
|
||||
// 立即返回,不等待后台任务完成
|
||||
ImportResultVO result = new ImportResultVO();
|
||||
result.setTaskId(taskId);
|
||||
result.setStatus("PROCESSING");
|
||||
result.setMessage("导入任务已提交,正在后台处理");
|
||||
StaffFmyRelationImportSubmitResultVO result = new StaffFmyRelationImportSubmitResultVO();
|
||||
if (hasRelationRows) {
|
||||
result.setRelationTaskId(relationService.importRelation(relationList));
|
||||
}
|
||||
if (hasAssetRows) {
|
||||
result.setAssetTaskId(assetInfoImportService.importAssetInfo(assetList));
|
||||
}
|
||||
result.setMessage(buildImportSubmitMessage(hasRelationRows, hasAssetRows));
|
||||
|
||||
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
||||
}
|
||||
@@ -198,4 +210,14 @@ public class CcdiStaffFmyRelationController extends BaseController {
|
||||
|
||||
return getDataTable(pageData, failures.size());
|
||||
}
|
||||
|
||||
private String buildImportSubmitMessage(boolean hasRelationRows, boolean hasAssetRows) {
|
||||
if (hasRelationRows && hasAssetRows) {
|
||||
return "已提交员工亲属关系和亲属资产信息导入任务";
|
||||
}
|
||||
if (hasRelationRows) {
|
||||
return "已提交员工亲属关系导入任务";
|
||||
}
|
||||
return "已提交亲属资产信息导入任务";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,18 +64,6 @@ public class CcdiStaffRecruitmentController extends BaseController {
|
||||
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, "员工招聘信息");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取招聘信息详细信息
|
||||
*/
|
||||
@@ -126,16 +114,14 @@ public class CcdiStaffRecruitmentController extends BaseController {
|
||||
@Operation(summary = "下载导入模板")
|
||||
@PostMapping("/importTemplate")
|
||||
public void importTemplate(HttpServletResponse response) {
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffRecruitmentExcel.class, "员工招聘信息");
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载历史工作经历导入模板
|
||||
*/
|
||||
@Operation(summary = "下载历史工作经历导入模板")
|
||||
@PostMapping("/workImportTemplate")
|
||||
public void workImportTemplate(HttpServletResponse response) {
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffRecruitmentWorkExcel.class, "历史工作经历");
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||
response,
|
||||
CcdiStaffRecruitmentExcel.class,
|
||||
"招聘信息",
|
||||
CcdiStaffRecruitmentWorkExcel.class,
|
||||
"历史工作经历",
|
||||
"招聘信息管理导入模板"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,16 +133,25 @@ public class CcdiStaffRecruitmentController extends BaseController {
|
||||
@Log(title = "员工招聘信息", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importData")
|
||||
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("至少需要一条数据");
|
||||
}
|
||||
|
||||
// 提交异步任务
|
||||
String taskId = recruitmentService.importRecruitment(list);
|
||||
String taskId = recruitmentService.importRecruitment(recruitmentList, workList);
|
||||
|
||||
// 立即返回,不等待后台任务完成
|
||||
ImportResultVO result = new ImportResultVO();
|
||||
result.setTaskId(taskId);
|
||||
result.setStatus("PROCESSING");
|
||||
@@ -165,31 +160,6 @@ public class CcdiStaffRecruitmentController extends BaseController {
|
||||
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步导入历史工作经历
|
||||
*/
|
||||
@Operation(summary = "异步导入历史工作经历")
|
||||
@Parameter(name = "file", description = "导入文件", required = true)
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:import')")
|
||||
@Log(title = "员工招聘历史工作经历", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importWorkData")
|
||||
public AjaxResult importWorkData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
|
||||
List<CcdiStaffRecruitmentWorkExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffRecruitmentWorkExcel.class);
|
||||
|
||||
if (list == null || list.isEmpty()) {
|
||||
return error("至少需要一条数据");
|
||||
}
|
||||
|
||||
String taskId = recruitmentService.importRecruitmentWork(list);
|
||||
|
||||
ImportResultVO result = new ImportResultVO();
|
||||
result.setTaskId(taskId);
|
||||
result.setStatus("PROCESSING");
|
||||
result.setMessage("历史工作经历导入任务已提交,正在后台处理");
|
||||
|
||||
return AjaxResult.success("历史工作经历导入任务已提交,正在后台处理", result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询导入状态
|
||||
*/
|
||||
|
||||
@@ -63,18 +63,6 @@ public class CcdiStaffTransferController extends BaseController {
|
||||
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, "员工调动记录信息");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取员工调动记录详细信息
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -89,7 +89,6 @@ public class CcdiEnterpriseBaseInfoAddDTO implements Serializable {
|
||||
private String shareholder5;
|
||||
|
||||
@Schema(description = "经营状态")
|
||||
@NotBlank(message = "经营状态不能为空")
|
||||
@Size(max = 50, message = "经营状态长度不能超过50个字符")
|
||||
private String status;
|
||||
|
||||
@@ -102,6 +101,5 @@ public class CcdiEnterpriseBaseInfoAddDTO implements Serializable {
|
||||
private String entSource;
|
||||
|
||||
@Schema(description = "数据来源")
|
||||
@NotBlank(message = "数据来源不能为空")
|
||||
private String dataSource;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ public class CcdiEnterpriseBaseInfoEditDTO implements Serializable {
|
||||
private String shareholder5;
|
||||
|
||||
@Schema(description = "经营状态")
|
||||
@NotBlank(message = "经营状态不能为空")
|
||||
@Size(max = 50, message = "经营状态长度不能超过50个字符")
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.*;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -9,15 +10,16 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 采购交易信息新增DTO
|
||||
* 招投标信息新增DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-06
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "采购交易信息新增")
|
||||
@Schema(description = "招投标信息新增")
|
||||
public class CcdiPurchaseTransactionAddDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
@@ -88,30 +90,10 @@ public class CcdiPurchaseTransactionAddDTO implements Serializable {
|
||||
@Schema(description = "采购方式")
|
||||
private String purchaseMethod;
|
||||
|
||||
/** 中标供应商名称 */
|
||||
@Size(max = 200, message = "中标供应商名称长度不能超过200个字符")
|
||||
@Schema(description = "中标供应商名称")
|
||||
private String supplierName;
|
||||
|
||||
/** 供应商联系人 */
|
||||
@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;
|
||||
/** 供应商明细 */
|
||||
@Valid
|
||||
@Schema(description = "供应商明细列表")
|
||||
private List<CcdiPurchaseTransactionSupplierDTO> supplierList;
|
||||
|
||||
/** 采购申请日期(或立项日期) */
|
||||
@NotNull(message = "采购申请日期不能为空")
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.*;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -9,15 +10,16 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 采购交易信息编辑DTO
|
||||
* 招投标信息编辑DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-06
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "采购交易信息编辑")
|
||||
@Schema(description = "招投标信息编辑")
|
||||
public class CcdiPurchaseTransactionEditDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
@@ -88,30 +90,10 @@ public class CcdiPurchaseTransactionEditDTO implements Serializable {
|
||||
@Schema(description = "采购方式")
|
||||
private String purchaseMethod;
|
||||
|
||||
/** 中标供应商名称 */
|
||||
@Size(max = 200, message = "中标供应商名称长度不能超过200个字符")
|
||||
@Schema(description = "中标供应商名称")
|
||||
private String supplierName;
|
||||
|
||||
/** 供应商联系人 */
|
||||
@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;
|
||||
/** 供应商明细 */
|
||||
@Valid
|
||||
@Schema(description = "供应商明细列表")
|
||||
private List<CcdiPurchaseTransactionSupplierDTO> supplierList;
|
||||
|
||||
/** 采购申请日期(或立项日期) */
|
||||
@NotNull(message = "采购申请日期不能为空")
|
||||
|
||||
@@ -9,13 +9,13 @@ import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 采购交易信息查询DTO
|
||||
* 招投标信息查询DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-06
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "采购交易信息查询条件")
|
||||
@Schema(description = "招投标信息查询条件")
|
||||
public class CcdiPurchaseTransactionQueryDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -10,22 +10,22 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息新增DTO
|
||||
* 员工亲属实体关联新增DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "员工实体关系信息新增")
|
||||
@Schema(description = "员工亲属实体关联新增")
|
||||
public class CcdiStaffEnterpriseRelationAddDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 身份证号 */
|
||||
@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 = "身份证号格式不正确")
|
||||
@Schema(description = "身份证号")
|
||||
/** 亲属身份证号 */
|
||||
@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 = "亲属身份证号格式不正确")
|
||||
@Schema(description = "亲属身份证号")
|
||||
private String personId;
|
||||
|
||||
/** 关联人在企业的职务 */
|
||||
|
||||
@@ -10,13 +10,13 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息编辑DTO
|
||||
* 员工亲属实体关联编辑DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "员工实体关系信息编辑")
|
||||
@Schema(description = "员工亲属实体关联编辑")
|
||||
public class CcdiStaffEnterpriseRelationEditDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
@@ -27,8 +27,8 @@ public class CcdiStaffEnterpriseRelationEditDTO implements Serializable {
|
||||
@Schema(description = "主键ID")
|
||||
private Long id;
|
||||
|
||||
/** 身份证号 */
|
||||
@Schema(description = "身份证号(不可修改)")
|
||||
/** 亲属身份证号 */
|
||||
@Schema(description = "亲属身份证号(不可修改)")
|
||||
private String personId;
|
||||
|
||||
/** 关联人在企业的职务 */
|
||||
|
||||
@@ -7,22 +7,30 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息查询DTO
|
||||
* 员工亲属实体关联查询DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "员工实体关系信息查询条件")
|
||||
@Schema(description = "员工亲属实体关联查询条件")
|
||||
public class CcdiStaffEnterpriseRelationQueryDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 身份证号 */
|
||||
@Schema(description = "身份证号")
|
||||
/** 亲属身份证号 */
|
||||
@Schema(description = "亲属身份证号")
|
||||
private String personId;
|
||||
|
||||
/** 亲属姓名 */
|
||||
@Schema(description = "亲属姓名")
|
||||
private String relationName;
|
||||
|
||||
/** 关联员工 */
|
||||
@Schema(description = "关联员工")
|
||||
private String staffPersonName;
|
||||
|
||||
/** 统一社会信用代码 */
|
||||
@Schema(description = "统一社会信用代码")
|
||||
private String socialCreditCode;
|
||||
|
||||
@@ -7,10 +7,12 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 员工招聘信息编辑DTO
|
||||
@@ -91,4 +93,8 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable {
|
||||
/** 面试官2工号 */
|
||||
@Size(max = 10, message = "面试官2工号长度不能超过10个字符")
|
||||
private String interviewerId2;
|
||||
|
||||
/** 历史工作经历列表 */
|
||||
@Valid
|
||||
private List<CcdiStaffRecruitmentWorkEditDTO> workExperienceList;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -88,7 +88,7 @@ public class CcdiEnterpriseBaseInfoExcel implements Serializable {
|
||||
@ColumnWidth(18)
|
||||
private String shareholder5;
|
||||
|
||||
@ExcelProperty(value = "经营状态*", index = 16)
|
||||
@ExcelProperty(value = "经营状态", index = 16)
|
||||
@ColumnWidth(16)
|
||||
private String status;
|
||||
|
||||
@@ -99,8 +99,4 @@ public class CcdiEnterpriseBaseInfoExcel implements Serializable {
|
||||
@ExcelProperty(value = "企业来源*", index = 18)
|
||||
@ColumnWidth(18)
|
||||
private String entSource;
|
||||
|
||||
@ExcelProperty(value = "数据来源*", index = 19)
|
||||
@ColumnWidth(18)
|
||||
private String dataSource;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 采购交易信息Excel导入导出对象
|
||||
* 招投标主信息Excel导入导出对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-06
|
||||
@@ -88,107 +88,82 @@ public class CcdiPurchaseTransactionExcel implements Serializable {
|
||||
@Required
|
||||
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)
|
||||
@Required
|
||||
private Date applyDate;
|
||||
|
||||
/** 采购计划批准日期 */
|
||||
@ExcelProperty(value = "采购计划批准日期", index = 18)
|
||||
@ExcelProperty(value = "采购计划批准日期", index = 13)
|
||||
@ColumnWidth(18)
|
||||
private Date planApproveDate;
|
||||
|
||||
/** 采购公告发布日期 */
|
||||
@ExcelProperty(value = "采购公告发布日期", index = 19)
|
||||
@ExcelProperty(value = "采购公告发布日期", index = 14)
|
||||
@ColumnWidth(18)
|
||||
private Date announceDate;
|
||||
|
||||
/** 开标日期 */
|
||||
@ExcelProperty(value = "开标日期", index = 20)
|
||||
@ExcelProperty(value = "开标日期", index = 15)
|
||||
@ColumnWidth(18)
|
||||
private Date bidOpenDate;
|
||||
|
||||
/** 合同签订日期 */
|
||||
@ExcelProperty(value = "合同签订日期", index = 21)
|
||||
@ExcelProperty(value = "合同签订日期", index = 16)
|
||||
@ColumnWidth(18)
|
||||
private Date contractSignDate;
|
||||
|
||||
/** 预计交货日期 */
|
||||
@ExcelProperty(value = "预计交货日期", index = 22)
|
||||
@ExcelProperty(value = "预计交货日期", index = 17)
|
||||
@ColumnWidth(18)
|
||||
private Date expectedDeliveryDate;
|
||||
|
||||
/** 实际交货日期 */
|
||||
@ExcelProperty(value = "实际交货日期", index = 23)
|
||||
@ExcelProperty(value = "实际交货日期", index = 18)
|
||||
@ColumnWidth(18)
|
||||
private Date actualDeliveryDate;
|
||||
|
||||
/** 验收日期 */
|
||||
@ExcelProperty(value = "验收日期", index = 24)
|
||||
@ExcelProperty(value = "验收日期", index = 19)
|
||||
@ColumnWidth(18)
|
||||
private Date acceptanceDate;
|
||||
|
||||
/** 结算日期 */
|
||||
@ExcelProperty(value = "结算日期", index = 25)
|
||||
@ExcelProperty(value = "结算日期", index = 20)
|
||||
@ColumnWidth(18)
|
||||
private Date settlementDate;
|
||||
|
||||
/** 申请人工号 */
|
||||
@ExcelProperty(value = "申请人工号", index = 26)
|
||||
@ExcelProperty(value = "申请人工号", index = 21)
|
||||
@ColumnWidth(15)
|
||||
@Required
|
||||
private String applicantId;
|
||||
|
||||
/** 申请人姓名 */
|
||||
@ExcelProperty(value = "申请人姓名", index = 27)
|
||||
@ExcelProperty(value = "申请人姓名", index = 22)
|
||||
@ColumnWidth(15)
|
||||
@Required
|
||||
private String applicantName;
|
||||
|
||||
/** 申请部门 */
|
||||
@ExcelProperty(value = "申请部门", index = 28)
|
||||
@ExcelProperty(value = "申请部门", index = 23)
|
||||
@ColumnWidth(18)
|
||||
@Required
|
||||
private String applyDepartment;
|
||||
|
||||
/** 采购负责人工号 */
|
||||
@ExcelProperty(value = "采购负责人工号", index = 29)
|
||||
@ExcelProperty(value = "采购负责人工号", index = 24)
|
||||
@ColumnWidth(15)
|
||||
private String purchaseLeaderId;
|
||||
|
||||
/** 采购负责人姓名 */
|
||||
@ExcelProperty(value = "采购负责人姓名", index = 30)
|
||||
@ExcelProperty(value = "采购负责人姓名", index = 25)
|
||||
@ColumnWidth(15)
|
||||
private String purchaseLeaderName;
|
||||
|
||||
/** 采购部门 */
|
||||
@ExcelProperty(value = "采购部门", index = 31)
|
||||
@ExcelProperty(value = "采购部门", index = 26)
|
||||
@ColumnWidth(18)
|
||||
private String purchaseDepartment;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -10,23 +10,23 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息Excel导入导出对象
|
||||
* 员工亲属实体关联Excel导入导出对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "员工实体关系信息Excel导入导出对象")
|
||||
@Schema(description = "员工亲属实体关联Excel导入导出对象")
|
||||
public class CcdiStaffEnterpriseRelationExcel implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 身份证号 */
|
||||
@ExcelProperty(value = "身份证号", index = 0)
|
||||
/** 亲属身份证号 */
|
||||
@ExcelProperty(value = "亲属身份证号", index = 0)
|
||||
@ColumnWidth(20)
|
||||
@Required
|
||||
@Schema(description = "身份证号")
|
||||
@Schema(description = "亲属身份证号")
|
||||
private String personId;
|
||||
|
||||
/** 统一社会信用代码 */
|
||||
|
||||
@@ -15,6 +15,14 @@ import java.math.BigDecimal;
|
||||
@Schema(description = "亲属资产信息导入失败记录")
|
||||
public class AssetImportFailureVO {
|
||||
|
||||
/** Sheet名称 */
|
||||
@Schema(description = "Sheet名称")
|
||||
private String sheetName;
|
||||
|
||||
/** Excel行号 */
|
||||
@Schema(description = "Excel行号")
|
||||
private Integer rowNum;
|
||||
|
||||
/** 亲属证件号 */
|
||||
@Schema(description = "亲属证件号")
|
||||
private String personId;
|
||||
|
||||
@@ -15,6 +15,14 @@ import java.math.BigDecimal;
|
||||
@Schema(description = "员工资产信息导入失败记录")
|
||||
public class BaseStaffAssetImportFailureVO {
|
||||
|
||||
/** Sheet名称 */
|
||||
@Schema(description = "Sheet名称")
|
||||
private String sheetName;
|
||||
|
||||
/** Excel行号 */
|
||||
@Schema(description = "Excel行号")
|
||||
private Integer rowNum;
|
||||
|
||||
/** 员工身份证号 */
|
||||
@Schema(description = "员工身份证号")
|
||||
private String personId;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -8,15 +8,16 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 采购交易信息VO
|
||||
* 招投标信息VO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-06
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "采购交易信息")
|
||||
@Schema(description = "招投标信息")
|
||||
public class CcdiPurchaseTransactionVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
@@ -90,6 +91,14 @@ public class CcdiPurchaseTransactionVO implements Serializable {
|
||||
@Schema(description = "供应商银行账户")
|
||||
private String supplierBankAccount;
|
||||
|
||||
/** 参与供应商数 */
|
||||
@Schema(description = "参与供应商数")
|
||||
private Integer supplierCount;
|
||||
|
||||
/** 供应商明细 */
|
||||
@Schema(description = "供应商明细列表")
|
||||
private List<CcdiPurchaseTransactionSupplierVO> supplierList;
|
||||
|
||||
/** 采购申请日期(或立项日期) */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@Schema(description = "采购申请日期")
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息VO
|
||||
* 员工亲属实体关联VO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "员工实体关系信息")
|
||||
@Schema(description = "员工亲属实体关联")
|
||||
public class CcdiStaffEnterpriseRelationVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
@@ -25,13 +25,21 @@ public class CcdiStaffEnterpriseRelationVO implements Serializable {
|
||||
@Schema(description = "主键ID")
|
||||
private Long id;
|
||||
|
||||
/** 身份证号 */
|
||||
@Schema(description = "身份证号")
|
||||
/** 亲属身份证号 */
|
||||
@Schema(description = "亲属身份证号")
|
||||
private String personId;
|
||||
|
||||
/** 员工姓名 */
|
||||
@Schema(description = "员工姓名")
|
||||
private String personName;
|
||||
/** 亲属姓名 */
|
||||
@Schema(description = "亲属姓名")
|
||||
private String relationName;
|
||||
|
||||
/** 关联员工身份证号 */
|
||||
@Schema(description = "关联员工身份证号")
|
||||
private String staffPersonId;
|
||||
|
||||
/** 关联员工姓名 */
|
||||
@Schema(description = "关联员工姓名")
|
||||
private String staffPersonName;
|
||||
|
||||
/** 关联人在企业的职务 */
|
||||
@Schema(description = "关联人在企业的职务")
|
||||
|
||||
@@ -14,8 +14,14 @@ import java.math.BigDecimal;
|
||||
@Schema(description = "导入失败记录")
|
||||
public class ImportFailureVO {
|
||||
|
||||
@Schema(description = "Sheet名称")
|
||||
private String sheetName;
|
||||
|
||||
@Schema(description = "Excel行号")
|
||||
private Integer rowNum;
|
||||
|
||||
@Schema(description = "柜员号")
|
||||
private Long employeeId;
|
||||
private Long staffId;
|
||||
|
||||
@Schema(description = "姓名")
|
||||
private String name;
|
||||
|
||||
@@ -6,15 +6,23 @@ import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 采购交易信息导入失败记录VO
|
||||
* 招投标信息导入失败记录VO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-06
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "采购交易信息导入失败记录")
|
||||
@Schema(description = "招投标信息导入失败记录")
|
||||
public class PurchaseTransactionImportFailureVO {
|
||||
|
||||
/** 失败来源Sheet */
|
||||
@Schema(description = "失败来源Sheet")
|
||||
private String sheetName;
|
||||
|
||||
/** 失败行号 */
|
||||
@Schema(description = "失败行号")
|
||||
private String sheetRowNum;
|
||||
|
||||
/** 采购事项ID */
|
||||
@Schema(description = "采购事项ID")
|
||||
private String purchaseId;
|
||||
|
||||
@@ -13,6 +13,12 @@ import lombok.Data;
|
||||
@Schema(description = "招聘信息导入失败记录")
|
||||
public class RecruitmentImportFailureVO {
|
||||
|
||||
@Schema(description = "失败Sheet")
|
||||
private String sheetName;
|
||||
|
||||
@Schema(description = "失败行号")
|
||||
private String sheetRowNum;
|
||||
|
||||
@Schema(description = "招聘项目编号")
|
||||
private String recruitId;
|
||||
|
||||
|
||||
@@ -7,22 +7,26 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息导入失败记录VO
|
||||
* 员工亲属实体关联导入失败记录VO
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "员工实体关系信息导入失败记录")
|
||||
@Schema(description = "员工亲属实体关联导入失败记录")
|
||||
public class StaffEnterpriseRelationImportFailureVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 身份证号 */
|
||||
@Schema(description = "身份证号")
|
||||
/** 亲属身份证号 */
|
||||
@Schema(description = "亲属身份证号")
|
||||
private String personId;
|
||||
|
||||
/** 亲属姓名 */
|
||||
@Schema(description = "亲属姓名")
|
||||
private String relationName;
|
||||
|
||||
/** 统一社会信用代码 */
|
||||
@Schema(description = "统一社会信用代码")
|
||||
private String socialCreditCode;
|
||||
|
||||
@@ -15,6 +15,14 @@ import java.math.BigDecimal;
|
||||
@Schema(description = "员工亲属关系信息导入失败记录")
|
||||
public class StaffFmyRelationImportFailureVO {
|
||||
|
||||
/** Sheet名称 */
|
||||
@Schema(description = "Sheet名称")
|
||||
private String sheetName;
|
||||
|
||||
/** Excel行号 */
|
||||
@Schema(description = "Excel行号")
|
||||
private Integer rowNum;
|
||||
|
||||
/** 员工身份证号 */
|
||||
@Schema(description = "员工身份证号")
|
||||
private String personId;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -35,6 +35,14 @@ public interface CcdiPurchaseTransactionMapper extends BaseMapper<CcdiPurchaseTr
|
||||
*/
|
||||
CcdiPurchaseTransactionVO selectTransactionById(@Param("purchaseId") String purchaseId);
|
||||
|
||||
/**
|
||||
* 删除指定采购事项ID对应的供应商明细
|
||||
*
|
||||
* @param purchaseIds 采购事项ID列表
|
||||
* @return 删除行数
|
||||
*/
|
||||
int deleteSuppliersByPurchaseIds(@Param("purchaseIds") List<String> purchaseIds);
|
||||
|
||||
/**
|
||||
* 批量插入采购交易数据
|
||||
*
|
||||
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
|
||||
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 org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -38,6 +39,14 @@ public interface CcdiStaffEnterpriseRelationMapper extends BaseMapper<CcdiStaffE
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 根据亲属身份证号批量置无效
|
||||
*
|
||||
* @param personId 亲属身份证号
|
||||
* @return 影响行数
|
||||
*/
|
||||
int invalidateByFamilyCertNo(@Param("personId") String personId);
|
||||
|
||||
/**
|
||||
* 批量插入员工实体关系数据
|
||||
*
|
||||
|
||||
@@ -15,10 +15,9 @@ public interface ICcdiBaseStaffImportService {
|
||||
/**
|
||||
* 异步导入员工数据
|
||||
*
|
||||
* @param excelList Excel数据列表
|
||||
* @param isUpdateSupport 是否更新已存在的数据
|
||||
* @param excelList Excel数据列表
|
||||
*/
|
||||
void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport, String taskId);
|
||||
void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, String taskId);
|
||||
|
||||
/**
|
||||
* 查询导入状态
|
||||
|
||||
@@ -78,11 +78,10 @@ public interface ICcdiBaseStaffService {
|
||||
/**
|
||||
* 导入员工数据
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持
|
||||
* @param excelList Excel实体列表
|
||||
* @return 结果
|
||||
*/
|
||||
String importBaseStaff(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport);
|
||||
String importBaseStaff(List<CcdiBaseStaffExcel> excelList);
|
||||
|
||||
/**
|
||||
* 查询员工下拉列表
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
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.ImportStatusVO;
|
||||
|
||||
@@ -17,11 +18,17 @@ public interface ICcdiPurchaseTransactionImportService {
|
||||
/**
|
||||
* 异步导入采购交易数据
|
||||
*
|
||||
* @param excelList Excel数据列表
|
||||
* @param taskId 任务ID
|
||||
* @param userName 当前用户名
|
||||
* @param mainExcelList 主信息Excel数据列表
|
||||
* @param supplierExcelList 供应商明细Excel数据列表
|
||||
* @param taskId 任务ID
|
||||
* @param userName 当前用户名
|
||||
*/
|
||||
void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList, String taskId, String userName);
|
||||
void importTransactionAsync(
|
||||
List<CcdiPurchaseTransactionExcel> mainExcelList,
|
||||
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList,
|
||||
String taskId,
|
||||
String userName
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询导入状态
|
||||
|
||||
@@ -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.CcdiPurchaseTransactionQueryDTO;
|
||||
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 java.util.List;
|
||||
@@ -77,8 +78,12 @@ public interface ICcdiPurchaseTransactionService {
|
||||
/**
|
||||
* 导入采购交易数据(异步)
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param mainExcelList 主信息Excel实体列表
|
||||
* @param supplierExcelList 供应商明细Excel实体列表
|
||||
* @return 任务ID
|
||||
*/
|
||||
String importTransaction(List<CcdiPurchaseTransactionExcel> excelList);
|
||||
String importTransaction(
|
||||
List<CcdiPurchaseTransactionExcel> mainExcelList,
|
||||
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureV
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息异步导入服务层
|
||||
* 员工亲属实体关联异步导入服务层
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
|
||||
@@ -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.CcdiStaffEnterpriseRelationQueryDTO;
|
||||
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 java.util.List;
|
||||
@@ -42,6 +43,14 @@ public interface ICcdiStaffEnterpriseRelationService {
|
||||
*/
|
||||
List<CcdiStaffEnterpriseRelationExcel> selectRelationListForExport(CcdiStaffEnterpriseRelationQueryDTO queryDTO);
|
||||
|
||||
/**
|
||||
* 查询有效员工亲属下拉选项
|
||||
*
|
||||
* @param query 搜索关键词
|
||||
* @return 下拉选项
|
||||
*/
|
||||
List<CcdiStaffEnterpriseRelationOptionVO> selectFamilyOptions(String query);
|
||||
|
||||
/**
|
||||
* 查询员工实体关系详情
|
||||
*
|
||||
|
||||
@@ -22,21 +22,11 @@ public interface ICcdiStaffRecruitmentImportService {
|
||||
* @param taskId 任务ID
|
||||
* @param userName 用户名
|
||||
*/
|
||||
void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList,
|
||||
void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> recruitmentList,
|
||||
List<CcdiStaffRecruitmentWorkExcel> workList,
|
||||
String taskId,
|
||||
String userName);
|
||||
|
||||
/**
|
||||
* 异步导入招聘记录历史工作经历数据
|
||||
*
|
||||
* @param excelList Excel数据列表
|
||||
* @param taskId 任务ID
|
||||
* @param userName 用户名
|
||||
*/
|
||||
void importRecruitmentWorkAsync(List<CcdiStaffRecruitmentWorkExcel> excelList,
|
||||
String taskId,
|
||||
String userName);
|
||||
|
||||
/**
|
||||
* 查询导入状态
|
||||
*
|
||||
|
||||
@@ -81,13 +81,6 @@ public interface ICcdiStaffRecruitmentService {
|
||||
* @param excelList Excel实体列表
|
||||
* @return 结果
|
||||
*/
|
||||
String importRecruitment(List<CcdiStaffRecruitmentExcel> excelList);
|
||||
|
||||
/**
|
||||
* 导入招聘记录历史工作经历数据(异步)
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @return 任务ID
|
||||
*/
|
||||
String importRecruitmentWork(List<CcdiStaffRecruitmentWorkExcel> excelList);
|
||||
String importRecruitment(List<CcdiStaffRecruitmentExcel> recruitmentList,
|
||||
List<CcdiStaffRecruitmentWorkExcel> workList);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ import java.util.concurrent.TimeUnit;
|
||||
public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportService {
|
||||
|
||||
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
|
||||
private CcdiAssetInfoMapper assetInfoMapper;
|
||||
@@ -91,7 +93,8 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
|
||||
|
||||
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 {
|
||||
validateExcel(excel);
|
||||
Set<String> familyIds = ownerMap.get(excel.getPersonId());
|
||||
@@ -111,6 +114,8 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
|
||||
} catch (Exception e) {
|
||||
AssetImportFailureVO failureVO = new AssetImportFailureVO();
|
||||
BeanUtils.copyProperties(excel, failureVO);
|
||||
failureVO.setSheetName(SHEET_NAME);
|
||||
failureVO.setRowNum(i + EXCEL_DATA_START_ROW);
|
||||
failureVO.setErrorMessage(e.getMessage());
|
||||
failures.add(failureVO);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.info.collection.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.info.collection.domain.CcdiAssetInfo;
|
||||
@@ -90,14 +91,24 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
|
||||
.toList();
|
||||
|
||||
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 {
|
||||
validateExcel(excel);
|
||||
Set<String> familyIds = ownerMap.get(excel.getPersonId());
|
||||
if (familyIds == null || familyIds.isEmpty()) {
|
||||
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();
|
||||
BeanUtils.copyProperties(excel, assetInfo);
|
||||
@@ -109,6 +120,8 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
|
||||
} catch (Exception e) {
|
||||
BaseStaffAssetImportFailureVO failureVO = new BaseStaffAssetImportFailureVO();
|
||||
BeanUtils.copyProperties(excel, failureVO);
|
||||
failureVO.setSheetName("员工资产信息");
|
||||
failureVO.setRowNum(i + 2);
|
||||
failureVO.setErrorMessage(e.getMessage());
|
||||
failures.add(failureVO);
|
||||
}
|
||||
@@ -168,6 +181,18 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
|
||||
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) {
|
||||
if (mappings == null) {
|
||||
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) {
|
||||
Map<String, Object> statusData = new HashMap<>();
|
||||
statusData.put("status", status);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
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.dto.CcdiBaseStaffAddDTO;
|
||||
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.common.utils.IdCardUtil;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -43,16 +45,18 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Resource
|
||||
private SysDeptMapper deptMapper;
|
||||
|
||||
@Override
|
||||
@Async
|
||||
public void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport, String taskId) {
|
||||
public void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, String taskId) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 记录导入开始
|
||||
ImportLogUtils.logImportStart(log, taskId, "员工基础信息", excelList.size(), "系统");
|
||||
|
||||
List<CcdiBaseStaff> newRecords = new ArrayList<>();
|
||||
List<CcdiBaseStaff> updateRecords = new ArrayList<>();
|
||||
List<ImportFailureVO> failures = new ArrayList<>();
|
||||
|
||||
// 批量查询已存在的员工ID和身份证号
|
||||
@@ -75,13 +79,12 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
CcdiBaseStaffAddDTO addDTO = new CcdiBaseStaffAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
|
||||
// 验证数据(支持更新模式)
|
||||
validateStaffData(addDTO, isUpdateSupport, existingIds, existingIdCards);
|
||||
validateStaffData(addDTO, existingIds, existingIdCards);
|
||||
|
||||
CcdiBaseStaff staff = new CcdiBaseStaff();
|
||||
BeanUtils.copyProperties(excel, staff);
|
||||
|
||||
// 统一检查Excel内重复(更新和新增两个分支都需要检查)
|
||||
// 统一检查Excel内重复
|
||||
if (processedStaffIds.contains(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()));
|
||||
}
|
||||
|
||||
// 检查员工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) {
|
||||
@@ -115,11 +105,13 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
|
||||
// 记录进度
|
||||
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
|
||||
newRecords.size() + updateRecords.size(), failures.size());
|
||||
newRecords.size(), failures.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
ImportFailureVO failure = new ImportFailureVO();
|
||||
BeanUtils.copyProperties(excel, failure);
|
||||
failure.setSheetName("员工信息");
|
||||
failure.setRowNum(i + 2);
|
||||
failure.setErrorMessage(e.getMessage());
|
||||
failures.add(failure);
|
||||
|
||||
@@ -137,13 +129,6 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
saveBatch(newRecords, 500);
|
||||
}
|
||||
|
||||
// 批量更新已有数据(先删除再插入)
|
||||
if (!updateRecords.isEmpty() && isUpdateSupport) {
|
||||
ImportLogUtils.logBatchOperationStart(log, taskId, "更新",
|
||||
(updateRecords.size() + 499) / 500, 500);
|
||||
baseStaffMapper.insertOrUpdateBatch(updateRecords);
|
||||
}
|
||||
|
||||
// 保存失败记录到Redis
|
||||
if (!failures.isEmpty()) {
|
||||
try {
|
||||
@@ -157,7 +142,7 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
|
||||
ImportResult result = new ImportResult();
|
||||
result.setTotalCount(excelList.size());
|
||||
result.setSuccessCount(newRecords.size() + updateRecords.size());
|
||||
result.setSuccessCount(newRecords.size());
|
||||
result.setFailureCount(failures.size());
|
||||
|
||||
// 更新最终状态
|
||||
@@ -299,11 +284,10 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
* 验证员工数据
|
||||
*
|
||||
* @param addDTO 新增DTO
|
||||
* @param isUpdateSupport 是否支持更新
|
||||
* @param existingIds 已存在的员工ID集合(导入场景使用,传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())) {
|
||||
throw new RuntimeException("姓名不能为空");
|
||||
@@ -326,6 +310,7 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
if (StringUtils.isEmpty(addDTO.getStatus())) {
|
||||
throw new RuntimeException("状态不能为空");
|
||||
}
|
||||
validateDeptId(addDTO.getDeptId());
|
||||
|
||||
// 验证身份证号格式
|
||||
String idCardError = IdCardUtil.getErrorMessage(addDTO.getIdCard());
|
||||
@@ -347,12 +332,11 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
throw new RuntimeException("该身份证号已存在");
|
||||
}
|
||||
} else {
|
||||
// 导入场景:如果员工ID不存在,才检查身份证号唯一性
|
||||
if (!existingIds.contains(addDTO.getStaffId())) {
|
||||
// 使用批量查询的结果检查身份证号唯一性
|
||||
if (existingIdCards != null && existingIdCards.contains(addDTO.getIdCard())) {
|
||||
throw new RuntimeException("该身份证号已存在");
|
||||
}
|
||||
if (existingIds.contains(addDTO.getStaffId())) {
|
||||
throw new RuntimeException("该员工ID已存在");
|
||||
}
|
||||
if (existingIdCards != null && existingIdCards.contains(addDTO.getIdCard())) {
|
||||
throw new RuntimeException("该身份证号已存在");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,4 +362,11 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,13 +211,12 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
|
||||
/**
|
||||
* 导入员工数据
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持
|
||||
* @param excelList Excel实体列表
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public String importBaseStaff(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport) {
|
||||
public String importBaseStaff(List<CcdiBaseStaffExcel> excelList) {
|
||||
String taskId = UUID.randomUUID().toString();
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
@@ -236,7 +235,7 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
|
||||
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
||||
redisTemplate.expire(statusKey, 7, java.util.concurrent.TimeUnit.DAYS);
|
||||
|
||||
importAsyncService.importBaseStaffAsync(excelList, isUpdateSupport, taskId);
|
||||
importAsyncService.importBaseStaffAsync(excelList, taskId);
|
||||
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@@ -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}$")) {
|
||||
throw new RuntimeException("统一社会信用代码格式不正确");
|
||||
}
|
||||
if (StringUtils.isEmpty(excel.getStatus())) {
|
||||
throw new RuntimeException("经营状态不能为空");
|
||||
}
|
||||
|
||||
String riskLevel = EnterpriseRiskLevel.resolveCode(StringUtils.trim(excel.getRiskLevel()));
|
||||
if (riskLevel == null) {
|
||||
throw new RuntimeException("风险等级不在允许范围内");
|
||||
@@ -143,10 +139,6 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
|
||||
if (entSource == null) {
|
||||
throw new RuntimeException("企业来源不在允许范围内");
|
||||
}
|
||||
String dataSource = resolveDataSourceCode(StringUtils.trim(excel.getDataSource()));
|
||||
if (dataSource == null) {
|
||||
throw new RuntimeException("数据来源不在允许范围内");
|
||||
}
|
||||
|
||||
if (existingCreditCodes.contains(excel.getSocialCreditCode())) {
|
||||
throw new RuntimeException(String.format("统一社会信用代码[%s]已存在,请勿重复导入", excel.getSocialCreditCode()));
|
||||
@@ -159,8 +151,8 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
|
||||
BeanUtils.copyProperties(excel, entity);
|
||||
entity.setRiskLevel(riskLevel);
|
||||
entity.setEntSource(entSource);
|
||||
entity.setDataSource(dataSource);
|
||||
entity.setStatus(StringUtils.trim(excel.getStatus()));
|
||||
entity.setDataSource(DataSource.IMPORT.getCode());
|
||||
entity.setStatus(trimToNull(excel.getStatus()));
|
||||
entity.setCreatedBy(userName);
|
||||
entity.setUpdatedBy(userName);
|
||||
return entity;
|
||||
@@ -206,15 +198,6 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
|
||||
redisTemplate.opsForHash().putAll(buildStatusKey(taskId), statusData);
|
||||
}
|
||||
|
||||
private String resolveDataSourceCode(String value) {
|
||||
for (DataSource source : DataSource.values()) {
|
||||
if (source.getCode().equals(value) || source.getDesc().equals(value)) {
|
||||
return source.getCode();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String buildStatusKey(String taskId) {
|
||||
return "import:enterpriseBaseInfo:" + taskId;
|
||||
}
|
||||
@@ -222,4 +205,11 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
|
||||
private String buildFailuresKey(String taskId) {
|
||||
return "import:enterpriseBaseInfo:" + taskId + ":failures";
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +85,12 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
||||
if (enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode()) != null) {
|
||||
throw new RuntimeException("该统一社会信用代码已存在");
|
||||
}
|
||||
validateEnumFields(addDTO.getStatus(), addDTO.getRiskLevel(), addDTO.getEntSource(), addDTO.getDataSource());
|
||||
validateRiskLevelAndEnterpriseSource(addDTO.getRiskLevel(), addDTO.getEntSource());
|
||||
|
||||
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
|
||||
BeanUtils.copyProperties(addDTO, entity);
|
||||
entity.setStatus(trimToNull(addDTO.getStatus()));
|
||||
entity.setDataSource(DataSource.MANUAL.getCode());
|
||||
return enterpriseBaseInfoMapper.insert(entity);
|
||||
}
|
||||
|
||||
@@ -103,6 +105,8 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
||||
|
||||
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
|
||||
BeanUtils.copyProperties(editDTO, entity);
|
||||
entity.setStatus(trimToNull(editDTO.getStatus()));
|
||||
entity.setDataSource(existing.getDataSource());
|
||||
return enterpriseBaseInfoMapper.updateById(entity);
|
||||
}
|
||||
|
||||
@@ -176,18 +180,22 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
||||
}
|
||||
|
||||
private void validateEnumFields(String status, String riskLevel, String entSource, String dataSource) {
|
||||
if (StringUtils.isEmpty(status)) {
|
||||
throw new RuntimeException("经营状态不能为空");
|
||||
validateRiskLevelAndEnterpriseSource(riskLevel, entSource);
|
||||
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)) {
|
||||
throw new RuntimeException("风险等级不在允许范围内");
|
||||
}
|
||||
if (!EnterpriseSource.contains(entSource)) {
|
||||
throw new RuntimeException("企业来源不在允许范围内");
|
||||
}
|
||||
if (!containsDataSource(dataSource)) {
|
||||
throw new RuntimeException("数据来源不在允许范围内");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsDataSource(String code) {
|
||||
@@ -199,6 +207,13 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf
|
||||
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>()
|
||||
|
||||
@@ -2,12 +2,15 @@ package com.ruoyi.info.collection.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
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.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.ImportStatusVO;
|
||||
import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO;
|
||||
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.utils.ImportLogUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
@@ -37,83 +40,155 @@ import java.util.stream.Collectors;
|
||||
public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTransactionImportService {
|
||||
|
||||
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
|
||||
private CcdiPurchaseTransactionMapper transactionMapper;
|
||||
|
||||
@Resource
|
||||
private CcdiPurchaseTransactionSupplierMapper supplierMapper;
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
@Async
|
||||
@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();
|
||||
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<>();
|
||||
|
||||
// 批量查询已存在的采购事项ID
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", excelList.size());
|
||||
Set<String> existingIds = getExistingPurchaseIds(excelList);
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的采购事项ID", safeMainList.size());
|
||||
Set<String> existingIds = getExistingPurchaseIds(safeMainList);
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "采购事项ID", existingIds.size());
|
||||
|
||||
// 用于跟踪Excel文件内已处理的采购事项ID
|
||||
Set<String> processedIds = new HashSet<>();
|
||||
Map<String, List<MainImportRow>> mainGroupMap = indexedMainRows.stream()
|
||||
.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 (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiPurchaseTransactionExcel excel = excelList.get(i);
|
||||
for (SupplierImportRow supplierExcel : indexedSupplierRows) {
|
||||
if (StringUtils.isEmpty(supplierExcel.data().getPurchaseId())) {
|
||||
failures.add(buildFailure(
|
||||
null,
|
||||
null,
|
||||
SUPPLIER_SHEET_NAME,
|
||||
String.valueOf(supplierExcel.sheetRowNum()),
|
||||
"供应商明细Sheet中的采购事项ID不能为空"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// 转换为AddDTO进行验证
|
||||
CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
|
||||
// 验证数据
|
||||
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()); // 标记为已处理
|
||||
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(),
|
||||
newRecords.size(), failures.size());
|
||||
ImportLogUtils.logProgress(log, taskId, index, Math.max(totalCount, purchaseIds.size()),
|
||||
newTransactions.size(), failures.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
PurchaseTransactionImportFailureVO failure = new PurchaseTransactionImportFailureVO();
|
||||
BeanUtils.copyProperties(excel, failure);
|
||||
failure.setErrorMessage(e.getMessage());
|
||||
failures.add(failure);
|
||||
MainImportRow mainRow = mainRows.isEmpty() ? null : mainRows.getFirst();
|
||||
CcdiPurchaseTransactionExcel mainExcel = mainRow == null ? null : mainRow.data();
|
||||
FailureMeta failureMeta = resolveFailureMeta(e, mainRows, supplierRows);
|
||||
failures.add(buildFailure(
|
||||
mainExcel,
|
||||
purchaseId,
|
||||
failureMeta.sheetName(),
|
||||
failureMeta.sheetRowNum(),
|
||||
e.getMessage()
|
||||
));
|
||||
|
||||
// 记录验证失败日志
|
||||
String keyData = String.format("采购事项ID=%s, 采购类别=%s, 标的物=%s",
|
||||
excel.getPurchaseId(), excel.getPurchaseCategory(), excel.getSubjectName());
|
||||
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
|
||||
purchaseId,
|
||||
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, "插入",
|
||||
(newRecords.size() + 499) / 500, 500);
|
||||
saveBatch(newRecords, 500);
|
||||
(newTransactions.size() + 499) / 500, 500);
|
||||
saveBatch(newTransactions, 500);
|
||||
}
|
||||
if (!newSuppliers.isEmpty()) {
|
||||
saveSupplierBatch(newSuppliers, 500);
|
||||
}
|
||||
|
||||
// 保存失败记录到Redis
|
||||
@@ -128,8 +203,8 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
|
||||
}
|
||||
|
||||
ImportResult result = new ImportResult();
|
||||
result.setTotalCount(excelList.size());
|
||||
result.setSuccessCount(newRecords.size());
|
||||
result.setTotalCount(totalCount);
|
||||
result.setSuccessCount(newTransactions.size());
|
||||
result.setFailureCount(failures.size());
|
||||
|
||||
// 更新最终状态
|
||||
@@ -138,8 +213,8 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
|
||||
|
||||
// 记录导入完成
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
ImportLogUtils.logImportComplete(log, taskId, "采购交易信息",
|
||||
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
|
||||
ImportLogUtils.logImportComplete(log, taskId, "招投标信息维护",
|
||||
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 existingIds 已存在的采购事项ID集合
|
||||
* @param addDTO 新增DTO
|
||||
*/
|
||||
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, Set<String> existingIds) {
|
||||
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, int sheetRowNum) {
|
||||
// 验证必填字段
|
||||
if (StringUtils.isEmpty(addDTO.getPurchaseId())) {
|
||||
throw new RuntimeException("采购事项ID不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购事项ID不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getPurchaseCategory())) {
|
||||
throw new RuntimeException("采购类别不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购类别不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getSubjectName())) {
|
||||
throw new RuntimeException("标的物名称不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "标的物名称不能为空");
|
||||
}
|
||||
if (addDTO.getPurchaseQty() == null) {
|
||||
throw new RuntimeException("采购数量不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购数量不能为空");
|
||||
}
|
||||
if (addDTO.getBudgetAmount() == null) {
|
||||
throw new RuntimeException("预算金额不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "预算金额不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getPurchaseMethod())) {
|
||||
throw new RuntimeException("采购方式不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购方式不能为空");
|
||||
}
|
||||
if (addDTO.getApplyDate() == null) {
|
||||
throw new RuntimeException("采购申请日期不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购申请日期不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getApplicantId())) {
|
||||
throw new RuntimeException("申请人工号不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请人工号不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getApplicantName())) {
|
||||
throw new RuntimeException("申请人姓名不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请人姓名不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getApplyDepartment())) {
|
||||
throw new RuntimeException("申请部门不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "申请部门不能为空");
|
||||
}
|
||||
|
||||
// 验证工号格式(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}$")) {
|
||||
throw new RuntimeException("采购负责人工号必须为7位数字");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "采购负责人工号必须为7位数字");
|
||||
}
|
||||
|
||||
// 验证金额非负
|
||||
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) {
|
||||
throw new RuntimeException("预算金额必须大于0");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "预算金额必须大于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) {
|
||||
throw new RuntimeException("实际采购金额必须大于0");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "实际采购金额必须大于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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,34 @@ package com.ruoyi.info.collection.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
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.CcdiPurchaseTransactionEditDTO;
|
||||
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.CcdiPurchaseTransactionSupplierExcel;
|
||||
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.CcdiPurchaseTransactionSupplierMapper;
|
||||
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
|
||||
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionService;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -39,6 +49,9 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
||||
@Resource
|
||||
private ICcdiPurchaseTransactionImportService transactionImportService;
|
||||
|
||||
@Resource
|
||||
private CcdiPurchaseTransactionSupplierMapper supplierMapper;
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@@ -93,7 +106,14 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
||||
*/
|
||||
@Override
|
||||
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已存在");
|
||||
}
|
||||
|
||||
List<CcdiPurchaseTransactionSupplier> supplierList = buildSupplierEntities(addDTO.getPurchaseId(), addDTO.getSupplierList());
|
||||
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
|
||||
BeanUtils.copyProperties(addDTO, transaction);
|
||||
fillWinnerSummary(transaction, supplierList);
|
||||
int result = transactionMapper.insert(transaction);
|
||||
saveSuppliers(supplierList);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -126,9 +149,13 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
||||
@Override
|
||||
@Transactional
|
||||
public int updateTransaction(CcdiPurchaseTransactionEditDTO editDTO) {
|
||||
List<CcdiPurchaseTransactionSupplier> supplierList = buildSupplierEntities(editDTO.getPurchaseId(), editDTO.getSupplierList());
|
||||
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
|
||||
BeanUtils.copyProperties(editDTO, transaction);
|
||||
fillWinnerSummary(transaction, supplierList);
|
||||
int result = transactionMapper.updateById(transaction);
|
||||
transactionMapper.deleteSuppliersByPurchaseIds(List.of(editDTO.getPurchaseId()));
|
||||
saveSuppliers(supplierList);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -142,19 +169,24 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
||||
@Override
|
||||
@Transactional
|
||||
public int deleteTransactionByIds(String[] purchaseIds) {
|
||||
transactionMapper.deleteSuppliersByPurchaseIds(List.of(purchaseIds));
|
||||
return transactionMapper.deleteBatchIds(java.util.List.of(purchaseIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入采购交易数据(异步)
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param mainExcelList 主信息Excel实体列表
|
||||
* @param supplierExcelList 供应商明细Excel实体列表
|
||||
* @return 任务ID
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public String importTransaction(java.util.List<CcdiPurchaseTransactionExcel> excelList) {
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
public String importTransaction(
|
||||
List<CcdiPurchaseTransactionExcel> mainExcelList,
|
||||
List<CcdiPurchaseTransactionSupplierExcel> supplierExcelList
|
||||
) {
|
||||
if ((mainExcelList == null || mainExcelList.isEmpty()) && (supplierExcelList == null || supplierExcelList.isEmpty())) {
|
||||
throw new RuntimeException("至少需要一条数据");
|
||||
}
|
||||
|
||||
@@ -170,7 +202,7 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
||||
Map<String, Object> statusData = new HashMap<>();
|
||||
statusData.put("taskId", taskId);
|
||||
statusData.put("status", "PROCESSING");
|
||||
statusData.put("totalCount", excelList.size());
|
||||
statusData.put("totalCount", countImportUnits(mainExcelList, supplierExcelList));
|
||||
statusData.put("successCount", 0);
|
||||
statusData.put("failureCount", 0);
|
||||
statusData.put("progress", 0);
|
||||
@@ -181,8 +213,134 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
||||
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
||||
|
||||
// 调用异步导入服务
|
||||
transactionImportService.importTransactionAsync(excelList, taskId, userName);
|
||||
transactionImportService.importTransactionAsync(mainExcelList, supplierExcelList, taskId, userName);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ 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.CcdiBaseStaff;
|
||||
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.excel.CcdiStaffEnterpriseRelationExcel;
|
||||
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||
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.service.ICcdiStaffEnterpriseRelationImportService;
|
||||
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
||||
@@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 员工实体关系信息异步导入服务层处理
|
||||
* 员工亲属实体关联异步导入服务层处理
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-02-09
|
||||
@@ -47,7 +47,7 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Resource
|
||||
private CcdiBaseStaffMapper baseStaffMapper;
|
||||
private CcdiStaffFmyRelationMapper familyRelationMapper;
|
||||
|
||||
@Override
|
||||
@Async
|
||||
@@ -56,37 +56,48 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 记录导入开始
|
||||
ImportLogUtils.logImportStart(log, taskId, "员工实体关系", excelList.size(), userName);
|
||||
ImportLogUtils.logImportStart(log, taskId, "员工亲属实体关联", excelList.size(), userName);
|
||||
|
||||
List<CcdiStaffEnterpriseRelation> newRecords = new ArrayList<>();
|
||||
List<StaffEnterpriseRelationImportFailureVO> failures = new ArrayList<>();
|
||||
|
||||
// 批量验证员工身份证号是否存在
|
||||
Set<String> excelPersonIds = excelList.stream()
|
||||
.map(CcdiStaffEnterpriseRelationExcel::getPersonId)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.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()) {
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "员工身份证号", excelPersonIds.size());
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "员工亲属关系", excelPersonIds.size());
|
||||
|
||||
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(CcdiBaseStaff::getIdCard)
|
||||
.in(CcdiBaseStaff::getIdCard, excelPersonIds);
|
||||
LambdaQueryWrapper<CcdiStaffFmyRelation> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.select(
|
||||
CcdiStaffFmyRelation::getRelationCertNo,
|
||||
CcdiStaffFmyRelation::getRelationName,
|
||||
CcdiStaffFmyRelation::getPersonId,
|
||||
CcdiStaffFmyRelation::getStatus,
|
||||
CcdiStaffFmyRelation::getIsEmpFamily
|
||||
)
|
||||
.in(CcdiStaffFmyRelation::getRelationCertNo, excelPersonIds);
|
||||
|
||||
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper);
|
||||
existingPersonIds = existingStaff.stream()
|
||||
.map(CcdiBaseStaff::getIdCard)
|
||||
.collect(Collectors.toSet());
|
||||
List<CcdiStaffFmyRelation> familyRelations = familyRelationMapper.selectList(wrapper);
|
||||
for (CcdiStaffFmyRelation familyRelation : familyRelations) {
|
||||
knownFamilyCertNos.add(familyRelation.getRelationCertNo());
|
||||
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组合
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的员工企业关系组合", excelList.size());
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的员工亲属实体关联组合", excelList.size());
|
||||
Set<String> existingCombinations = getExistingCombinations(excelList);
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工企业关系组合", existingCombinations.size());
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工亲属实体关联组合", existingCombinations.size());
|
||||
|
||||
// 用于跟踪Excel文件内已处理的组合
|
||||
Set<String> processedCombinations = new HashSet<>();
|
||||
@@ -103,41 +114,18 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
// 验证数据
|
||||
validateRelationData(addDTO);
|
||||
|
||||
// 身份证号存在性检查(在基本验证之后)
|
||||
if (!existingPersonIds.contains(excel.getPersonId())) {
|
||||
throw new RuntimeException(String.format(
|
||||
"第%d行: 身份证号[%s]不存在于员工信息表中,请先添加员工信息",
|
||||
i + 1, excel.getPersonId()));
|
||||
}
|
||||
CcdiStaffFmyRelation familyRelation = validFamilies.get(excel.getPersonId());
|
||||
CcdiStaffEnterpriseRelation relation = validateAndBuildEntity(
|
||||
excel,
|
||||
familyRelation,
|
||||
knownFamilyCertNos,
|
||||
existingCombinations,
|
||||
processedCombinations,
|
||||
userName
|
||||
);
|
||||
|
||||
String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode();
|
||||
|
||||
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);
|
||||
processedCombinations.add(combination); // 标记为已处理
|
||||
}
|
||||
newRecords.add(relation);
|
||||
processedCombinations.add(excel.getPersonId() + "|" + excel.getSocialCreditCode());
|
||||
|
||||
// 记录进度
|
||||
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
|
||||
@@ -146,11 +134,12 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
} catch (Exception e) {
|
||||
StaffEnterpriseRelationImportFailureVO failure = new StaffEnterpriseRelationImportFailureVO();
|
||||
BeanUtils.copyProperties(excel, failure);
|
||||
failure.setRelationName(familyNameMap.get(excel.getPersonId()));
|
||||
failure.setErrorMessage(e.getMessage());
|
||||
failures.add(failure);
|
||||
|
||||
// 记录验证失败日志
|
||||
String keyData = String.format("身份证号=%s, 统一社会信用代码=%s, 企业名称=%s",
|
||||
String keyData = String.format("亲属身份证号=%s, 统一社会信用代码=%s, 企业名称=%s",
|
||||
excel.getPersonId(), excel.getSocialCreditCode(), excel.getEnterpriseName());
|
||||
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
|
||||
}
|
||||
@@ -166,7 +155,7 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
// 保存失败记录到Redis
|
||||
if (!failures.isEmpty()) {
|
||||
try {
|
||||
String failuresKey = "import:staffEnterpriseRelation:" + taskId + ":failures";
|
||||
String failuresKey = "import:staffEnterpriseRelation:" + taskId + ":failures";
|
||||
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
|
||||
} catch (Exception e) {
|
||||
@@ -185,7 +174,7 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
|
||||
// 记录导入完成
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
ImportLogUtils.logImportComplete(log, taskId, "员工实体关系",
|
||||
ImportLogUtils.logImportComplete(log, taskId, "员工亲属实体关联",
|
||||
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
|
||||
}
|
||||
|
||||
@@ -251,9 +240,9 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
statusData.put("endTime", System.currentTimeMillis());
|
||||
|
||||
if ("SUCCESS".equals(status)) {
|
||||
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
|
||||
statusData.put("message", "员工亲属实体关联导入全部成功!共导入" + result.getTotalCount() + "条数据");
|
||||
} else {
|
||||
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条");
|
||||
statusData.put("message", "员工亲属实体关联导入成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条");
|
||||
}
|
||||
|
||||
redisTemplate.opsForHash().putAll(key, statusData);
|
||||
@@ -297,14 +286,14 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证员工实体关系数据
|
||||
* 验证员工亲属实体关联基础数据
|
||||
*
|
||||
* @param addDTO 新增DTO
|
||||
*/
|
||||
private void validateRelationData(CcdiStaffEnterpriseRelationAddDTO addDTO) {
|
||||
// 验证必填字段
|
||||
if (StringUtils.isEmpty(addDTO.getPersonId())) {
|
||||
throw new RuntimeException("身份证号不能为空");
|
||||
throw new RuntimeException("亲属身份证号不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getSocialCreditCode())) {
|
||||
throw new RuntimeException("统一社会信用代码不能为空");
|
||||
@@ -313,9 +302,9 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
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]$")) {
|
||||
throw new RuntimeException("身份证号格式不正确,必须为18位有效身份证号");
|
||||
throw new RuntimeException("亲属身份证号格式不正确,必须为18位有效身份证号");
|
||||
}
|
||||
|
||||
// 验证统一社会信用代码格式(18位)
|
||||
@@ -331,4 +320,38 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ package com.ruoyi.info.collection.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
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.CcdiStaffEnterpriseRelationEditDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
|
||||
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.mapper.CcdiStaffFmyRelationMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
|
||||
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationService;
|
||||
@@ -37,6 +40,9 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
|
||||
@Resource
|
||||
private CcdiStaffEnterpriseRelationMapper relationMapper;
|
||||
|
||||
@Resource
|
||||
private CcdiStaffFmyRelationMapper familyRelationMapper;
|
||||
|
||||
@Resource
|
||||
private ICcdiStaffEnterpriseRelationImportService relationImportService;
|
||||
|
||||
@@ -86,6 +92,11 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
|
||||
}).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
|
||||
@Transactional
|
||||
public int insertRelation(CcdiStaffEnterpriseRelationAddDTO addDTO) {
|
||||
// 检查身份证号+统一社会信用代码唯一性
|
||||
validateEffectiveFamily(addDTO.getPersonId());
|
||||
|
||||
if (relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())) {
|
||||
throw new RuntimeException("该身份证号和统一社会信用代码组合已存在");
|
||||
throw new RuntimeException("该亲属身份证号和统一社会信用代码组合已存在");
|
||||
}
|
||||
|
||||
CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation();
|
||||
BeanUtils.copyProperties(addDTO, relation);
|
||||
|
||||
// 设置默认值
|
||||
// 新增时强制设置状态为有效
|
||||
relation.setStatus(1);
|
||||
|
||||
if (relation.getIsEmployee() == null) {
|
||||
@@ -159,7 +169,7 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
|
||||
updateWrapper.set(editDTO.getRemark() != null, CcdiStaffEnterpriseRelation::getRemark, editDTO.getRemark());
|
||||
|
||||
// 注意:以下字段不可修改
|
||||
// - personId(身份证号,业务主键)
|
||||
// - personId(亲属身份证号,业务主键)
|
||||
// - socialCreditCode(统一社会信用代码,业务主键)
|
||||
// - dataSource(数据来源,系统字段)
|
||||
// - isEmployee(是否为员工,系统字段)
|
||||
@@ -224,4 +234,28 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
|
||||
|
||||
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 + "]不是有效员工亲属,请先维护有效的员工亲属关系");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import java.util.stream.Collectors;
|
||||
public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelationImportService {
|
||||
|
||||
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
|
||||
private CcdiStaffFmyRelationMapper relationMapper;
|
||||
@@ -168,6 +170,8 @@ public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelat
|
||||
} catch (Exception e) {
|
||||
StaffFmyRelationImportFailureVO failure = new StaffFmyRelationImportFailureVO();
|
||||
BeanUtils.copyProperties(excel, failure);
|
||||
failure.setSheetName(SHEET_NAME);
|
||||
failure.setRowNum(i + EXCEL_DATA_START_ROW);
|
||||
failure.setErrorMessage(e.getMessage());
|
||||
failures.add(failure);
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@ package com.ruoyi.info.collection.service.impl;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.info.collection.domain.CcdiAssetInfo;
|
||||
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.CcdiStaffFmyRelationEditDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
|
||||
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.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
||||
import com.ruoyi.info.collection.service.ICcdiAssetInfoService;
|
||||
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
|
||||
@@ -50,6 +51,9 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer
|
||||
@Resource
|
||||
private ICcdiAssetInfoService assetInfoService;
|
||||
|
||||
@Resource
|
||||
private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper;
|
||||
|
||||
/**
|
||||
* 查询员工亲属关系列表
|
||||
*
|
||||
@@ -161,6 +165,9 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer
|
||||
CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation();
|
||||
BeanUtils.copyProperties(editDTO, 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());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.ruoyi.info.collection.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.common.utils.IdCardUtil;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment;
|
||||
import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO;
|
||||
@@ -16,9 +18,19 @@ import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper;
|
||||
import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService;
|
||||
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
||||
import com.ruoyi.common.utils.IdCardUtil;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
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.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -28,10 +40,6 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 招聘信息异步导入Service实现
|
||||
*
|
||||
@@ -44,6 +52,10 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
||||
|
||||
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
|
||||
private CcdiStaffRecruitmentMapper recruitmentMapper;
|
||||
|
||||
@@ -56,181 +68,56 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
||||
@Override
|
||||
@Async
|
||||
@Transactional
|
||||
public void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList,
|
||||
public void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> recruitmentList,
|
||||
List<CcdiStaffRecruitmentWorkExcel> workList,
|
||||
String taskId,
|
||||
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();
|
||||
|
||||
// 记录导入开始
|
||||
ImportLogUtils.logImportStart(log, taskId, "招聘信息", excelList.size(), userName);
|
||||
ImportLogUtils.logImportStart(log, taskId, "招聘信息双Sheet", totalCount, userName);
|
||||
|
||||
List<CcdiStaffRecruitment> newRecords = new ArrayList<>();
|
||||
List<RecruitmentImportFailureVO> failures = new ArrayList<>();
|
||||
List<MainImportRow> indexedMainRows = buildMainImportRows(safeRecruitmentList);
|
||||
List<WorkImportRow> indexedWorkRows = buildWorkImportRows(safeWorkList);
|
||||
|
||||
// 批量查询已存在的招聘记录编号
|
||||
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的招聘记录编号", excelList.size());
|
||||
Set<String> existingRecruitIds = getExistingRecruitIds(excelList);
|
||||
ImportLogUtils.logBatchQueryComplete(log, taskId, "招聘记录编号", existingRecruitIds.size());
|
||||
MainImportResult mainImportResult = importMainSheet(indexedMainRows, failures, userName, taskId);
|
||||
int workSuccessCount = importWorkSheet(
|
||||
indexedWorkRows,
|
||||
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);
|
||||
addDTO.setRecruitType(RecruitType.inferCode(addDTO.getRecruitName()));
|
||||
|
||||
// 验证数据
|
||||
validateRecruitmentData(addDTO, existingRecruitIds);
|
||||
|
||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||
BeanUtils.copyProperties(excel, recruitment);
|
||||
recruitment.setRecruitType(addDTO.getRecruitType());
|
||||
|
||||
if (existingRecruitIds.contains(excel.getRecruitId())) {
|
||||
// 招聘记录编号在数据库中已存在,直接报错
|
||||
throw new RuntimeException(String.format("招聘记录编号[%s]已存在,请勿重复导入", excel.getRecruitId()));
|
||||
} 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()) {
|
||||
try {
|
||||
String failuresKey = "import:recruitment:" + taskId + ":failures";
|
||||
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
|
||||
} catch (Exception e) {
|
||||
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
|
||||
}
|
||||
saveFailures(taskId, failures);
|
||||
}
|
||||
|
||||
ImportResult result = new ImportResult();
|
||||
result.setTotalCount(excelList.size());
|
||||
result.setSuccessCount(newRecords.size());
|
||||
result.setFailureCount(failures.size());
|
||||
result.setTotalCount(totalCount);
|
||||
result.setSuccessCount(mainImportResult.successCount() + workSuccessCount);
|
||||
result.setFailureCount(Math.max(totalCount - result.getSuccessCount(), 0));
|
||||
|
||||
// 更新最终状态
|
||||
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
||||
updateImportStatus(taskId, finalStatus, result);
|
||||
|
||||
// 记录导入完成
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
ImportLogUtils.logImportComplete(log, taskId, "招聘信息",
|
||||
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Async
|
||||
@Transactional
|
||||
public void importRecruitmentWorkAsync(List<CcdiStaffRecruitmentWorkExcel> excelList,
|
||||
String taskId,
|
||||
String userName) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
ImportLogUtils.logImportStart(log, taskId, "招聘历史工作经历", excelList.size(), userName);
|
||||
|
||||
List<RecruitmentImportFailureVO> failures = new ArrayList<>();
|
||||
List<CcdiStaffRecruitmentWork> validRecords = new ArrayList<>();
|
||||
Set<String> failedRecruitIds = new HashSet<>();
|
||||
Set<String> processedRecruitSortKeys = new HashSet<>();
|
||||
|
||||
Map<String, CcdiStaffRecruitment> recruitmentMap = getRecruitmentMap(excelList);
|
||||
|
||||
for (int i = 0; i < excelList.size(); i++) {
|
||||
CcdiStaffRecruitmentWorkExcel excel = excelList.get(i);
|
||||
try {
|
||||
CcdiStaffRecruitment recruitment = recruitmentMap.get(trim(excel.getRecruitId()));
|
||||
validateRecruitmentWorkData(excel, recruitment, processedRecruitSortKeys);
|
||||
|
||||
CcdiStaffRecruitmentWork work = new CcdiStaffRecruitmentWork();
|
||||
BeanUtils.copyProperties(excel, work);
|
||||
work.setRecruitId(trim(excel.getRecruitId()));
|
||||
work.setCreatedBy(userName);
|
||||
work.setUpdatedBy(userName);
|
||||
validRecords.add(work);
|
||||
|
||||
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
|
||||
validRecords.size(), failures.size());
|
||||
} catch (Exception e) {
|
||||
failedRecruitIds.add(trim(excel.getRecruitId()));
|
||||
failures.add(buildWorkFailure(excel, e.getMessage()));
|
||||
String keyData = String.format("招聘记录编号=%s, 候选人=%s, 工作单位=%s",
|
||||
excel.getRecruitId(), excel.getCandName(), excel.getCompanyName());
|
||||
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
|
||||
}
|
||||
}
|
||||
|
||||
List<CcdiStaffRecruitmentWork> importRecords = validRecords.stream()
|
||||
.filter(work -> !failedRecruitIds.contains(work.getRecruitId()))
|
||||
.toList();
|
||||
appendSkippedFailures(validRecords, failedRecruitIds, failures);
|
||||
|
||||
if (!importRecords.isEmpty()) {
|
||||
Set<String> importRecruitIds = importRecords.stream()
|
||||
.map(CcdiStaffRecruitmentWork::getRecruitId)
|
||||
.collect(Collectors.toSet());
|
||||
LambdaQueryWrapper<CcdiStaffRecruitmentWork> deleteWrapper = new LambdaQueryWrapper<>();
|
||||
deleteWrapper.in(CcdiStaffRecruitmentWork::getRecruitId, importRecruitIds);
|
||||
recruitmentWorkMapper.delete(deleteWrapper);
|
||||
|
||||
importRecords.forEach(recruitmentWorkMapper::insert);
|
||||
}
|
||||
|
||||
if (!failures.isEmpty()) {
|
||||
try {
|
||||
String failuresKey = "import:recruitment:" + taskId + ":failures";
|
||||
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
|
||||
} catch (Exception e) {
|
||||
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
|
||||
}
|
||||
}
|
||||
|
||||
ImportResult result = new ImportResult();
|
||||
result.setTotalCount(excelList.size());
|
||||
result.setSuccessCount(importRecords.size());
|
||||
result.setFailureCount(failures.size());
|
||||
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
||||
String finalStatus = resolveFinalStatus(result);
|
||||
updateImportStatus(taskId, finalStatus, result);
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
ImportLogUtils.logImportComplete(log, taskId, "招聘历史工作经历",
|
||||
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
|
||||
ImportLogUtils.logImportComplete(
|
||||
log,
|
||||
taskId,
|
||||
"招聘信息双Sheet",
|
||||
totalCount,
|
||||
result.getSuccessCount(),
|
||||
result.getFailureCount(),
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -270,14 +157,188 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
||||
return JSON.parseArray(JSON.toJSONString(failuresObj), RecruitmentImportFailureVO.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询已存在的招聘记录编号
|
||||
*/
|
||||
private Set<String> getExistingRecruitIds(List<CcdiStaffRecruitmentExcel> excelList) {
|
||||
List<String> recruitIds = excelList.stream()
|
||||
.map(CcdiStaffRecruitmentExcel::getRecruitId)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.collect(Collectors.toList());
|
||||
private MainImportResult importMainSheet(List<MainImportRow> mainRows,
|
||||
List<RecruitmentImportFailureVO> failures,
|
||||
String userName,
|
||||
String taskId) {
|
||||
if (mainRows.isEmpty()) {
|
||||
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(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()) {
|
||||
return Collections.emptySet();
|
||||
@@ -288,148 +349,138 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
||||
List<CcdiStaffRecruitment> existingRecruitments = recruitmentMapper.selectList(wrapper);
|
||||
|
||||
return existingRecruitments.stream()
|
||||
.map(CcdiStaffRecruitment::getRecruitId)
|
||||
.collect(Collectors.toSet());
|
||||
.map(CcdiStaffRecruitment::getRecruitId)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证招聘信息数据
|
||||
*/
|
||||
private void validateRecruitmentData(CcdiStaffRecruitmentAddDTO addDTO,
|
||||
Set<String> existingRecruitIds) {
|
||||
// 验证必填字段
|
||||
private boolean hasExistingWorkHistory(String recruitId) {
|
||||
LambdaQueryWrapper<CcdiStaffRecruitmentWork> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CcdiStaffRecruitmentWork::getRecruitId, recruitId);
|
||||
return recruitmentWorkMapper.selectCount(wrapper) > 0;
|
||||
}
|
||||
|
||||
private void validateRecruitmentData(CcdiStaffRecruitmentAddDTO addDTO, int sheetRowNum) {
|
||||
if (StringUtils.isEmpty(addDTO.getRecruitId())) {
|
||||
throw new RuntimeException("招聘记录编号不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getRecruitName())) {
|
||||
throw new RuntimeException("招聘项目名称不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘项目名称不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getPosName())) {
|
||||
throw new RuntimeException("职位名称不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位名称不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getPosCategory())) {
|
||||
throw new RuntimeException("职位类别不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位类别不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getPosDesc())) {
|
||||
throw new RuntimeException("职位描述不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位描述不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getCandName())) {
|
||||
throw new RuntimeException("应聘人员姓名不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员姓名不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getCandEdu())) {
|
||||
throw new RuntimeException("应聘人员学历不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员学历不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getCandId())) {
|
||||
throw new RuntimeException("证件号码不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "证件号码不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getCandSchool())) {
|
||||
throw new RuntimeException("应聘人员毕业院校不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员毕业院校不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getCandMajor())) {
|
||||
throw new RuntimeException("应聘人员专业不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员专业不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getCandGrad())) {
|
||||
throw new RuntimeException("应聘人员毕业年月不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员毕业年月不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getAdmitStatus())) {
|
||||
throw new RuntimeException("录用情况不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "录用情况不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(addDTO.getRecruitType())) {
|
||||
throw new RuntimeException("招聘类型不能为空");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘类型不能为空");
|
||||
}
|
||||
|
||||
// 验证证件号码格式
|
||||
String idCardError = IdCardUtil.getErrorMessage(addDTO.getCandId());
|
||||
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])$")) {
|
||||
throw new RuntimeException("毕业年月格式不正确,应为YYYYMM");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "毕业年月格式不正确,应为YYYYMM");
|
||||
}
|
||||
|
||||
// 验证录用状态
|
||||
if (AdmitStatus.getDescByCode(addDTO.getAdmitStatus()) == null) {
|
||||
throw new RuntimeException("录用情况只能填写'录用'、'未录用'或'放弃'");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "录用情况只能填写'录用'、'未录用'或'放弃'");
|
||||
}
|
||||
|
||||
if (RecruitType.getDescByCode(addDTO.getRecruitType()) == null) {
|
||||
throw new RuntimeException("招聘类型只能填写'SOCIAL'或'CAMPUS'");
|
||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘类型只能填写'SOCIAL'或'CAMPUS'");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, CcdiStaffRecruitment> getRecruitmentMap(List<CcdiStaffRecruitmentWorkExcel> excelList) {
|
||||
List<String> recruitIds = excelList.stream()
|
||||
.map(CcdiStaffRecruitmentWorkExcel::getRecruitId)
|
||||
.map(this::trim)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (recruitIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
private void validateWorkGroup(List<WorkImportRow> workRows, CcdiStaffRecruitment recruitment) {
|
||||
Set<Integer> processedSortOrders = new HashSet<>();
|
||||
for (WorkImportRow workRow : workRows) {
|
||||
validateRecruitmentWorkData(workRow.data(), recruitment, processedSortOrders, workRow.sheetRowNum());
|
||||
}
|
||||
List<CcdiStaffRecruitment> recruitments = recruitmentMapper.selectBatchIds(recruitIds);
|
||||
return recruitments.stream()
|
||||
.collect(Collectors.toMap(CcdiStaffRecruitment::getRecruitId, item -> item));
|
||||
}
|
||||
|
||||
private void validateRecruitmentWorkData(CcdiStaffRecruitmentWorkExcel excel,
|
||||
CcdiStaffRecruitment recruitment,
|
||||
Set<String> processedRecruitSortKeys) {
|
||||
Set<Integer> processedSortOrders,
|
||||
int sheetRowNum) {
|
||||
if (StringUtils.isEmpty(trim(excel.getRecruitId()))) {
|
||||
throw new RuntimeException("招聘记录编号不能为空");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(trim(excel.getCandName()))) {
|
||||
throw new RuntimeException("候选人姓名不能为空");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "候选人姓名不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(trim(excel.getRecruitName()))) {
|
||||
throw new RuntimeException("招聘项目名称不能为空");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘项目名称不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(trim(excel.getPosName()))) {
|
||||
throw new RuntimeException("职位名称不能为空");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "职位名称不能为空");
|
||||
}
|
||||
if (excel.getSortOrder() == null || excel.getSortOrder() <= 0) {
|
||||
throw new RuntimeException("排序号不能为空且必须大于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 new RuntimeException("工作单位不能为空");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "工作单位不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(trim(excel.getPositionName()))) {
|
||||
throw new RuntimeException("岗位不能为空");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "岗位不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(trim(excel.getJobStartMonth()))) {
|
||||
throw new RuntimeException("入职年月不能为空");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "入职年月不能为空");
|
||||
}
|
||||
validateMonth(excel.getJobStartMonth(), "入职年月");
|
||||
validateMonth(excel.getJobStartMonth(), "入职年月", sheetRowNum);
|
||||
if (StringUtils.isNotEmpty(trim(excel.getJobEndMonth()))) {
|
||||
validateMonth(excel.getJobEndMonth(), "离职年月");
|
||||
validateMonth(excel.getJobEndMonth(), "离职年月", sheetRowNum);
|
||||
}
|
||||
if (recruitment == null) {
|
||||
throw new RuntimeException("招聘记录编号不存在,请先维护招聘主信息");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不存在,请先维护招聘主信息");
|
||||
}
|
||||
if (!"SOCIAL".equals(recruitment.getRecruitType())) {
|
||||
throw new RuntimeException("该招聘记录不是社招,不允许导入历史工作经历");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "该招聘记录不是社招,不允许导入历史工作经历");
|
||||
}
|
||||
if (!sameText(excel.getCandName(), recruitment.getCandName())) {
|
||||
throw new RuntimeException("招聘记录编号与候选人姓名不匹配");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号与候选人姓名不匹配");
|
||||
}
|
||||
if (!sameText(excel.getRecruitName(), recruitment.getRecruitName())) {
|
||||
throw new RuntimeException("招聘记录编号与招聘项目名称不匹配");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号与招聘项目名称不匹配");
|
||||
}
|
||||
if (!sameText(excel.getPosName(), recruitment.getPosName())) {
|
||||
throw new RuntimeException("招聘记录编号与职位名称不匹配");
|
||||
}
|
||||
String duplicateKey = trim(excel.getRecruitId()) + "#" + excel.getSortOrder();
|
||||
if (!processedRecruitSortKeys.add(duplicateKey)) {
|
||||
throw new RuntimeException("同一招聘记录编号下排序号重复");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号与职位名称不匹配");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateMonth(String value, String fieldName) {
|
||||
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 new RuntimeException(fieldName + "格式不正确,应为YYYY-MM");
|
||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), fieldName + "格式不正确,应为YYYY-MM");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,32 +492,50 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
||||
return value == null ? null : value.trim();
|
||||
}
|
||||
|
||||
private RecruitmentImportFailureVO buildWorkFailure(CcdiStaffRecruitmentWorkExcel excel, String errorMessage) {
|
||||
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 void appendSkippedFailures(List<CcdiStaffRecruitmentWork> validRecords,
|
||||
Set<String> failedRecruitIds,
|
||||
List<RecruitmentImportFailureVO> failures) {
|
||||
Set<String> appendedRecruitIds = new HashSet<>();
|
||||
for (CcdiStaffRecruitmentWork work : validRecords) {
|
||||
if (failedRecruitIds.contains(work.getRecruitId()) && appendedRecruitIds.add(work.getRecruitId())) {
|
||||
RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO();
|
||||
failure.setRecruitId(work.getRecruitId());
|
||||
failure.setCompanyName(work.getCompanyName());
|
||||
failure.setPositionName(work.getPositionName());
|
||||
failure.setErrorMessage("同一招聘记录编号存在失败行,已跳过该编号下全部工作经历,避免覆盖旧数据");
|
||||
failures.add(failure);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
String key = "import:recruitment:" + taskId;
|
||||
Map<String, Object> statusData = new HashMap<>();
|
||||
@@ -486,35 +555,100 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
||||
redisTemplate.opsForHash().putAll(key, statusData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存
|
||||
*/
|
||||
private void saveBatch(List<CcdiStaffRecruitment> list, int batchSize) {
|
||||
// 使用真正的批量插入,分批次执行以提高性能
|
||||
for (int i = 0; i < list.size(); i += batchSize) {
|
||||
int end = Math.min(i + batchSize, list.size());
|
||||
List<CcdiStaffRecruitment> subList = list.subList(i, end);
|
||||
|
||||
// 过滤掉已存在的记录,防止主键冲突
|
||||
List<String> recruitIds = subList.stream()
|
||||
.map(CcdiStaffRecruitment::getRecruitId)
|
||||
.collect(Collectors.toList());
|
||||
.map(CcdiStaffRecruitment::getRecruitId)
|
||||
.toList();
|
||||
if (recruitIds.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!recruitIds.isEmpty()) {
|
||||
List<CcdiStaffRecruitment> existingRecords = recruitmentMapper.selectBatchIds(recruitIds);
|
||||
Set<String> existingIds = existingRecords.stream()
|
||||
.map(CcdiStaffRecruitment::getRecruitId)
|
||||
.collect(Collectors.toSet());
|
||||
List<CcdiStaffRecruitment> existingRecords = recruitmentMapper.selectBatchIds(recruitIds);
|
||||
Set<String> existingIds = existingRecords.stream()
|
||||
.map(CcdiStaffRecruitment::getRecruitId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 只插入不存在的记录
|
||||
List<CcdiStaffRecruitment> toInsert = subList.stream()
|
||||
.filter(r -> !existingIds.contains(r.getRecruitId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!toInsert.isEmpty()) {
|
||||
recruitmentMapper.insertBatch(toInsert);
|
||||
}
|
||||
List<CcdiStaffRecruitment> toInsert = subList.stream()
|
||||
.filter(record -> !existingIds.contains(record.getRecruitId()))
|
||||
.toList();
|
||||
if (!toInsert.isEmpty()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentAddDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentEditDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentQueryDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffRecruitmentWorkEditDTO;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
|
||||
import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO;
|
||||
import com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentWorkVO;
|
||||
import com.ruoyi.info.collection.enums.AdmitStatus;
|
||||
import com.ruoyi.info.collection.enums.RecruitType;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper;
|
||||
import com.ruoyi.info.collection.service.ICcdiStaffRecruitmentImportService;
|
||||
@@ -28,6 +30,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -151,6 +154,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||
BeanUtils.copyProperties(editDTO, recruitment);
|
||||
int result = recruitmentMapper.updateById(recruitment);
|
||||
replaceWorkExperienceList(editDTO);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -178,24 +182,26 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public String importRecruitment(java.util.List<CcdiStaffRecruitmentExcel> excelList) {
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
public String importRecruitment(List<CcdiStaffRecruitmentExcel> recruitmentList,
|
||||
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("至少需要一条数据");
|
||||
}
|
||||
|
||||
// 生成任务ID
|
||||
String taskId = UUID.randomUUID().toString();
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 获取当前用户名
|
||||
String userName = SecurityUtils.getUsername();
|
||||
int totalCount = recruitmentList.size() + workList.size();
|
||||
|
||||
// 初始化Redis状态
|
||||
String statusKey = "import:recruitment:" + taskId;
|
||||
Map<String, Object> statusData = new HashMap<>();
|
||||
statusData.put("taskId", taskId);
|
||||
statusData.put("status", "PROCESSING");
|
||||
statusData.put("totalCount", excelList.size());
|
||||
statusData.put("totalCount", totalCount);
|
||||
statusData.put("successCount", 0);
|
||||
statusData.put("failureCount", 0);
|
||||
statusData.put("progress", 0);
|
||||
@@ -205,44 +211,7 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
||||
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
||||
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
||||
|
||||
// 调用异步导入服务
|
||||
recruitmentImportService.importRecruitmentAsync(excelList, taskId, userName);
|
||||
|
||||
return taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入招聘记录历史工作经历数据(异步)
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @return 任务ID
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public String importRecruitmentWork(List<CcdiStaffRecruitmentWorkExcel> excelList) {
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
throw new RuntimeException("至少需要一条数据");
|
||||
}
|
||||
|
||||
String taskId = UUID.randomUUID().toString();
|
||||
long startTime = System.currentTimeMillis();
|
||||
String userName = SecurityUtils.getUsername();
|
||||
|
||||
String statusKey = "import:recruitment:" + taskId;
|
||||
Map<String, Object> statusData = new HashMap<>();
|
||||
statusData.put("taskId", taskId);
|
||||
statusData.put("status", "PROCESSING");
|
||||
statusData.put("totalCount", excelList.size());
|
||||
statusData.put("successCount", 0);
|
||||
statusData.put("failureCount", 0);
|
||||
statusData.put("progress", 0);
|
||||
statusData.put("startTime", startTime);
|
||||
statusData.put("message", "正在处理历史工作经历...");
|
||||
|
||||
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
||||
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
||||
|
||||
recruitmentImportService.importRecruitmentWorkAsync(excelList, taskId, userName);
|
||||
recruitmentImportService.importRecruitmentAsync(recruitmentList, workList, taskId, userName);
|
||||
|
||||
return taskId;
|
||||
}
|
||||
@@ -262,4 +231,43 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.ruoyi.info.collection.utils;
|
||||
|
||||
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.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
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(带字典下拉框)
|
||||
* 导出的数据包含实际值,但模板中有下拉框供后续编辑使用
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<result property="contactPhone" column="contact_phone"/>
|
||||
<result property="supplierUscc" column="supplier_uscc"/>
|
||||
<result property="supplierBankAccount" column="supplier_bank_account"/>
|
||||
<result property="supplierCount" column="supplier_count"/>
|
||||
<result property="applyDate" column="apply_date"/>
|
||||
<result property="planApproveDate" column="plan_approve_date"/>
|
||||
<result property="announceDate" column="announce_date"/>
|
||||
@@ -47,49 +48,61 @@
|
||||
<!-- 分页查询采购交易列表 -->
|
||||
<select id="selectTransactionPage" resultMap="CcdiPurchaseTransactionVOResult">
|
||||
SELECT
|
||||
purchase_id, purchase_category, project_name, subject_name, subject_desc,
|
||||
purchase_qty, budget_amount, bid_amount, actual_amount, contract_amount, settlement_amount,
|
||||
purchase_method, supplier_name, contact_person, contact_phone, supplier_uscc, supplier_bank_account,
|
||||
apply_date, plan_approve_date, announce_date, bid_open_date, contract_sign_date,
|
||||
expected_delivery_date, actual_delivery_date, acceptance_date, settlement_date,
|
||||
applicant_id, applicant_name, apply_department, purchase_leader_id, purchase_leader_name, purchase_department,
|
||||
created_by, create_time, updated_by, update_time
|
||||
FROM ccdi_purchase_transaction
|
||||
t.purchase_id, t.purchase_category, t.project_name, t.subject_name, t.subject_desc,
|
||||
t.purchase_qty, t.budget_amount, t.bid_amount, t.actual_amount, t.contract_amount, t.settlement_amount,
|
||||
t.purchase_method, t.supplier_name, t.contact_person, t.contact_phone, t.supplier_uscc, t.supplier_bank_account,
|
||||
IFNULL(supplier_stats.supplier_count, 0) AS supplier_count,
|
||||
t.apply_date, t.plan_approve_date, t.announce_date, t.bid_open_date, t.contract_sign_date,
|
||||
t.expected_delivery_date, t.actual_delivery_date, t.acceptance_date, t.settlement_date,
|
||||
t.applicant_id, t.applicant_name, t.apply_department, t.purchase_leader_id, t.purchase_leader_name, t.purchase_department,
|
||||
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>
|
||||
<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 test="query.subjectName != null and query.subjectName != ''">
|
||||
AND subject_name LIKE CONCAT('%', #{query.subjectName}, '%')
|
||||
AND t.subject_name LIKE CONCAT('%', #{query.subjectName}, '%')
|
||||
</if>
|
||||
<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 test="query.applicantId != null and query.applicantId != ''">
|
||||
AND applicant_id = #{query.applicantId}
|
||||
AND t.applicant_id = #{query.applicantId}
|
||||
</if>
|
||||
<if test="query.applyDateStart != null">
|
||||
AND apply_date >= #{query.applyDateStart}
|
||||
AND t.apply_date >= #{query.applyDateStart}
|
||||
</if>
|
||||
<if test="query.applyDateEnd != null">
|
||||
AND apply_date <= #{query.applyDateEnd}
|
||||
AND t.apply_date <= #{query.applyDateEnd}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY create_time DESC
|
||||
ORDER BY t.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 查询采购交易详情 -->
|
||||
<select id="selectTransactionById" resultMap="CcdiPurchaseTransactionVOResult">
|
||||
SELECT
|
||||
purchase_id, purchase_category, project_name, subject_name, subject_desc,
|
||||
purchase_qty, budget_amount, bid_amount, actual_amount, contract_amount, settlement_amount,
|
||||
purchase_method, supplier_name, contact_person, contact_phone, supplier_uscc, supplier_bank_account,
|
||||
apply_date, plan_approve_date, announce_date, bid_open_date, contract_sign_date,
|
||||
expected_delivery_date, actual_delivery_date, acceptance_date, settlement_date,
|
||||
applicant_id, applicant_name, apply_department, purchase_leader_id, purchase_leader_name, purchase_department,
|
||||
created_by, create_time, updated_by, update_time
|
||||
FROM ccdi_purchase_transaction
|
||||
WHERE purchase_id = #{purchaseId}
|
||||
t.purchase_id, t.purchase_category, t.project_name, t.subject_name, t.subject_desc,
|
||||
t.purchase_qty, t.budget_amount, t.bid_amount, t.actual_amount, t.contract_amount, t.settlement_amount,
|
||||
t.purchase_method, t.supplier_name, t.contact_person, t.contact_phone, t.supplier_uscc, t.supplier_bank_account,
|
||||
IFNULL(supplier_stats.supplier_count, 0) AS supplier_count,
|
||||
t.apply_date, t.plan_approve_date, t.announce_date, t.bid_open_date, t.contract_sign_date,
|
||||
t.expected_delivery_date, t.actual_delivery_date, t.acceptance_date, t.settlement_date,
|
||||
t.applicant_id, t.applicant_name, t.apply_department, t.purchase_leader_id, t.purchase_leader_name, t.purchase_department,
|
||||
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 t.purchase_id = #{purchaseId}
|
||||
</select>
|
||||
|
||||
<!-- 批量插入采购交易数据 -->
|
||||
@@ -137,4 +150,12 @@
|
||||
</foreach>
|
||||
</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>
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO" id="CcdiStaffEnterpriseRelationVOResult">
|
||||
<id property="id" column="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="socialCreditCode" column="social_credit_code"/>
|
||||
<result property="enterpriseName" column="enterprise_name"/>
|
||||
@@ -28,17 +30,28 @@
|
||||
<!-- 分页查询员工实体关系列表 -->
|
||||
<select id="selectRelationPage" resultMap="CcdiStaffEnterpriseRelationVOResult">
|
||||
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.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.update_time
|
||||
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>
|
||||
<if test="query.personId != null and query.personId != ''">
|
||||
AND ser.person_id LIKE CONCAT('%', #{query.personId}, '%')
|
||||
</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 != ''">
|
||||
AND ser.social_credit_code LIKE CONCAT('%', #{query.socialCreditCode}, '%')
|
||||
</if>
|
||||
@@ -55,16 +68,40 @@
|
||||
<!-- 查询员工实体关系详情 -->
|
||||
<select id="selectRelationById" resultMap="CcdiStaffEnterpriseRelationVOResult">
|
||||
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.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.update_time
|
||||
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}
|
||||
</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 COUNT(1) > 0
|
||||
@@ -84,6 +121,14 @@
|
||||
</foreach>
|
||||
</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 INTO ccdi_staff_enterprise_relation
|
||||
|
||||
@@ -40,10 +40,10 @@
|
||||
r.created_by, r.create_time, r.updated_by, r.update_time
|
||||
FROM ccdi_staff_recruitment r
|
||||
LEFT JOIN (
|
||||
SELECT recruit_id, COUNT(1) AS work_experience_count
|
||||
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
|
||||
) w ON w.recruit_id = r.recruit_id
|
||||
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>
|
||||
<if test="query.recruitName != null and query.recruitName != ''">
|
||||
AND r.recruit_name LIKE CONCAT('%', #{query.recruitName}, '%')
|
||||
|
||||
@@ -96,8 +96,12 @@ class CcdiAssetInfoControllerTest {
|
||||
@Test
|
||||
void getImportFailures_shouldReturnPagedRows() {
|
||||
AssetImportFailureVO failure1 = new AssetImportFailureVO();
|
||||
failure1.setSheetName("亲属资产信息");
|
||||
failure1.setRowNum(2);
|
||||
failure1.setPersonId("A1");
|
||||
AssetImportFailureVO failure2 = new AssetImportFailureVO();
|
||||
failure2.setSheetName("亲属资产信息");
|
||||
failure2.setRowNum(3);
|
||||
failure2.setPersonId("A2");
|
||||
when(assetInfoImportService.getImportFailures("task-3")).thenReturn(List.of(failure1, failure2));
|
||||
|
||||
@@ -105,7 +109,10 @@ class CcdiAssetInfoControllerTest {
|
||||
|
||||
assertEquals(2, result.getTotal());
|
||||
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
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.ruoyi.info.collection.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.github.pagehelper.parser.defaults.DefaultCountSqlParser;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
|
||||
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
|
||||
import org.apache.ibatis.mapping.BoundSql;
|
||||
import org.apache.ibatis.mapping.Environment;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
||||
import org.apache.ibatis.type.TypeAliasRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiStaffEnterpriseRelationMapperTest {
|
||||
|
||||
private static final String RESOURCE = "mapper/info/collection/CcdiStaffEnterpriseRelationMapper.xml";
|
||||
|
||||
@Test
|
||||
void selectRelationPage_shouldJoinFamilyRelationAndStaff() throws Exception {
|
||||
MappedStatement mappedStatement = loadMappedStatement(
|
||||
"com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper.selectRelationPage");
|
||||
|
||||
CcdiStaffEnterpriseRelationQueryDTO queryDTO = new CcdiStaffEnterpriseRelationQueryDTO();
|
||||
queryDTO.setRelationName("李");
|
||||
queryDTO.setStaffPersonName("张");
|
||||
String sql = renderSql(mappedStatement, Map.of(
|
||||
"page", new Page<>(1, 10),
|
||||
"query", queryDTO
|
||||
));
|
||||
String countSql = normalizeSql(new DefaultCountSqlParser().getSmartCountSql(sql, "0"));
|
||||
|
||||
assertTrue(sql.contains("LEFT JOIN ccdi_staff_fmy_relation sfr"), sql);
|
||||
assertTrue(sql.contains("LEFT JOIN ccdi_base_staff bs ON sfr.person_id = bs.id_card"), sql);
|
||||
assertTrue(sql.contains("sfr.relation_name"), sql);
|
||||
assertTrue(sql.contains("staff_person_id"), sql);
|
||||
assertTrue(sql.contains("bs.name AS staff_person_name"), sql);
|
||||
assertTrue(sql.contains("sfr.relation_name LIKE CONCAT('%', ?, '%')"), sql);
|
||||
assertTrue(sql.contains("bs.name LIKE CONCAT('%', ?, '%')"), sql);
|
||||
assertFalse(countSql.contains("1AND"), countSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectFamilyOptions_shouldOnlyQueryEffectiveEmployeeFamilies() throws Exception {
|
||||
MappedStatement mappedStatement = loadMappedStatement(
|
||||
"com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper.selectFamilyOptions");
|
||||
|
||||
String sql = renderSql(mappedStatement, Map.of("query", "320101"));
|
||||
|
||||
assertTrue(sql.contains("sfr.is_emp_family = 1"), sql);
|
||||
assertTrue(sql.contains("sfr.status = 1"), sql);
|
||||
assertTrue(sql.contains("sfr.relation_cert_no LIKE CONCAT('%', ?, '%')"), sql);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapperXml_shouldContainInvalidateByFamilyCertNo() throws Exception {
|
||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
assertTrue(xml.contains("<update id=\"invalidateByFamilyCertNo\">"), xml);
|
||||
assertTrue(xml.contains("WHERE person_id = #{personId}"), xml);
|
||||
assertTrue(xml.contains("SET status = 0"), xml);
|
||||
}
|
||||
}
|
||||
|
||||
private MappedStatement loadMappedStatement(String statementId) throws Exception {
|
||||
Configuration configuration = new Configuration();
|
||||
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));
|
||||
registerTypeAliases(configuration.getTypeAliasRegistry());
|
||||
configuration.getLanguageRegistry().register(XMLLanguageDriver.class);
|
||||
configuration.addMapper(CcdiStaffEnterpriseRelationMapper.class);
|
||||
|
||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||
XMLMapperBuilder xmlMapperBuilder =
|
||||
new XMLMapperBuilder(inputStream, configuration, RESOURCE, configuration.getSqlFragments());
|
||||
xmlMapperBuilder.parse();
|
||||
}
|
||||
return configuration.getMappedStatement(statementId);
|
||||
}
|
||||
|
||||
private String renderSql(MappedStatement mappedStatement, Map<String, Object> params) {
|
||||
BoundSql boundSql = mappedStatement.getBoundSql(new HashMap<>(params));
|
||||
return normalizeSql(boundSql.getSql());
|
||||
}
|
||||
|
||||
private String normalizeSql(String sql) {
|
||||
return sql.replaceAll("\\s+", " ").trim();
|
||||
}
|
||||
|
||||
private void registerTypeAliases(TypeAliasRegistry typeAliasRegistry) {
|
||||
typeAliasRegistry.registerAlias("map", Map.class);
|
||||
}
|
||||
|
||||
private static class NoOpDataSource implements DataSource {
|
||||
|
||||
@Override
|
||||
public java.sql.Connection getConnection() {
|
||||
throw new UnsupportedOperationException("Not required for SQL rendering tests");
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.sql.Connection getConnection(String username, String password) {
|
||||
throw new UnsupportedOperationException("Not required for SQL rendering tests");
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.io.PrintWriter getLogWriter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogWriter(java.io.PrintWriter out) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginTimeout(int seconds) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLoginTimeout() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.logging.Logger getParentLogger() {
|
||||
return java.util.logging.Logger.getGlobal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(Class<T> iface) {
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWrapperFor(Class<?> iface) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,10 +93,39 @@ class CcdiBaseStaffAssetImportServiceImplTest {
|
||||
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(valueOperations).set(eq("import:baseStaffAsset:task-2:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
|
||||
BaseStaffAssetImportFailureVO failure = (BaseStaffAssetImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
|
||||
assertEquals("员工资产信息", failure.getSheetName());
|
||||
assertEquals(2, failure.getRowNum());
|
||||
assertEquals("320101199201010022", failure.getPersonId());
|
||||
assertTrue(failure.getErrorMessage().contains("员工资产导入仅支持员工本人证件号"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importAssetInfoAsync_shouldFailWhenAssetAlreadyExists() {
|
||||
CcdiBaseStaffAssetInfoExcel excel = buildExcel("320101199001010011", "房产");
|
||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
when(assetInfoMapper.selectOwnerCandidatesByBaseStaffIdCards(List.of("320101199001010011")))
|
||||
.thenReturn(List.of(owner("320101199001010011", "320101199001010011")));
|
||||
|
||||
CcdiAssetInfo existing = new CcdiAssetInfo();
|
||||
existing.setFamilyId("320101199001010011");
|
||||
existing.setPersonId("320101199001010011");
|
||||
existing.setAssetMainType("房产");
|
||||
existing.setAssetSubType("房产小类");
|
||||
existing.setAssetName("房产名称");
|
||||
when(assetInfoMapper.selectList(any())).thenReturn(List.of(existing));
|
||||
|
||||
service.importAssetInfoAsync(List.of(excel), "task-duplicate", "tester");
|
||||
|
||||
verify(assetInfoMapper, never()).insertBatch(any());
|
||||
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(valueOperations).set(eq("import:baseStaffAsset:task-duplicate:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
|
||||
BaseStaffAssetImportFailureVO failure = (BaseStaffAssetImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
|
||||
assertEquals("员工资产信息", failure.getSheetName());
|
||||
assertEquals(2, failure.getRowNum());
|
||||
assertTrue(failure.getErrorMessage().contains("资产记录已存在"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getImportStatusAndFailures_shouldUseBaseStaffAssetPrefixes() {
|
||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.entity.SysDept;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
|
||||
import com.ruoyi.info.collection.service.impl.CcdiBaseStaffImportServiceImpl;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
@@ -11,60 +17,109 @@ import java.util.Set;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiBaseStaffImportServiceImplTest {
|
||||
|
||||
private final CcdiBaseStaffImportServiceImpl service = new CcdiBaseStaffImportServiceImpl();
|
||||
@InjectMocks
|
||||
private CcdiBaseStaffImportServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private SysDeptMapper deptMapper;
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldAllowEmptyAnnualIncome() {
|
||||
assertDoesNotThrow(() -> service.validateStaffData(buildDto(null), false, Collections.emptySet(), Collections.emptySet()));
|
||||
mockNormalDept();
|
||||
assertDoesNotThrow(() -> service.validateStaffData(buildDto(null), Collections.emptySet(), Collections.emptySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldAllowZeroAndTwoDecimalAnnualIncome() {
|
||||
assertDoesNotThrow(() -> service.validateStaffData(buildDto(new BigDecimal("0.00")), false, Collections.emptySet(), Collections.emptySet()));
|
||||
assertDoesNotThrow(() -> service.validateStaffData(buildDto(new BigDecimal("12345.67")), false, Collections.emptySet(), Collections.emptySet()));
|
||||
mockNormalDept();
|
||||
assertDoesNotThrow(() -> service.validateStaffData(buildDto(new BigDecimal("0.00")), Collections.emptySet(), Collections.emptySet()));
|
||||
assertDoesNotThrow(() -> service.validateStaffData(buildDto(new BigDecimal("12345.67")), Collections.emptySet(), Collections.emptySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldAllowPartyMemberValuesZeroAndOne() {
|
||||
mockNormalDept();
|
||||
CcdiBaseStaffAddDTO nonPartyMember = buildDto(null);
|
||||
nonPartyMember.setPartyMember(0);
|
||||
CcdiBaseStaffAddDTO partyMember = buildDto(null);
|
||||
partyMember.setPartyMember(1);
|
||||
|
||||
assertDoesNotThrow(() -> service.validateStaffData(nonPartyMember, false, Collections.emptySet(), Collections.emptySet()));
|
||||
assertDoesNotThrow(() -> service.validateStaffData(partyMember, false, Collections.emptySet(), Collections.emptySet()));
|
||||
assertDoesNotThrow(() -> service.validateStaffData(nonPartyMember, Collections.emptySet(), Collections.emptySet()));
|
||||
assertDoesNotThrow(() -> service.validateStaffData(partyMember, Collections.emptySet(), Collections.emptySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldRejectInvalidPartyMemberValue() {
|
||||
mockNormalDept();
|
||||
CcdiBaseStaffAddDTO dto = buildDto(null);
|
||||
dto.setPartyMember(2);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.validateStaffData(dto, false, Set.of(), Set.of()));
|
||||
() -> service.validateStaffData(dto, Set.of(), Set.of()));
|
||||
|
||||
assertEquals("是否党员只能填写'0'或'1'", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldRejectNegativeAnnualIncome() {
|
||||
mockNormalDept();
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.validateStaffData(buildDto(new BigDecimal("-1.00")), false, Set.of(), Set.of()));
|
||||
() -> service.validateStaffData(buildDto(new BigDecimal("-1.00")), Set.of(), Set.of()));
|
||||
|
||||
assertEquals("年收入不能为负数", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldRejectAnnualIncomeWithMoreThanTwoDecimals() {
|
||||
mockNormalDept();
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.validateStaffData(buildDto(new BigDecimal("12.345")), false, Set.of(), Set.of()));
|
||||
() -> service.validateStaffData(buildDto(new BigDecimal("12.345")), Set.of(), Set.of()));
|
||||
|
||||
assertEquals("年收入最多保留2位小数", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldAllowWhenDeptIsNormalAndNotDeleted() {
|
||||
mockNormalDept();
|
||||
assertDoesNotThrow(() -> service.validateStaffData(buildDto(null), Set.of(), Set.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldRejectWhenDeptDoesNotExist() {
|
||||
when(deptMapper.selectDeptById(10L)).thenReturn(null);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.validateStaffData(buildDto(null), Set.of(), Set.of()));
|
||||
|
||||
assertEquals("所属部门ID[10]不存在或已停用/删除,请检查机构号", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldRejectWhenDeptIsDisabled() {
|
||||
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept("1", "0"));
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.validateStaffData(buildDto(null), Set.of(), Set.of()));
|
||||
|
||||
assertEquals("所属部门ID[10]不存在或已停用/删除,请检查机构号", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldRejectWhenDeptIsDeleted() {
|
||||
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept("0", "2"));
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.validateStaffData(buildDto(null), Set.of(), Set.of()));
|
||||
|
||||
assertEquals("所属部门ID[10]不存在或已停用/删除,请检查机构号", exception.getMessage());
|
||||
}
|
||||
|
||||
private CcdiBaseStaffAddDTO buildDto(BigDecimal annualIncome) {
|
||||
CcdiBaseStaffAddDTO dto = new CcdiBaseStaffAddDTO();
|
||||
dto.setName("张三");
|
||||
@@ -77,4 +132,17 @@ class CcdiBaseStaffImportServiceImplTest {
|
||||
dto.setAnnualIncome(annualIncome);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private SysDept buildDept(String status, String delFlag) {
|
||||
SysDept dept = new SysDept();
|
||||
dept.setDeptId(10L);
|
||||
dept.setDeptName("测试部门");
|
||||
dept.setStatus(status);
|
||||
dept.setDelFlag(delFlag);
|
||||
return dept;
|
||||
}
|
||||
|
||||
private void mockNormalDept() {
|
||||
lenient().when(deptMapper.selectDeptById(10L)).thenReturn(buildDept("0", "0"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
||||
import com.ruoyi.info.collection.service.impl.CcdiEnterpriseBaseInfoImportServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class CcdiEnterpriseBaseInfoImportServiceImplTest {
|
||||
|
||||
private final CcdiEnterpriseBaseInfoImportServiceImpl service = new CcdiEnterpriseBaseInfoImportServiceImpl();
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldRejectWhenDatabaseAlreadyContainsCreditCode() {
|
||||
CcdiEnterpriseBaseInfoExcel excel = buildExcel();
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.validateAndBuildEntity(excel, Set.of("91310000123456789A"), new HashSet<>(), "admin"));
|
||||
|
||||
assertEquals("统一社会信用代码[91310000123456789A]已存在,请勿重复导入", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldRejectWhenExcelContainsDuplicateCreditCode() {
|
||||
CcdiEnterpriseBaseInfoExcel excel = buildExcel();
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.validateAndBuildEntity(excel, Set.of(), new HashSet<>(Set.of("91310000123456789A")), "admin"));
|
||||
|
||||
assertEquals("统一社会信用代码[91310000123456789A]在导入文件中重复,已跳过此条记录", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldNormalizeEnumTextToCode() {
|
||||
CcdiEnterpriseBaseInfoExcel excel = buildExcel();
|
||||
excel.setRiskLevel("高风险");
|
||||
excel.setEntSource("一般企业");
|
||||
|
||||
CcdiEnterpriseBaseInfo entity = service.validateAndBuildEntity(excel, Set.of(), new HashSet<>(), "admin");
|
||||
|
||||
assertEquals("1", entity.getRiskLevel());
|
||||
assertEquals("GENERAL", entity.getEntSource());
|
||||
assertEquals("IMPORT", entity.getDataSource());
|
||||
assertEquals("admin", entity.getCreatedBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldAllowBlankStatus() {
|
||||
CcdiEnterpriseBaseInfoExcel excel = buildExcel();
|
||||
excel.setStatus(null);
|
||||
|
||||
CcdiEnterpriseBaseInfo entity = service.validateAndBuildEntity(excel, Set.of(), new HashSet<>(), "admin");
|
||||
|
||||
assertNull(entity.getStatus());
|
||||
assertEquals("IMPORT", entity.getDataSource());
|
||||
}
|
||||
|
||||
private CcdiEnterpriseBaseInfoExcel buildExcel() {
|
||||
CcdiEnterpriseBaseInfoExcel excel = new CcdiEnterpriseBaseInfoExcel();
|
||||
excel.setSocialCreditCode("91310000123456789A");
|
||||
excel.setEnterpriseName("测试企业");
|
||||
excel.setEnterpriseType("有限责任公司");
|
||||
excel.setEnterpriseNature("民营企业");
|
||||
excel.setIndustryClass("制造业");
|
||||
excel.setIndustryName("电子设备");
|
||||
excel.setStatus("存续");
|
||||
excel.setRiskLevel("1");
|
||||
excel.setEntSource("GENERAL");
|
||||
return excel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO;
|
||||
import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO;
|
||||
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||
import com.ruoyi.info.collection.service.impl.CcdiEnterpriseBaseInfoServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiEnterpriseBaseInfoServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiEnterpriseBaseInfoServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiCustEnterpriseRelationMapper custEnterpriseRelationMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiIntermediaryEnterpriseRelationMapper intermediaryEnterpriseRelationMapper;
|
||||
|
||||
@Mock
|
||||
private ICcdiEnterpriseBaseInfoImportService enterpriseBaseInfoImportService;
|
||||
|
||||
@Mock
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Test
|
||||
void insertEnterpriseBaseInfo_shouldPersistWhenSocialCreditCodeIsUnique() {
|
||||
CcdiEnterpriseBaseInfoAddDTO addDTO = buildAddDto();
|
||||
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
||||
when(enterpriseBaseInfoMapper.insert(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
||||
|
||||
int result = service.insertEnterpriseBaseInfo(addDTO);
|
||||
|
||||
assertEquals(1, result);
|
||||
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
||||
verify(enterpriseBaseInfoMapper).insert(captor.capture());
|
||||
assertEquals("测试企业", captor.getValue().getEnterpriseName());
|
||||
assertEquals("GENERAL", captor.getValue().getEntSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertEnterpriseBaseInfo_shouldSetManualDataSourceAndAllowBlankStatus() {
|
||||
CcdiEnterpriseBaseInfoAddDTO addDTO = buildAddDto();
|
||||
addDTO.setStatus(null);
|
||||
addDTO.setDataSource("API");
|
||||
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
||||
when(enterpriseBaseInfoMapper.insert(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
||||
|
||||
int result = service.insertEnterpriseBaseInfo(addDTO);
|
||||
|
||||
assertEquals(1, result);
|
||||
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
||||
verify(enterpriseBaseInfoMapper).insert(captor.capture());
|
||||
assertEquals("MANUAL", captor.getValue().getDataSource());
|
||||
assertNull(captor.getValue().getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertEnterpriseBaseInfo_shouldRejectInvalidRiskLevel() {
|
||||
CcdiEnterpriseBaseInfoAddDTO addDTO = buildAddDto();
|
||||
addDTO.setRiskLevel("9");
|
||||
when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.insertEnterpriseBaseInfo(addDTO));
|
||||
|
||||
assertEquals("风险等级不在允许范围内", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateEnterpriseBaseInfo_shouldRejectWhenRecordMissing() {
|
||||
CcdiEnterpriseBaseInfoEditDTO editDTO = buildEditDto();
|
||||
when(enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode())).thenReturn(null);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.updateEnterpriseBaseInfo(editDTO));
|
||||
|
||||
assertEquals("实体库记录不存在", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateEnterpriseBaseInfo_shouldKeepExistingDataSource() {
|
||||
CcdiEnterpriseBaseInfoEditDTO editDTO = buildEditDto();
|
||||
editDTO.setDataSource("API");
|
||||
CcdiEnterpriseBaseInfo existing = new CcdiEnterpriseBaseInfo();
|
||||
existing.setSocialCreditCode(editDTO.getSocialCreditCode());
|
||||
existing.setDataSource("MANUAL");
|
||||
when(enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode())).thenReturn(existing);
|
||||
when(enterpriseBaseInfoMapper.updateById(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1);
|
||||
|
||||
int result = service.updateEnterpriseBaseInfo(editDTO);
|
||||
|
||||
assertEquals(1, result);
|
||||
ArgumentCaptor<CcdiEnterpriseBaseInfo> captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class);
|
||||
verify(enterpriseBaseInfoMapper).updateById(captor.capture());
|
||||
assertEquals("MANUAL", captor.getValue().getDataSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectEnterpriseBaseInfoById_shouldConvertEntityToVo() {
|
||||
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
|
||||
entity.setSocialCreditCode("91310000123456789A");
|
||||
entity.setEnterpriseName("测试企业");
|
||||
entity.setRiskLevel("1");
|
||||
entity.setEntSource("GENERAL");
|
||||
when(enterpriseBaseInfoMapper.selectById("91310000123456789A")).thenReturn(entity);
|
||||
|
||||
CcdiEnterpriseBaseInfoVO vo = service.selectEnterpriseBaseInfoById("91310000123456789A");
|
||||
|
||||
assertNotNull(vo);
|
||||
assertEquals("测试企业", vo.getEnterpriseName());
|
||||
assertEquals("1", vo.getRiskLevel());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteEnterpriseBaseInfoByIds_shouldDeleteInBatch() {
|
||||
when(staffEnterpriseRelationMapper.selectCount(any())).thenReturn(0L);
|
||||
when(custEnterpriseRelationMapper.selectCount(any())).thenReturn(0L);
|
||||
when(intermediaryEnterpriseRelationMapper.selectCount(any())).thenReturn(0L);
|
||||
when(enterpriseBaseInfoMapper.deleteBatchIds(java.util.List.of("91310000123456789A", "91310000123456789B")))
|
||||
.thenReturn(2);
|
||||
|
||||
int result = service.deleteEnterpriseBaseInfoByIds(new String[]{"91310000123456789A", "91310000123456789B"});
|
||||
|
||||
assertEquals(2, result);
|
||||
verify(enterpriseBaseInfoMapper).deleteBatchIds(java.util.List.of("91310000123456789A", "91310000123456789B"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteEnterpriseBaseInfoByIds_shouldRejectWhenStaffRelationExists() {
|
||||
when(staffEnterpriseRelationMapper.selectCount(any())).thenReturn(1L);
|
||||
when(custEnterpriseRelationMapper.selectCount(any())).thenReturn(0L);
|
||||
when(intermediaryEnterpriseRelationMapper.selectCount(any())).thenReturn(0L);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.deleteEnterpriseBaseInfoByIds(new String[]{"91310000123456789A"}));
|
||||
|
||||
assertEquals("统一社会信用代码[91310000123456789A]已关联员工,删除失败", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteEnterpriseBaseInfoByIds_shouldRejectWhenMultipleRelationsExist() {
|
||||
when(staffEnterpriseRelationMapper.selectCount(any())).thenReturn(1L);
|
||||
when(custEnterpriseRelationMapper.selectCount(any())).thenReturn(1L);
|
||||
when(intermediaryEnterpriseRelationMapper.selectCount(any())).thenReturn(1L);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.deleteEnterpriseBaseInfoByIds(new String[]{"91310000123456789A"}));
|
||||
|
||||
assertEquals("统一社会信用代码[91310000123456789A]已关联员工、信贷客户、中介,删除失败", exception.getMessage());
|
||||
}
|
||||
|
||||
private CcdiEnterpriseBaseInfoAddDTO buildAddDto() {
|
||||
CcdiEnterpriseBaseInfoAddDTO dto = new CcdiEnterpriseBaseInfoAddDTO();
|
||||
dto.setSocialCreditCode("91310000123456789A");
|
||||
dto.setEnterpriseName("测试企业");
|
||||
dto.setEnterpriseType("有限责任公司");
|
||||
dto.setEnterpriseNature("民营企业");
|
||||
dto.setIndustryClass("制造业");
|
||||
dto.setIndustryName("电子设备");
|
||||
dto.setStatus("存续");
|
||||
dto.setRiskLevel("1");
|
||||
dto.setEntSource("GENERAL");
|
||||
dto.setDataSource("MANUAL");
|
||||
return dto;
|
||||
}
|
||||
|
||||
private CcdiEnterpriseBaseInfoEditDTO buildEditDto() {
|
||||
CcdiEnterpriseBaseInfoEditDTO dto = new CcdiEnterpriseBaseInfoEditDTO();
|
||||
dto.setSocialCreditCode("91310000123456789A");
|
||||
dto.setEnterpriseName("测试企业");
|
||||
dto.setStatus("存续");
|
||||
dto.setRiskLevel("1");
|
||||
dto.setEntSource("GENERAL");
|
||||
dto.setDataSource("MANUAL");
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
|
||||
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
|
||||
import com.ruoyi.info.collection.service.impl.CcdiStaffEnterpriseRelationImportServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class CcdiStaffEnterpriseRelationImportServiceImplTest {
|
||||
|
||||
private final CcdiStaffEnterpriseRelationImportServiceImpl service = new CcdiStaffEnterpriseRelationImportServiceImpl();
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldBuildImportEntityForValidFamily() throws Exception {
|
||||
CcdiStaffEnterpriseRelationExcel excel = buildExcel();
|
||||
CcdiStaffFmyRelation familyRelation = buildFamily();
|
||||
|
||||
CcdiStaffEnterpriseRelation entity = invokeValidateAndBuildEntity(
|
||||
excel,
|
||||
familyRelation,
|
||||
Set.of(excel.getPersonId()),
|
||||
Set.of(),
|
||||
new HashSet<>(),
|
||||
"admin"
|
||||
);
|
||||
|
||||
assertEquals("IMPORT", entity.getDataSource());
|
||||
assertEquals(1, entity.getStatus());
|
||||
assertEquals(1, entity.getIsEmpFamily());
|
||||
assertEquals("admin", entity.getCreatedBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldRejectWhenFamilyDoesNotExist() throws Exception {
|
||||
CcdiStaffEnterpriseRelationExcel excel = buildExcel();
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> invokeValidateAndBuildEntity(excel, null, Set.of(), Set.of(), new HashSet<>(), "admin"));
|
||||
|
||||
assertEquals("亲属身份证号[" + excel.getPersonId() + "]不存在,请先维护员工亲属关系", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldRejectWhenFamilyIsInvalid() throws Exception {
|
||||
CcdiStaffEnterpriseRelationExcel excel = buildExcel();
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> invokeValidateAndBuildEntity(excel, null, Set.of(excel.getPersonId()), Set.of(), new HashSet<>(), "admin"));
|
||||
|
||||
assertEquals("亲属身份证号[" + excel.getPersonId() + "]不是有效员工亲属,请先维护有效的员工亲属关系", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldRejectWhenCombinationAlreadyExistsInDatabase() throws Exception {
|
||||
CcdiStaffEnterpriseRelationExcel excel = buildExcel();
|
||||
CcdiStaffFmyRelation familyRelation = buildFamily();
|
||||
String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode();
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> invokeValidateAndBuildEntity(excel, familyRelation, Set.of(excel.getPersonId()), Set.of(combination), new HashSet<>(), "admin"));
|
||||
|
||||
assertEquals("亲属身份证号[" + excel.getPersonId() + "]和统一社会信用代码[" + excel.getSocialCreditCode() + "]的组合已存在,请勿重复导入", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateAndBuildEntity_shouldRejectWhenCombinationAlreadyExistsInExcel() throws Exception {
|
||||
CcdiStaffEnterpriseRelationExcel excel = buildExcel();
|
||||
CcdiStaffFmyRelation familyRelation = buildFamily();
|
||||
String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode();
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> invokeValidateAndBuildEntity(excel, familyRelation, Set.of(excel.getPersonId()), Set.of(), new HashSet<>(Set.of(combination)), "admin"));
|
||||
|
||||
assertEquals("亲属身份证号[" + excel.getPersonId() + "]和统一社会信用代码[" + excel.getSocialCreditCode() + "]的组合在导入文件中重复,已跳过此条记录", exception.getMessage());
|
||||
}
|
||||
|
||||
private CcdiStaffEnterpriseRelation invokeValidateAndBuildEntity(CcdiStaffEnterpriseRelationExcel excel,
|
||||
CcdiStaffFmyRelation familyRelation,
|
||||
Set<String> knownFamilyCertNos,
|
||||
Set<String> existingCombinations,
|
||||
Set<String> processedCombinations,
|
||||
String userName) throws Exception {
|
||||
Method method = CcdiStaffEnterpriseRelationImportServiceImpl.class.getDeclaredMethod(
|
||||
"validateAndBuildEntity",
|
||||
CcdiStaffEnterpriseRelationExcel.class,
|
||||
CcdiStaffFmyRelation.class,
|
||||
Set.class,
|
||||
Set.class,
|
||||
Set.class,
|
||||
String.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
try {
|
||||
return (CcdiStaffEnterpriseRelation) method.invoke(
|
||||
service,
|
||||
excel,
|
||||
familyRelation,
|
||||
knownFamilyCertNos,
|
||||
existingCombinations,
|
||||
processedCombinations,
|
||||
userName
|
||||
);
|
||||
} catch (InvocationTargetException ex) {
|
||||
Throwable targetException = ex.getTargetException();
|
||||
if (targetException instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private CcdiStaffEnterpriseRelationExcel buildExcel() {
|
||||
CcdiStaffEnterpriseRelationExcel excel = new CcdiStaffEnterpriseRelationExcel();
|
||||
excel.setPersonId("320101199001010022");
|
||||
excel.setSocialCreditCode("91310000123456789A");
|
||||
excel.setEnterpriseName("测试企业");
|
||||
excel.setRelationPersonPost("董事");
|
||||
return excel;
|
||||
}
|
||||
|
||||
private CcdiStaffFmyRelation buildFamily() {
|
||||
CcdiStaffFmyRelation familyRelation = new CcdiStaffFmyRelation();
|
||||
familyRelation.setPersonId("320101199001010011");
|
||||
familyRelation.setRelationCertNo("320101199001010022");
|
||||
familyRelation.setRelationName("李四");
|
||||
familyRelation.setStatus(1);
|
||||
familyRelation.setIsEmpFamily(true);
|
||||
return familyRelation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
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.CcdiStaffEnterpriseRelationEditDTO;
|
||||
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
||||
import com.ruoyi.info.collection.service.impl.CcdiStaffEnterpriseRelationServiceImpl;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiStaffEnterpriseRelationServiceImplTest {
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
registerTableInfo(CcdiStaffEnterpriseRelation.class, CcdiStaffEnterpriseRelationMapper.class.getName());
|
||||
registerTableInfo(CcdiStaffFmyRelation.class, CcdiStaffFmyRelationMapper.class.getName());
|
||||
}
|
||||
|
||||
@InjectMocks
|
||||
private CcdiStaffEnterpriseRelationServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private CcdiStaffEnterpriseRelationMapper relationMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiStaffFmyRelationMapper familyRelationMapper;
|
||||
|
||||
@Mock
|
||||
private ICcdiStaffEnterpriseRelationImportService relationImportService;
|
||||
|
||||
@Mock
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Test
|
||||
void insertRelation_shouldAllowValidFamily() {
|
||||
CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();
|
||||
CcdiStaffFmyRelation familyRelation = new CcdiStaffFmyRelation();
|
||||
familyRelation.setRelationCertNo(addDTO.getPersonId());
|
||||
familyRelation.setRelationName("李四");
|
||||
familyRelation.setPersonId("320101199001010011");
|
||||
|
||||
when(familyRelationMapper.selectOne(any())).thenReturn(familyRelation);
|
||||
when(relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())).thenReturn(false);
|
||||
when(relationMapper.insert(any(CcdiStaffEnterpriseRelation.class))).thenReturn(1);
|
||||
|
||||
int result = service.insertRelation(addDTO);
|
||||
|
||||
assertEquals(1, result);
|
||||
ArgumentCaptor<CcdiStaffEnterpriseRelation> captor = ArgumentCaptor.forClass(CcdiStaffEnterpriseRelation.class);
|
||||
verify(relationMapper).insert(captor.capture());
|
||||
assertEquals(1, captor.getValue().getStatus());
|
||||
assertEquals("MANUAL", captor.getValue().getDataSource());
|
||||
assertEquals(1, captor.getValue().getIsEmpFamily());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertRelation_shouldRejectInvalidFamily() {
|
||||
CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();
|
||||
|
||||
when(familyRelationMapper.selectOne(any()))
|
||||
.thenReturn(null)
|
||||
.thenReturn(new CcdiStaffFmyRelation());
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class, () -> service.insertRelation(addDTO));
|
||||
|
||||
assertEquals("亲属身份证号[" + addDTO.getPersonId() + "]不是有效员工亲属,请先维护有效的员工亲属关系", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertRelation_shouldRejectMissingFamily() {
|
||||
CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();
|
||||
|
||||
when(familyRelationMapper.selectOne(any())).thenReturn(null);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class, () -> service.insertRelation(addDTO));
|
||||
|
||||
assertEquals("亲属身份证号[" + addDTO.getPersonId() + "]不存在,请先维护员工亲属关系", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateRelation_shouldExecuteEditFlow() {
|
||||
CcdiStaffEnterpriseRelationEditDTO editDTO = new CcdiStaffEnterpriseRelationEditDTO();
|
||||
editDTO.setId(1L);
|
||||
editDTO.setPersonId("320101199001010022");
|
||||
editDTO.setSocialCreditCode("91310000123456789A");
|
||||
editDTO.setEnterpriseName("测试企业");
|
||||
editDTO.setRelationPersonPost("董事");
|
||||
editDTO.setStatus(0);
|
||||
editDTO.setRemark("测试备注");
|
||||
|
||||
when(relationMapper.update(isNull(), any(LambdaUpdateWrapper.class))).thenReturn(1);
|
||||
|
||||
int result = service.updateRelation(editDTO);
|
||||
|
||||
assertEquals(1, result);
|
||||
verify(relationMapper).update(isNull(), any(LambdaUpdateWrapper.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectFamilyOptions_shouldDelegateToMapper() {
|
||||
CcdiStaffEnterpriseRelationOptionVO option = new CcdiStaffEnterpriseRelationOptionVO();
|
||||
option.setRelationCertNo("320101199001010022");
|
||||
option.setRelationName("李四");
|
||||
option.setStaffPersonId("320101199001010011");
|
||||
option.setStaffPersonName("张三");
|
||||
List<CcdiStaffEnterpriseRelationOptionVO> expected = List.of(option);
|
||||
|
||||
when(relationMapper.selectFamilyOptions("320101")).thenReturn(expected);
|
||||
|
||||
List<CcdiStaffEnterpriseRelationOptionVO> result = service.selectFamilyOptions("320101");
|
||||
|
||||
assertSame(expected, result);
|
||||
}
|
||||
|
||||
private CcdiStaffEnterpriseRelationAddDTO buildAddDto() {
|
||||
CcdiStaffEnterpriseRelationAddDTO addDTO = new CcdiStaffEnterpriseRelationAddDTO();
|
||||
addDTO.setPersonId("320101199001010022");
|
||||
addDTO.setSocialCreditCode("91310000123456789A");
|
||||
addDTO.setEnterpriseName("测试企业");
|
||||
addDTO.setRelationPersonPost("董事");
|
||||
return addDTO;
|
||||
}
|
||||
|
||||
private static void registerTableInfo(Class<?> entityClass, String namespace) {
|
||||
if (TableInfoHelper.getTableInfo(entityClass) != null) {
|
||||
return;
|
||||
}
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new Configuration(), "");
|
||||
assistant.setCurrentNamespace(namespace);
|
||||
TableInfoHelper.initTableInfo(assistant, entityClass);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
|
||||
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.service.impl.CcdiStaffFmyRelationServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -46,6 +47,9 @@ class CcdiStaffFmyRelationServiceImplTest {
|
||||
@Mock
|
||||
private ICcdiAssetInfoService assetInfoService;
|
||||
|
||||
@Mock
|
||||
private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper;
|
||||
|
||||
@Test
|
||||
void selectRelationById_shouldAggregateAssetInfoList() {
|
||||
CcdiStaffFmyRelationVO relationVO = new CcdiStaffFmyRelationVO();
|
||||
@@ -171,6 +175,36 @@ class CcdiStaffFmyRelationServiceImplTest {
|
||||
verify(assetInfoService).replaceByFamilyIdAndPersonId("320101199001010011", "A123456789", editDTO.getAssetInfoList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateRelation_shouldInvalidateEnterpriseRelationsWhenFamilyBecomesInvalid() {
|
||||
CcdiStaffFmyRelation existing = new CcdiStaffFmyRelation();
|
||||
existing.setId(10L);
|
||||
existing.setRelationCertType("护照");
|
||||
existing.setRelationCertNo("A123456789");
|
||||
existing.setStatus(1);
|
||||
|
||||
CcdiStaffFmyRelationEditDTO editDTO = new CcdiStaffFmyRelationEditDTO();
|
||||
editDTO.setId(10L);
|
||||
editDTO.setPersonId("320101199001010011");
|
||||
editDTO.setRelationType("配偶");
|
||||
editDTO.setRelationName("李四");
|
||||
editDTO.setRelationCertType("护照");
|
||||
editDTO.setRelationCertNo("A123456789");
|
||||
editDTO.setStatus(0);
|
||||
editDTO.setAssetInfoList(List.of(buildAssetDto("车辆")));
|
||||
|
||||
when(relationMapper.selectById(10L)).thenReturn(existing);
|
||||
when(relationMapper.updateById(any(CcdiStaffFmyRelation.class))).thenReturn(1);
|
||||
|
||||
int result = service.updateRelation(editDTO);
|
||||
|
||||
assertEquals(1, result);
|
||||
var order = inOrder(relationMapper, staffEnterpriseRelationMapper, assetInfoService);
|
||||
order.verify(relationMapper).updateById(any(CcdiStaffFmyRelation.class));
|
||||
order.verify(staffEnterpriseRelationMapper).invalidateByFamilyCertNo("A123456789");
|
||||
order.verify(assetInfoService).replaceByFamilyIdAndPersonId("320101199001010011", "A123456789", editDTO.getAssetInfoList());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteRelationByIds_shouldDeleteRelativeAssetsBeforeDeletingRelations() {
|
||||
CcdiStaffFmyRelation relation1 = new CcdiStaffFmyRelation();
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiStaffRecruitmentDualImportContractTest {
|
||||
|
||||
@Test
|
||||
void shouldExposeSingleDualSheetImportEntry() throws Exception {
|
||||
String controller = Files.readString(
|
||||
Path.of("src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java")
|
||||
);
|
||||
assertTrue(controller.contains("\"招聘信息\""));
|
||||
assertTrue(controller.contains("\"历史工作经历\""));
|
||||
assertFalse(controller.contains("workImportTemplate"));
|
||||
assertFalse(controller.contains("importWorkData"));
|
||||
|
||||
String service = Files.readString(
|
||||
Path.of("src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java")
|
||||
);
|
||||
assertTrue(service.contains("String importRecruitment("));
|
||||
assertTrue(service.contains("List<CcdiStaffRecruitmentExcel> recruitmentList"));
|
||||
assertTrue(service.contains("List<CcdiStaffRecruitmentWorkExcel> workList"));
|
||||
assertFalse(service.contains("importRecruitmentWork("));
|
||||
|
||||
String importService = Files.readString(
|
||||
Path.of("src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java")
|
||||
);
|
||||
assertTrue(importService.contains("void importRecruitmentAsync("));
|
||||
assertTrue(importService.contains("List<CcdiStaffRecruitmentExcel> recruitmentList"));
|
||||
assertTrue(importService.contains("List<CcdiStaffRecruitmentWorkExcel> workList"));
|
||||
assertFalse(importService.contains("importRecruitmentWorkAsync("));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeFailureSheetFieldsAndSingleTaskInit() throws Exception {
|
||||
assertHasField(
|
||||
"com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO",
|
||||
"sheetName"
|
||||
);
|
||||
assertHasField(
|
||||
"com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO",
|
||||
"sheetRowNum"
|
||||
);
|
||||
|
||||
String serviceImpl = Files.readString(
|
||||
Path.of("src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java")
|
||||
);
|
||||
assertTrue(serviceImpl.contains("recruitmentList.size() + workList.size()"));
|
||||
assertFalse(serviceImpl.contains("importRecruitmentWork("));
|
||||
}
|
||||
|
||||
private void assertHasField(String className, String fieldName) throws Exception {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
assertNotNull(field);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
|
||||
import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentWorkMapper;
|
||||
import com.ruoyi.info.collection.service.impl.CcdiStaffRecruitmentImportServiceImpl;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.redis.core.HashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiStaffRecruitmentImportServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiStaffRecruitmentImportServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private CcdiStaffRecruitmentMapper recruitmentMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiStaffRecruitmentWorkMapper recruitmentWorkMapper;
|
||||
|
||||
@Mock
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Mock
|
||||
private ValueOperations<String, Object> valueOperations;
|
||||
|
||||
@Mock
|
||||
private HashOperations<String, Object, Object> hashOperations;
|
||||
|
||||
@Test
|
||||
void shouldFailWholeWorkGroupWhenExistingHistoryExists() {
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||
when(recruitmentMapper.selectBatchIds(any())).thenReturn(List.of(buildRecruitment("RC001")));
|
||||
when(recruitmentWorkMapper.selectCount(any())).thenReturn(1L);
|
||||
|
||||
CcdiStaffRecruitmentWorkExcel workRow = new CcdiStaffRecruitmentWorkExcel();
|
||||
workRow.setRecruitId("RC001");
|
||||
workRow.setCandName("张三");
|
||||
workRow.setRecruitName("社会招聘项目");
|
||||
workRow.setPosName("Java工程师");
|
||||
workRow.setSortOrder(1);
|
||||
workRow.setCompanyName("测试科技");
|
||||
workRow.setPositionName("开发工程师");
|
||||
workRow.setJobStartMonth("2022-01");
|
||||
|
||||
service.importRecruitmentAsync(Collections.emptyList(), List.of(workRow), "task-1", "admin");
|
||||
|
||||
verify(recruitmentWorkMapper, never()).delete(any());
|
||||
verify(recruitmentWorkMapper, never()).insert(any(com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork.class));
|
||||
|
||||
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(valueOperations).set(eq("import:recruitment:task-1:failures"), failureCaptor.capture(), anyLong(), any());
|
||||
Object rawFailures = failureCaptor.getValue();
|
||||
assertNotNull(rawFailures);
|
||||
assertInstanceOf(List.class, rawFailures);
|
||||
List<?> failures = (List<?>) rawFailures;
|
||||
assertFalse(failures.isEmpty());
|
||||
RecruitmentImportFailureVO failure = (RecruitmentImportFailureVO) failures.get(0);
|
||||
assertEquals("历史工作经历", failure.getSheetName());
|
||||
assertEquals("2", failure.getSheetRowNum());
|
||||
assertEquals("招聘记录编号[RC001]已存在历史工作经历,不允许重复导入", failure.getErrorMessage());
|
||||
}
|
||||
|
||||
private CcdiStaffRecruitment buildRecruitment(String recruitId) {
|
||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||
recruitment.setRecruitId(recruitId);
|
||||
recruitment.setRecruitType("SOCIAL");
|
||||
recruitment.setCandName("张三");
|
||||
recruitment.setRecruitName("社会招聘项目");
|
||||
recruitment.setPosName("Java工程师");
|
||||
return recruitment;
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,13 @@ import com.ruoyi.common.core.domain.entity.SysDictData;
|
||||
import com.ruoyi.common.utils.DictUtils;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.DataValidation;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
@@ -98,6 +102,59 @@ class EasyExcelUtilTemplateTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importTemplateWithDictDropdown_shouldCreateRecruitmentDualSheetTemplate() throws Exception {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
try (MockedStatic<DictUtils> mocked = mockStatic(DictUtils.class)) {
|
||||
mocked.when(() -> DictUtils.getDictCache("ccdi_admit_status"))
|
||||
.thenReturn(List.of(
|
||||
buildDictData("录用"),
|
||||
buildDictData("未录用"),
|
||||
buildDictData("放弃")
|
||||
));
|
||||
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||
response,
|
||||
CcdiStaffRecruitmentExcel.class,
|
||||
"招聘信息",
|
||||
CcdiStaffRecruitmentWorkExcel.class,
|
||||
"历史工作经历",
|
||||
"招聘信息管理导入模板"
|
||||
);
|
||||
}
|
||||
|
||||
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||
assertEquals(2, workbook.getNumberOfSheets(), "招聘导入模板应输出双Sheet");
|
||||
assertEquals("招聘信息", workbook.getSheetAt(0).getSheetName());
|
||||
assertEquals("历史工作经历", workbook.getSheetAt(1).getSheetName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importTemplateWithDictDropdown_shouldUseUpdatedEnterpriseHeaders() throws Exception {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
try (MockedStatic<DictUtils> mocked = mockStatic(DictUtils.class)) {
|
||||
mocked.when(() -> DictUtils.getDictCache("ccdi_entity_type"))
|
||||
.thenReturn(List.of(buildDictData("有限责任公司")));
|
||||
mocked.when(() -> DictUtils.getDictCache("ccdi_enterprise_nature"))
|
||||
.thenReturn(List.of(buildDictData("民营企业")));
|
||||
mocked.when(() -> DictUtils.getDictCache("ccdi_certificate_type"))
|
||||
.thenReturn(List.of(buildDictData("居民身份证")));
|
||||
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiEnterpriseBaseInfoExcel.class, "实体库管理");
|
||||
}
|
||||
|
||||
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||
Row headerRow = workbook.getSheetAt(0).getRow(0);
|
||||
assertEquals("经营状态", headerRow.getCell(16).getStringCellValue());
|
||||
assertEquals("风险等级*", headerRow.getCell(17).getStringCellValue());
|
||||
assertEquals("企业来源*", headerRow.getCell(18).getStringCellValue());
|
||||
assertEquals(19, headerRow.getLastCellNum());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTextColumn(Sheet sheet, int columnIndex) {
|
||||
CellStyle style = sheet.getColumnStyle(columnIndex);
|
||||
assertNotNull(style, "文本列应设置默认样式");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -80,4 +81,6 @@ public class CcdiProjectExtendedPurchaseDetailVO {
|
||||
private String updatedBy;
|
||||
|
||||
private String updateTime;
|
||||
|
||||
private List<CcdiProjectExtendedPurchaseSupplierVO> supplierList;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 专项核查采购供应商明细
|
||||
*/
|
||||
@Data
|
||||
public class CcdiProjectExtendedPurchaseSupplierVO {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String purchaseId;
|
||||
|
||||
private String supplierName;
|
||||
|
||||
private String supplierUscc;
|
||||
|
||||
private String contactPerson;
|
||||
|
||||
private String contactPhone;
|
||||
|
||||
private String supplierBankAccount;
|
||||
|
||||
private Integer isBidWinner;
|
||||
|
||||
private Integer sortOrder;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedRecruitmentQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExtendedTransferQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseListItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseSupplierVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedRecruitmentListItemVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
|
||||
@@ -96,6 +97,18 @@ public interface CcdiProjectSpecialCheckMapper {
|
||||
@Param("purchaseId") String purchaseId
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询专项核查采购供应商明细
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param purchaseId 采购事项ID
|
||||
* @return 供应商明细
|
||||
*/
|
||||
List<CcdiProjectExtendedPurchaseSupplierVO> selectExtendedPurchaseSuppliers(
|
||||
@Param("projectId") Long projectId,
|
||||
@Param("purchaseId") String purchaseId
|
||||
);
|
||||
|
||||
/**
|
||||
* 查询专项核查招聘拓展列表
|
||||
*
|
||||
|
||||
@@ -103,6 +103,9 @@ public class CcdiProjectSpecialCheckServiceImpl implements ICcdiProjectSpecialCh
|
||||
if (detail == null) {
|
||||
throw new ServiceException("当前记录不属于该项目专项核查范围");
|
||||
}
|
||||
detail.setSupplierList(defaultList(
|
||||
specialCheckMapper.selectExtendedPurchaseSuppliers(queryDTO.getProjectId(), queryDTO.getPurchaseId())
|
||||
));
|
||||
return detail;
|
||||
}
|
||||
|
||||
|
||||
@@ -574,6 +574,33 @@
|
||||
where p.purchase_id = #{purchaseId}
|
||||
</select>
|
||||
|
||||
<select id="selectExtendedPurchaseSuppliers"
|
||||
resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedPurchaseSupplierVO">
|
||||
select
|
||||
s.id,
|
||||
s.purchase_id,
|
||||
s.supplier_name,
|
||||
s.supplier_uscc,
|
||||
s.contact_person,
|
||||
s.contact_phone,
|
||||
s.supplier_bank_account,
|
||||
s.is_bid_winner,
|
||||
s.sort_order
|
||||
from ccdi_purchase_transaction_supplier s
|
||||
inner join ccdi_purchase_transaction p on p.purchase_id = s.purchase_id
|
||||
inner join (
|
||||
select distinct scope.staff_name
|
||||
from (
|
||||
<include refid="projectEmployeeScopeSql"/>
|
||||
) scope
|
||||
where scope.staff_name is not null
|
||||
and scope.staff_name != ''
|
||||
) scoped_staff
|
||||
on scoped_staff.staff_name = p.applicant_name
|
||||
where s.purchase_id = #{purchaseId}
|
||||
order by s.sort_order asc, s.id asc
|
||||
</select>
|
||||
|
||||
<select id="selectExtendedRecruitmentPage" resultMap="ExtendedRecruitmentListItemResultMap">
|
||||
<bind name="projectId" value="query.projectId"/>
|
||||
select distinct r.recruit_id,
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
# 招聘信息管理双 Sheet 导入设计文档
|
||||
|
||||
## 1. 背景
|
||||
|
||||
当前招聘信息管理已经具备以下能力:
|
||||
|
||||
- 主信息维护:`ccdi_staff_recruitment`
|
||||
- 历史工作经历维护:`ccdi_staff_recruitment_work`
|
||||
- 页面编辑态支持手工维护历史工作经历
|
||||
- 导入能力仍拆分为两条独立入口:
|
||||
- `招聘信息导入`
|
||||
- `历史工作经历导入`
|
||||
|
||||
对比已经完成改造的招投标信息维护,招聘信息管理当前导入模式存在三个问题:
|
||||
|
||||
1. 导入入口与当前主从结构不一致,用户需要自行判断先后顺序
|
||||
2. 双 Sheet 场景无法在同一个文件中同时提交主信息和工作经历
|
||||
3. 失败记录无法直接定位失败来源 Sheet 和 Excel 行号
|
||||
|
||||
本次需求要求参考招投标信息维护的双 Sheet 模式,将招聘信息管理改造成单入口双 Sheet 导入,并补齐失败定位信息。
|
||||
|
||||
## 2. 目标与范围
|
||||
|
||||
### 2.1 目标
|
||||
|
||||
1. 招聘信息管理页面只保留一个导入按钮
|
||||
2. 导入模板改为双 Sheet:
|
||||
- `招聘信息`
|
||||
- `历史工作经历`
|
||||
3. 后端统一用一个异步任务处理整份文件
|
||||
4. 支持以下三类导入方式:
|
||||
- 只导 `招聘信息` Sheet
|
||||
- 只导 `历史工作经历` Sheet
|
||||
- 两个 Sheet 同时导入
|
||||
5. 失败列表必须展示:
|
||||
- 失败 Sheet
|
||||
- 失败行号
|
||||
- 失败原因
|
||||
|
||||
### 2.2 已确认业务口径
|
||||
|
||||
1. 页面导入入口合并为单按钮
|
||||
2. `历史工作经历` Sheet 允许独立导入
|
||||
3. `历史工作经历` 独立导入时,继续按库内已存在的招聘主信息匹配
|
||||
4. 如果某个招聘记录已经存在历史工作经历,再次导入该招聘记录的工作经历时直接报错,不允许覆盖旧数据
|
||||
5. 失败展示统一为一个列表,通过 `失败Sheet`、`失败行号`、`失败原因` 定位问题
|
||||
|
||||
### 2.3 非目标
|
||||
|
||||
1. 不调整招聘信息手工新增、编辑、详情、列表查询主体逻辑
|
||||
2. 不调整编辑页中手工维护历史工作经历的交互
|
||||
3. 不新增数据库表,不修改现有表结构
|
||||
4. 不保留旧的“导入工作经历”按钮或旧接口作为兼容入口
|
||||
5. 不新增独立任务中心或额外状态体系
|
||||
|
||||
## 3. 现状分析
|
||||
|
||||
### 3.1 前端现状
|
||||
|
||||
招聘信息管理页面当前有两个导入按钮:
|
||||
|
||||
1. `导入`
|
||||
2. `导入工作经历`
|
||||
|
||||
两个按钮分别对应不同模板和不同上传接口:
|
||||
|
||||
- `/ccdi/staffRecruitment/importTemplate`
|
||||
- `/ccdi/staffRecruitment/workImportTemplate`
|
||||
- `/ccdi/staffRecruitment/importData`
|
||||
- `/ccdi/staffRecruitment/importWorkData`
|
||||
|
||||
页面当前仅维护一套失败记录弹窗,但会根据 `currentImportType` 切换展示字段,本质仍然是两条独立导入链路。
|
||||
|
||||
### 3.2 后端现状
|
||||
|
||||
当前招聘导入服务拆成两条独立任务:
|
||||
|
||||
1. `importRecruitment(List<CcdiStaffRecruitmentExcel>)`
|
||||
2. `importRecruitmentWork(List<CcdiStaffRecruitmentWorkExcel>)`
|
||||
|
||||
对应的异步实现也拆成:
|
||||
|
||||
1. `importRecruitmentAsync(...)`
|
||||
2. `importRecruitmentWorkAsync(...)`
|
||||
|
||||
其中历史工作经历导入现有语义为:
|
||||
|
||||
- 允许独立导入
|
||||
- 按 `recruitId` 匹配招聘主信息
|
||||
- 若同一 `recruitId` 任一行失败,则整组不落库
|
||||
- 导入前会删除该 `recruitId` 旧工作经历,再导入新数据
|
||||
|
||||
本次要调整的是最后一条覆盖语义:改为“已有旧记录则失败,不覆盖”。
|
||||
|
||||
## 4. 方案对比
|
||||
|
||||
### 4.1 方案 A:单入口 + 单任务编排 + 单失败列表
|
||||
|
||||
做法:
|
||||
|
||||
- 页面只保留一个导入按钮
|
||||
- 后端一次读取双 Sheet
|
||||
- 用一个异步任务统一处理主信息和工作经历
|
||||
- 用一个失败列表展示全部失败记录
|
||||
|
||||
优点:
|
||||
|
||||
1. 与招投标信息维护双 Sheet 模式一致
|
||||
2. 支持同文件主从联动导入
|
||||
3. 失败展示口径最统一
|
||||
4. 页面交互最简单
|
||||
|
||||
缺点:
|
||||
|
||||
1. 需要把当前两条招聘导入任务重构为一条统一任务
|
||||
|
||||
### 4.2 方案 B:单入口 + 双任务复用 + 前端聚合失败
|
||||
|
||||
做法:
|
||||
|
||||
- 上传一个双 Sheet 文件
|
||||
- 后端仍拆成主信息任务和工作经历任务
|
||||
- 前端再聚合两个任务的失败记录
|
||||
|
||||
缺点:
|
||||
|
||||
1. 工作经历 Sheet 无法天然引用“本次主 Sheet 刚成功导入的数据”
|
||||
2. 会引入跨任务依赖和前端拼装逻辑
|
||||
3. 导入状态与失败记录口径不够干净
|
||||
|
||||
### 4.3 方案 C:仅合并模板和按钮,不做真实双 Sheet 联动
|
||||
|
||||
做法:
|
||||
|
||||
- 页面只有一个导入按钮
|
||||
- 模板做成双 Sheet
|
||||
- 但工作经历仍只能引用库内已有招聘主信息
|
||||
|
||||
缺点:
|
||||
|
||||
1. 无法完成同文件主从联动
|
||||
2. 不能真正对齐招投标信息维护的双 Sheet 模式
|
||||
3. 业务理解成本仍然偏高
|
||||
|
||||
### 4.4 最终选择
|
||||
|
||||
采用方案 A:`单入口 + 单异步任务 + 单失败列表`。
|
||||
|
||||
理由:
|
||||
|
||||
1. 最符合“参考招投标信息维护双 Sheet 导入”的目标
|
||||
2. 业务链路闭环最完整
|
||||
3. 用户侧交互最清晰
|
||||
4. 后续维护成本最低
|
||||
|
||||
## 5. 总体设计
|
||||
|
||||
### 5.1 模板设计
|
||||
|
||||
统一模板文件名:`招聘信息管理导入模板`
|
||||
|
||||
模板包含两个 Sheet:
|
||||
|
||||
1. `招聘信息`
|
||||
2. `历史工作经历`
|
||||
|
||||
约束:
|
||||
|
||||
1. 两个 Sheet 都允许为空表头,但整份文件至少一个 Sheet 有数据
|
||||
2. `历史工作经历` Sheet 保持当前字段结构,不新增业务字段
|
||||
3. `招聘信息` Sheet 保持当前字段结构,不新增业务字段
|
||||
|
||||
### 5.2 页面交互设计
|
||||
|
||||
页面顶部仅保留一个 `导入` 按钮。
|
||||
|
||||
上传弹窗说明文案明确提示:
|
||||
|
||||
- 模板包含 `招聘信息`、`历史工作经历` 两个 Sheet
|
||||
- 支持只填写一个 Sheet,也支持两个 Sheet 同时填写
|
||||
|
||||
上传成功后:
|
||||
|
||||
1. 只返回一个 `taskId`
|
||||
2. 前端只启动一套轮询
|
||||
3. 页面只保留一个“查看导入失败记录”入口
|
||||
|
||||
### 5.3 接口设计
|
||||
|
||||
保留并调整以下接口:
|
||||
|
||||
1. `POST /ccdi/staffRecruitment/importTemplate`
|
||||
- 输出双 Sheet 模板
|
||||
2. `POST /ccdi/staffRecruitment/importData`
|
||||
- 一次接收整份双 Sheet 文件
|
||||
- 返回统一 `taskId`
|
||||
3. `GET /ccdi/staffRecruitment/importStatus/{taskId}`
|
||||
- 查询统一任务状态
|
||||
4. `GET /ccdi/staffRecruitment/importFailures/{taskId}`
|
||||
- 查询统一失败记录
|
||||
|
||||
移除以下独立导入入口:
|
||||
|
||||
1. `POST /ccdi/staffRecruitment/workImportTemplate`
|
||||
2. `POST /ccdi/staffRecruitment/importWorkData`
|
||||
|
||||
## 6. 后端处理流程设计
|
||||
|
||||
### 6.1 任务编排
|
||||
|
||||
`/importData` 收到文件后,一次读取两个 Sheet:
|
||||
|
||||
- `招聘信息` -> `List<CcdiStaffRecruitmentExcel>`
|
||||
- `历史工作经历` -> `List<CcdiStaffRecruitmentWorkExcel>`
|
||||
|
||||
若两个 Sheet 都为空,直接返回错误:`至少需要一条数据`。
|
||||
|
||||
否则初始化一条统一导入任务,并异步执行以下两个阶段:
|
||||
|
||||
1. 阶段一:处理 `招聘信息` Sheet
|
||||
2. 阶段二:处理 `历史工作经历` Sheet
|
||||
|
||||
### 6.2 阶段一:招聘信息 Sheet
|
||||
|
||||
沿用现有主信息导入规则:
|
||||
|
||||
1. 招聘记录编号不能为空
|
||||
2. 招聘项目名称、职位名称、职位类别、职位描述、候选人姓名、学历、证件号码、毕业院校、专业、毕业年月、录用情况不能为空
|
||||
3. 证件号码格式必须合法
|
||||
4. 毕业年月必须为 `YYYYMM`
|
||||
5. 录用情况只能填写合法枚举
|
||||
6. 招聘类型继续按现有逻辑推断
|
||||
7. 数据库中已存在的 `recruitId` 直接失败
|
||||
8. 文件内重复 `recruitId` 直接失败
|
||||
|
||||
阶段一成功后,建立“本次主 Sheet 成功导入的招聘记录映射”,供阶段二匹配使用。
|
||||
|
||||
### 6.3 阶段二:历史工作经历 Sheet
|
||||
|
||||
#### 6.3.1 匹配规则
|
||||
|
||||
工作经历按 `recruitId` 分组处理。
|
||||
|
||||
每组匹配顺序固定为:
|
||||
|
||||
1. 先匹配本次 `招聘信息` Sheet 中成功导入的招聘主信息
|
||||
2. 再匹配数据库中已存在的招聘主信息
|
||||
|
||||
若都匹配不到,整组失败。
|
||||
|
||||
#### 6.3.2 基础校验
|
||||
|
||||
沿用并收敛现有规则:
|
||||
|
||||
1. `recruitId` 不能为空
|
||||
2. 候选人姓名不能为空
|
||||
3. 招聘项目名称不能为空
|
||||
4. 职位名称不能为空
|
||||
5. 排序号不能为空且必须大于 0
|
||||
6. 工作单位不能为空
|
||||
7. 岗位不能为空
|
||||
8. 入职年月不能为空,格式必须为 `YYYY-MM`
|
||||
9. 离职年月若有值,格式必须为 `YYYY-MM`
|
||||
10. 候选人姓名、招聘项目名称、职位名称必须与匹配到的招聘主信息一致
|
||||
11. 匹配到的招聘主信息 `recruitType` 必须为 `SOCIAL`
|
||||
12. 同一 `recruitId` 下,文件内 `sortOrder` 不能重复
|
||||
|
||||
#### 6.3.3 已有工作经历报错规则
|
||||
|
||||
若数据库中某个 `recruitId` 已经存在任意历史工作经历记录,则本次再导入该 `recruitId` 的工作经历时:
|
||||
|
||||
1. 直接整组失败
|
||||
2. 不删除旧记录
|
||||
3. 不做覆盖
|
||||
4. 不做部分追加
|
||||
|
||||
失败原因统一返回明确文案,例如:
|
||||
|
||||
`招聘记录编号[xxx]已存在历史工作经历,不允许重复导入`
|
||||
|
||||
#### 6.3.4 分组成功与失败策略
|
||||
|
||||
按 `recruitId` 维度整组处理:
|
||||
|
||||
1. 若该组任意一行失败,则整组不落库
|
||||
2. 若该组全部通过校验,则整组批量插入
|
||||
|
||||
这样可以保证同一招聘记录下的工作经历要么整组成功,要么整组失败,不会出现排序半成功半失败的问题。
|
||||
|
||||
### 6.4 失败记录结构
|
||||
|
||||
统一失败记录对象新增以下字段:
|
||||
|
||||
1. `sheetName`
|
||||
2. `sheetRowNum`
|
||||
3. `errorMessage`
|
||||
|
||||
并继续保留必要业务定位字段:
|
||||
|
||||
1. `recruitId`
|
||||
2. `recruitName`
|
||||
3. `posName`
|
||||
4. `candName`
|
||||
5. `candId`
|
||||
6. `companyName`
|
||||
7. `positionName`
|
||||
|
||||
其中:
|
||||
|
||||
- 主 Sheet 失败时,`sheetName = 招聘信息`
|
||||
- 工作经历 Sheet 失败时,`sheetName = 历史工作经历`
|
||||
- `sheetRowNum` 返回 Excel 实际数据行号
|
||||
|
||||
## 7. 前端展示设计
|
||||
|
||||
### 7.1 导入入口
|
||||
|
||||
页面只保留一个导入按钮,不再保留 `导入工作经历`。
|
||||
|
||||
### 7.2 轮询状态
|
||||
|
||||
前端本地只维护一条最近导入任务状态:
|
||||
|
||||
1. `taskId`
|
||||
2. `status`
|
||||
3. `hasFailures`
|
||||
4. `totalCount`
|
||||
5. `successCount`
|
||||
6. `failureCount`
|
||||
|
||||
不再按导入类型区分任务状态。
|
||||
|
||||
### 7.3 失败弹窗
|
||||
|
||||
失败弹窗统一展示一个列表,至少包含以下列:
|
||||
|
||||
1. `失败Sheet`
|
||||
2. `失败行号`
|
||||
3. `招聘记录编号`
|
||||
4. `招聘项目名称`
|
||||
5. `职位名称`
|
||||
6. `候选人姓名`
|
||||
7. `工作单位`
|
||||
8. `失败原因`
|
||||
|
||||
展示规则:
|
||||
|
||||
1. 主 Sheet 失败时,`工作单位` 为空
|
||||
2. 工作经历 Sheet 失败时,`工作单位` 有值
|
||||
3. `失败行号` 展示为 `第X行`
|
||||
|
||||
## 8. 影响范围
|
||||
|
||||
### 8.1 后端
|
||||
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentService.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffRecruitmentImportService.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentServiceImpl.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffRecruitmentImportServiceImpl.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/RecruitmentImportFailureVO.java`
|
||||
|
||||
### 8.2 前端
|
||||
|
||||
- `ruoyi-ui/src/views/ccdiStaffRecruitment/index.vue`
|
||||
- `ruoyi-ui/src/api/ccdiStaffRecruitment.js`
|
||||
|
||||
### 8.3 不影响范围
|
||||
|
||||
1. 招聘详情页展示结构
|
||||
2. 招聘手工新增、编辑、删除接口
|
||||
3. 数据库结构与 SQL 迁移
|
||||
4. 路由、菜单、权限标识
|
||||
|
||||
## 9. 验证方案
|
||||
|
||||
### 9.1 后端验证
|
||||
|
||||
1. `mvn -pl ccdi-info-collection,ruoyi-admin -am -DskipTests compile`
|
||||
|
||||
### 9.2 前端验证
|
||||
|
||||
1. `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && cd ruoyi-ui && npm run build:prod`
|
||||
|
||||
### 9.3 页面实测
|
||||
|
||||
必须通过真实页面下载模板后再构造测试文件,至少覆盖:
|
||||
|
||||
1. 只导 `招聘信息` Sheet 成功
|
||||
2. 只导 `历史工作经历` Sheet,匹配库内已有主信息成功
|
||||
3. 双 Sheet 同时导入,工作经历引用本次主 Sheet 成功
|
||||
4. 工作经历命中已存在旧记录时报错
|
||||
5. 工作经历命中非社招主信息时报错
|
||||
6. 文件内工作经历排序号重复时报错
|
||||
7. 失败弹窗正确展示 `失败Sheet`、`失败行号`、`失败原因`
|
||||
|
||||
测试结束后需关闭测试过程中启动的前后端进程。
|
||||
|
||||
## 10. 结论
|
||||
|
||||
本次采用“单入口 + 双 Sheet 模板 + 单异步任务 + 单失败列表”的最短路径实现:
|
||||
|
||||
1. 页面导入入口统一为一个按钮
|
||||
2. 双 Sheet 模板统一为 `招聘信息` + `历史工作经历`
|
||||
3. 工作经历继续允许独立导入
|
||||
4. 工作经历匹配顺序为“本次主 Sheet 成功数据优先,其次数据库已有主信息”
|
||||
5. 数据库里已存在工作经历时,本次重复导入直接报错,不覆盖旧数据
|
||||
6. 失败列表统一展示失败 Sheet、失败行号和失败原因
|
||||
@@ -0,0 +1,37 @@
|
||||
# 员工信息维护双 Sheet 导入后端实施计划
|
||||
|
||||
## 目标
|
||||
- 将员工信息维护导入模板改为 `员工信息` + `员工资产信息` 双 Sheet。
|
||||
- 统一由 `/ccdi/baseStaff/importData` 接收单文件上传,并按有数据的 Sheet 分别调用现有员工导入与员工资产导入方法。
|
||||
- 员工信息导入取消“更新已存在员工”能力,命中现有员工 ID 或身份证号时直接记失败。
|
||||
- 两类失败记录统一补充 `sheetName`、`rowNum`、`errorMessage`,便于直接定位 Excel 中的失败位置。
|
||||
|
||||
## 实施内容
|
||||
- 控制器改造
|
||||
- 修改 `CcdiBaseStaffController#importTemplate`,下载双 Sheet 模板,文件名统一为“员工信息维护导入模板”。
|
||||
- 修改 `CcdiBaseStaffController#importData`,按 Sheet 名分别读取 `CcdiBaseStaffExcel` 与 `CcdiBaseStaffAssetInfoExcel`。
|
||||
- 两个 Sheet 均为空时返回错误;任一 Sheet 有数据时,仅提交对应导入任务。
|
||||
- 返回新的双任务提交结果对象,包含 `staffTaskId`、`assetTaskId`、`message`。
|
||||
- 服务改造
|
||||
- 修改 `ICcdiBaseStaffService`、`CcdiBaseStaffServiceImpl`,移除 `updateSupport` 参数。
|
||||
- 修改 `ICcdiBaseStaffImportService`、`CcdiBaseStaffImportServiceImpl`,移除更新分支与 `insertOrUpdateBatch` 调用。
|
||||
- 员工导入校验统一为:
|
||||
- 员工 ID 已存在:失败
|
||||
- 身份证号已存在:失败
|
||||
- Excel 内重复:失败
|
||||
- 员工资产导入补充重复校验:
|
||||
- 数据库中存在同一 `personId + assetMainType + assetSubType + assetName`:失败
|
||||
- 导入文件中存在同一组合重复:失败
|
||||
- VO 修正
|
||||
- 新增员工双 Sheet 提交结果 VO。
|
||||
- 修正员工导入失败记录 VO 字段名为 `staffId`,与前端表格字段保持一致。
|
||||
- 员工与员工资产失败记录 VO 均增加 `sheetName`、`rowNum`。
|
||||
|
||||
## 验证
|
||||
- `mvn -pl ccdi-info-collection,ruoyi-admin -am -DskipTests compile`
|
||||
- 补充控制器与服务层回归测试,覆盖双 Sheet 分发与“已存在即失败”规则。
|
||||
|
||||
## 影响范围
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/`
|
||||
@@ -0,0 +1,26 @@
|
||||
# 2026-04-22 招投标导入失败展示增强后端实施计划
|
||||
|
||||
## 1. 目标
|
||||
|
||||
- 为招投标导入失败记录补充失败来源 `Sheet`
|
||||
- 为失败记录补充 Excel 失败行号
|
||||
- 保持现有导入校验逻辑不变,仅增强失败记录元数据
|
||||
|
||||
## 2. 涉及范围
|
||||
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/PurchaseTransactionImportFailureVO.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionImportServiceImpl.java`
|
||||
|
||||
## 3. 实施步骤
|
||||
|
||||
1. 在失败记录 VO 中新增 `sheetName`、`sheetRowNum` 字段,供前端弹窗直接读取
|
||||
2. 在导入服务中为主信息 Sheet 和供应商明细 Sheet 建立“Excel 数据行号”上下文
|
||||
3. 在主信息校验、供应商校验、主从关系校验、空采购事项 ID 供应商校验等失败分支中,统一写入对应的 `Sheet` 与行号
|
||||
4. 对跨多行触发的失败场景,行号以合并字符串形式返回,便于页面直接展示
|
||||
5. 保留原有失败原因与业务字段,避免影响已有失败记录查询接口
|
||||
|
||||
## 4. 验证方式
|
||||
|
||||
- 执行后端编译,确认新增字段和异常封装无编译错误
|
||||
- 通过真实页面上传失败样本,核对失败记录接口返回 `sheetName / sheetRowNum / errorMessage`
|
||||
- 覆盖至少一个主信息失败样本和一个供应商明细失败样本
|
||||
@@ -0,0 +1,35 @@
|
||||
# 招投标信息维护后端实施计划
|
||||
|
||||
## 目标
|
||||
- 将现有 `purchaseTransaction` 后端链路改造为“招投标主信息 + 供应商明细子表”结构。
|
||||
- 保留原有 URL、权限前缀和内部类名,统一用户可见文案为“招投标信息维护”。
|
||||
- 支持详情查询返回全部供应商明细,列表返回中标供应商摘要和参与供应商数。
|
||||
- 支持双 Sheet 导入模板与按 `purchaseId` 聚合校验的异步导入。
|
||||
|
||||
## 实施内容
|
||||
- 数据层
|
||||
- 新增 `ccdi_purchase_transaction_supplier` 明细表初始化 SQL 与增量迁移脚本。
|
||||
- 迁移脚本回填历史中标供应商数据,并将菜单名称更新为“招投标信息维护”。
|
||||
- 领域模型
|
||||
- 新增供应商 entity、DTO、VO、Excel 模型。
|
||||
- 主 DTO/VO 增加 `supplierList`,主 VO 增加 `supplierCount`。
|
||||
- 主 Excel 模板改为仅承载招投标主信息,供应商明细独立建模。
|
||||
- 接口与服务
|
||||
- 列表 SQL 增加供应商数聚合。
|
||||
- 详情查询补充供应商明细列表。
|
||||
- 新增/修改时由 `supplierList` 自动回填主表中标供应商摘要字段。
|
||||
- 删除主记录时级联删除供应商明细。
|
||||
- 导入链路改为“双 Sheet 读取 + 按事项聚合校验 + 主从同落库”。
|
||||
- 项目专项核查
|
||||
- 项目采购详情 VO、Mapper、Service 增加供应商明细查询能力,保持项目详情与信息维护详情口径一致。
|
||||
|
||||
## 验证
|
||||
- `mvn -pl ccdi-info-collection,ccdi-project -am -DskipTests compile`
|
||||
- `./bin/restart_java_backend.sh restart`
|
||||
- 浏览器验证列表接口、详情接口与项目详情供应商明细展示。
|
||||
|
||||
## 产出文件
|
||||
- `sql/ccdi_purchase_transaction.sql`
|
||||
- `sql/ccdi_purchase_transaction_menu.sql`
|
||||
- `sql/migration/2026-04-22-bidding-info-maintenance-supplier-detail.sql`
|
||||
- `ccdi-info-collection` 与 `ccdi-project` 相关后端代码
|
||||
@@ -0,0 +1,39 @@
|
||||
# CCDI 数据库默认排序规则修复实施计划
|
||||
|
||||
## 保存路径确认
|
||||
|
||||
- 路径:`docs/plans/backend/2026-04-22-ccdi-database-default-collation-backend-implementation.md`
|
||||
- 归类:后端实施计划
|
||||
|
||||
## 目标
|
||||
|
||||
- 将 `ccdi` 数据库默认字符集统一为 `utf8mb4`
|
||||
- 将 `ccdi` 数据库默认排序规则统一为 `utf8mb4_general_ci`
|
||||
|
||||
## 背景
|
||||
|
||||
- 当前 `ccdi` 数据库默认排序规则为 `utf8mb4_unicode_ci`。
|
||||
- 仓库数据库规范要求业务库默认排序规则统一为 `utf8mb4_general_ci`,避免新建表或新增字符字段继续继承错误默认值。
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. 新增数据库增量脚本,执行 `ALTER DATABASE ccdi CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci`。
|
||||
2. 使用 `bin/mysql_utf8_exec.sh` 在当前开发库执行脚本。
|
||||
3. 查询 `information_schema.SCHEMATA` 回查默认字符集与默认排序规则是否生效。
|
||||
4. 补充实施记录,说明变更范围与验证结果。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
bin/mysql_utf8_exec.sh sql/migration/2026-04-22-fix-ccdi-database-default-collation.sql
|
||||
```
|
||||
|
||||
```bash
|
||||
mysql ... -e "SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='ccdi';"
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
- `ccdi` 的 `DEFAULT_CHARACTER_SET_NAME` 为 `utf8mb4`
|
||||
- `ccdi` 的 `DEFAULT_COLLATION_NAME` 为 `utf8mb4_general_ci`
|
||||
- 本次变更已形成实施记录
|
||||
@@ -0,0 +1,47 @@
|
||||
# 信息维护移除导出与菜单排序后端实施计划
|
||||
|
||||
**Goal:** 移除信息维护相关模块的后端导出接口与导出权限,并通过增量 SQL 统一“信息维护”目录下的菜单顺序。
|
||||
|
||||
**Architecture:** 后端仅收口 `ccdi-info-collection` 控制器层的 `/export` 接口,不调整列表、详情、导入与删除链路;菜单治理通过 `sql/migration` 新增一份可重复执行脚本完成,脚本同时删除导出权限按钮并更新 `order_num`。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MySQL, Markdown
|
||||
|
||||
---
|
||||
|
||||
## 文件结构与职责
|
||||
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/`
|
||||
移除信息维护模块各控制器的 `/export` 接口。
|
||||
- `sql/migration/2026-04-22-remove-info-maintenance-export-and-sort-menus.sql`
|
||||
删除导出权限菜单并统一“信息维护”子菜单排序。
|
||||
- `sql/*.sql`
|
||||
修正仓库内已有菜单脚本,避免新库初始化时继续带出导出权限或错误顺序。
|
||||
|
||||
## 实施步骤
|
||||
|
||||
- [x] 盘点信息维护模块现存 `/export` 接口与导出权限点
|
||||
- [x] 移除员工、关系、招聘、调动、采购、账户等模块的控制器导出接口
|
||||
- [x] 新增菜单增量脚本,删除导出权限并统一菜单排序
|
||||
- [x] 同步修正仓库内已有菜单 SQL,避免新环境重新带回导出权限
|
||||
- [x] 运行检索校验,确认控制器层不再暴露信息维护导出接口
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
rg -n "@PostMapping\\(\"/export\"\\)|hasPermi\\('ccdi:.*:export'\\)" \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAccountInfoController.java \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffController.java \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiCustEnterpriseRelationController.java \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiCustFmyRelationController.java \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiPurchaseTransactionController.java \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffEnterpriseRelationController.java \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffFmyRelationController.java \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffRecruitmentController.java \
|
||||
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffTransferController.java
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
- 信息维护相关控制器不再提供 `/export` 接口
|
||||
- “信息维护”菜单下相关导出权限按钮已可通过增量 SQL 清理
|
||||
- 菜单排序调整为统一且可重复执行的固定顺序
|
||||
@@ -0,0 +1,39 @@
|
||||
# 员工亲属关系维护双 Sheet 导入后端实施计划
|
||||
|
||||
## 目标
|
||||
- 将员工亲属关系维护导入模板改为双 Sheet:
|
||||
- `员工亲属关系信息`
|
||||
- `亲属资产信息`
|
||||
- 将导入提交入口统一到 `/ccdi/staffFmyRelation/importData`。
|
||||
- 统一补充失败记录定位字段,支持前端展示 `Sheet / Excel行号 / 失败原因`。
|
||||
|
||||
## 实施内容
|
||||
- Controller 调整
|
||||
- `CcdiStaffFmyRelationController#importTemplate` 改为输出双 Sheet 模板,模板文件名统一为“员工亲属关系维护导入模板”。
|
||||
- `CcdiStaffFmyRelationController#importData` 一次读取两个 Sheet。
|
||||
- 按有数据的 Sheet 分别提交亲属关系导入任务和亲属资产导入任务。
|
||||
- 返回新的提交结果 VO,包含 `relationTaskId`、`assetTaskId` 和提示文案。
|
||||
- VO 调整
|
||||
- `StaffFmyRelationImportFailureVO` 增加 `sheetName`、`rowNum`。
|
||||
- `AssetImportFailureVO` 增加 `sheetName`、`rowNum`。
|
||||
- 新增 `StaffFmyRelationImportSubmitResultVO`。
|
||||
- 导入服务调整
|
||||
- `CcdiStaffFmyRelationImportServiceImpl` 失败记录写入固定 `sheetName=员工亲属关系信息`,并记录 Excel 数据行号。
|
||||
- `CcdiAssetInfoImportServiceImpl` 失败记录写入固定 `sheetName=亲属资产信息`,并记录 Excel 数据行号。
|
||||
- 兼容策略
|
||||
- 保留原 `CcdiAssetInfoController` 的状态查询与失败记录查询接口,前端继续复用原有资产任务轮询与失败记录查看能力。
|
||||
|
||||
## 验证
|
||||
- 后端优先验证:
|
||||
- `CcdiStaffFmyRelationControllerTest`
|
||||
- `CcdiAssetInfoControllerTest`
|
||||
- 编译验证:
|
||||
- `mvn -pl ccdi-info-collection -am -Dmaven.test.skip=true compile`
|
||||
|
||||
## 影响范围
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffFmyRelationController.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationImportServiceImpl.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/StaffFmyRelationImportFailureVO.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java`
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/StaffFmyRelationImportSubmitResultVO.java`
|
||||
@@ -0,0 +1,31 @@
|
||||
# 员工招聘列表排序规则冲突修复实施计划
|
||||
|
||||
## 保存路径确认
|
||||
|
||||
- 路径:`docs/plans/backend/2026-04-22-staff-recruitment-collation-fix-backend-implementation.md`
|
||||
- 归类:后端实施计划
|
||||
|
||||
## 背景
|
||||
|
||||
- 员工招聘列表查询执行 `ccdi_staff_recruitment` 与 `ccdi_staff_recruitment_work` 的 `recruit_id` 关联时,报错 `Illegal mix of collations (utf8mb4_0900_ai_ci) and (utf8mb4_general_ci)`。
|
||||
- 现有招聘主表已被纳入全库统一排序规则脚本,但历史工作经历子表在建表时未显式声明 `utf8mb4_general_ci`,且未被纳入统一修复脚本,导致在 MySQL 8 环境中可能沿用默认 `utf8mb4_0900_ai_ci`。
|
||||
|
||||
## 实施范围
|
||||
|
||||
- 后端 MyBatis 查询 XML
|
||||
- 招聘相关 SQL 建表脚本
|
||||
- 数据库增量迁移脚本
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. 修改招聘列表查询 SQL,在 `ccdi_staff_recruitment_work.recruit_id` 聚合与关联时显式使用 `utf8mb4_general_ci`,先恢复查询可用性。
|
||||
2. 修正 `2026-04-15-add-staff-recruitment-social-work-summary.sql`,为 `ccdi_staff_recruitment_work` 建表语句补齐 `COLLATE=utf8mb4_general_ci`。
|
||||
3. 补充增量脚本,将现有库中的 `ccdi_staff_recruitment_work` 转换为 `utf8mb4_general_ci`。
|
||||
4. 更新全库统一排序规则脚本,将该表纳入统一修复范围,避免后续漏执行。
|
||||
5. 编译受影响模块,确认 Mapper XML 与资源装配正常。
|
||||
|
||||
## 验证要点
|
||||
|
||||
- `selectRecruitmentPage` 查询不再因 `recruit_id` 关联报排序规则冲突。
|
||||
- `ccdi_staff_recruitment_work` 表级与字符字段排序规则统一为 `utf8mb4_general_ci`。
|
||||
- `mvn -pl ccdi-info-collection -am compile` 通过。
|
||||
@@ -0,0 +1,36 @@
|
||||
# 招聘信息历史工作经历手动编辑后端实施计划
|
||||
|
||||
## 文档信息
|
||||
|
||||
- 保存路径:`docs/plans/backend/2026-04-22-staff-recruitment-work-experience-manual-edit-plan.md`
|
||||
- 适用范围:招聘信息管理编辑接口
|
||||
- 需求目标:在招聘信息编辑页支持手动维护历史工作经历,并保证保存后落到 `ccdi_staff_recruitment_work` 子表
|
||||
|
||||
## 实施范围
|
||||
|
||||
1. 扩展招聘信息编辑 DTO,允许接收历史工作经历列表。
|
||||
2. 增加历史工作经历子项 DTO,并对字符长度、年月格式进行基础校验。
|
||||
3. 调整招聘信息编辑服务:
|
||||
- 主表 `ccdi_staff_recruitment` 继续按原逻辑更新;
|
||||
- 当招聘类型为 `SOCIAL` 且前端传入工作经历列表时,按招聘记录编号先删后插覆盖子表;
|
||||
- 当招聘类型改为 `CAMPUS` 时,删除该记录已存在的历史工作经历。
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. 新增历史工作经历编辑 DTO,约束 `companyName`、`departmentName`、`positionName`、年月等字段长度与格式。
|
||||
2. 在 `CcdiStaffRecruitmentEditDTO` 中增加 `workExperienceList` 字段,并启用嵌套校验。
|
||||
3. 在 `CcdiStaffRecruitmentServiceImpl.updateRecruitment` 中增加子表覆盖保存逻辑。
|
||||
4. 保持详情查询逻辑不变,继续通过已有 `selectWorkExperienceList` 返回子表明细。
|
||||
|
||||
## 影响评估
|
||||
|
||||
- 仅影响招聘信息编辑接口,不影响招聘信息导入、详情查询、列表分页逻辑。
|
||||
- 不新增数据库结构变更,不新增菜单或权限。
|
||||
- 旧前端若未传 `workExperienceList`,社招编辑仍保留已有工作经历数据,不会被误删。
|
||||
|
||||
## 验证要点
|
||||
|
||||
1. 编辑社招记录时可提交多条历史工作经历并成功保存。
|
||||
2. 编辑社招记录时删除全部历史工作经历后提交,子表数据应被清空。
|
||||
3. 将社招记录改为校招后提交,历史工作经历应自动删除。
|
||||
4. 非法年月格式或超长字段应被后端校验拒绝。
|
||||
@@ -0,0 +1,32 @@
|
||||
# 员工信息导入机构号校验后端实施计划
|
||||
|
||||
## 目标
|
||||
- 在员工信息 Excel 导入链路中校验 `所属部门ID(deptId)` 是否对应有效机构号。
|
||||
- 有效口径统一为 `sys_dept` 中“正常且未删除”的部门,即 `status = '0'` 且 `del_flag = '0'`。
|
||||
- 命中不存在、已停用或已删除的部门时,不入库,直接进入员工导入失败记录。
|
||||
|
||||
## 实施内容
|
||||
- 导入服务改造
|
||||
- 修改 `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffImportServiceImpl.java`。
|
||||
- 在 `validateStaffData` 中于必填校验后增加 `deptId` 有效性校验。
|
||||
- 新增私有方法按 `deptId` 查询部门并校验 `status` 与 `delFlag`。
|
||||
- 校验失败时抛出统一错误文案:`所属部门ID[xxx]不存在或已停用/删除,请检查机构号`。
|
||||
- 部门 Mapper 对齐
|
||||
- 修改 `ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml`。
|
||||
- 为 `selectDeptById` 查询补齐 `d.del_flag` 字段,保证导入服务可同时判断停用与逻辑删除状态。
|
||||
- 单元测试补充
|
||||
- 修改 `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffImportServiceImplTest.java`。
|
||||
- 增加部门存在、停用、删除三类校验测试。
|
||||
- 修改 `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffDualImportServiceTest.java`。
|
||||
- 增加混合导入场景测试,验证合法员工成功入库、非法 `deptId` 写入失败记录且任务状态为 `PARTIAL_SUCCESS`。
|
||||
|
||||
## 验证
|
||||
- 定向单测:
|
||||
- `mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiBaseStaffImportServiceImplTest,CcdiBaseStaffDualImportServiceTest test`
|
||||
- 编译校验:
|
||||
- `mvn -pl ccdi-info-collection,ruoyi-admin -am -DskipTests compile`
|
||||
|
||||
## 影响范围
|
||||
- 员工信息导入后端异步校验逻辑
|
||||
- 系统部门主键查询字段映射
|
||||
- 员工导入相关单元测试
|
||||
@@ -0,0 +1,23 @@
|
||||
# 招投标供应商校验后端实施计划
|
||||
|
||||
## 目标
|
||||
- 让招投标信息维护页面的新增、编辑接口仅保留供应商名称和统一信用代码必填校验。
|
||||
- 移除供应商联系人、联系电话、银行账户,以及供应商名称/统一信用代码的内容格式校验,避免页面保存被接口层拦截。
|
||||
|
||||
## 实施内容
|
||||
- 调整 `CcdiPurchaseTransactionSupplierDTO`
|
||||
- 保留 `supplierName` 的 `@NotBlank`。
|
||||
- 为 `supplierUscc` 增加 `@NotBlank` 必填校验。
|
||||
- 移除 `supplierName` 的长度校验。
|
||||
- 移除 `supplierUscc` 的格式校验。
|
||||
- 移除 `contactPerson`、`contactPhone`、`supplierBankAccount` 的内容校验注解。
|
||||
|
||||
## 验证
|
||||
- `mvn -pl ccdi-info-collection -am -DskipTests compile`
|
||||
- `sh bin/restart_java_backend.sh`
|
||||
- 结合真实页面验证:
|
||||
- 新增弹窗提交 `supplierUscc=ABC`、`contactPhone=123` 成功
|
||||
- 编辑弹窗提交 `supplierUscc=XYZ`、`contactPhone=abc123` 成功
|
||||
|
||||
## 产出文件
|
||||
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiPurchaseTransactionSupplierDTO.java`
|
||||
@@ -0,0 +1,50 @@
|
||||
# 实体库管理新增弹窗后端实施计划
|
||||
|
||||
## 文档路径确认
|
||||
|
||||
- 后端实施计划保存路径:`docs/plans/backend/`
|
||||
- 本文档文件名:`2026-04-23-enterprise-base-info-add-dialog-backend-implementation.md`
|
||||
|
||||
## 需求目标
|
||||
|
||||
- 实体库管理新增场景不再要求前端传入数据来源。
|
||||
- 新增时由后端自动写入数据来源,人工新增统一落为 `MANUAL`。
|
||||
- 编辑时后端保留原有数据来源,不允许通过请求修改。
|
||||
- 经营状态调整为非必填项。
|
||||
- 导入模板同步去除数据来源输入列,并将经营状态改为非必填展示。
|
||||
- 导入时由后端自动写入 `IMPORT`,不再依赖模板传值。
|
||||
|
||||
## 实施范围
|
||||
|
||||
- `CcdiEnterpriseBaseInfoAddDTO`
|
||||
- `CcdiEnterpriseBaseInfoEditDTO`
|
||||
- `CcdiEnterpriseBaseInfoServiceImpl`
|
||||
- `CcdiEnterpriseBaseInfoImportServiceImpl`
|
||||
- `CcdiEnterpriseBaseInfoExcel`
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. 去除新增 DTO 中 `status`、`dataSource` 的必填约束,编辑 DTO 中去除 `status` 的必填约束。
|
||||
2. 调整新增服务逻辑,仅校验风险等级和企业来源,新增数据时后端固定写入 `MANUAL`。
|
||||
3. 调整编辑服务逻辑,更新时始终沿用数据库中的原始数据来源。
|
||||
4. 调整状态字段赋值逻辑,将空白字符串统一收敛为 `null`,避免出现空串脏数据。
|
||||
5. 调整导入服务逻辑,去掉对模板数据来源的解析和校验,导入时固定写入 `IMPORT`。
|
||||
6. 修改导入模板对象,移除数据来源列,并把“经营状态*”调整为“经营状态”。
|
||||
7. 补充/更新单元测试,覆盖新增自动写入 `MANUAL`、编辑保留原数据来源、导入自动写入 `IMPORT`、经营状态非必填和模板表头变更。
|
||||
|
||||
## 验证方案
|
||||
|
||||
- 执行定向测试:
|
||||
`mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiEnterpriseBaseInfoServiceImplTest,CcdiEnterpriseBaseInfoImportServiceImplTest,EasyExcelUtilTemplateTest test`
|
||||
- 通过真实后端接口验证:
|
||||
- 新增不传 `dataSource`、不传 `status` 仍可成功
|
||||
- 查询结果中 `dataSource=MANUAL`
|
||||
- 删除测试数据成功
|
||||
- 下载导入模板并核对首行表头:
|
||||
- 包含 `经营状态`
|
||||
- 不包含 `数据来源`
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
- 当前工作区存在其他未提交改动,本次仅处理实体库管理相关文件,不回退无关内容。
|
||||
- 导入模板对象同时承担导入解析职责,本次变更后模板和导入字段保持一致。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user