From bda89202ba6be116ca36d7f7827b9386be1c4401 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Fri, 13 Mar 2026 16:29:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=91=98=E5=B7=A5=E4=B8=8E?= =?UTF-8?q?=E4=BA=B2=E5=B1=9E=E8=B5=84=E4=BA=A7=E5=AF=BC=E5=85=A5=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E6=8B=86=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CcdiAssetInfoController.java | 24 +- .../CcdiBaseStaffAssetImportController.java | 101 ++++++++ .../domain/excel/CcdiAssetInfoExcel.java | 4 +- .../excel/CcdiBaseStaffAssetInfoExcel.java | 89 +++++++ .../domain/vo/AssetImportFailureVO.java | 4 +- .../vo/BaseStaffAssetImportFailureVO.java | 49 ++++ .../mapper/CcdiAssetInfoMapper.java | 16 +- .../service/ICcdiAssetInfoImportService.java | 4 +- .../ICcdiBaseStaffAssetImportService.java | 49 ++++ .../impl/CcdiAssetInfoImportServiceImpl.java | 18 +- .../CcdiBaseStaffAssetImportServiceImpl.java | 226 ++++++++++++++++++ .../info/collection/CcdiAssetInfoMapper.xml | 22 +- .../CcdiAssetInfoControllerTest.java | 4 +- ...cdiBaseStaffAssetImportControllerTest.java | 119 +++++++++ .../CcdiAssetInfoImportServiceImplTest.java | 24 +- ...diBaseStaffAssetImportServiceImplTest.java | 145 +++++++++++ ...ployee-family-asset-import-split-design.md | 7 + 17 files changed, 841 insertions(+), 64 deletions(-) create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportController.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiBaseStaffAssetInfoExcel.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/BaseStaffAssetImportFailureVO.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffAssetImportService.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportControllerTest.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java index bae9a0a..2a93261 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java @@ -28,12 +28,12 @@ import java.util.ArrayList; import java.util.List; /** - * 资产信息导入Controller + * 亲属资产信息导入Controller * * @author ruoyi * @date 2026-03-12 */ -@Tag(name = "资产信息导入管理") +@Tag(name = "亲属资产信息导入管理") @RestController @RequestMapping("/ccdi/assetInfo") public class CcdiAssetInfoController extends BaseController { @@ -44,18 +44,18 @@ public class CcdiAssetInfoController extends BaseController { /** * 下载导入模板 */ - @Operation(summary = "下载资产导入模板") + @Operation(summary = "下载亲属资产导入模板") @PostMapping("/importTemplate") public void importTemplate(HttpServletResponse response) { - EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiAssetInfoExcel.class, "资产信息"); + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiAssetInfoExcel.class, "亲属资产信息"); } /** - * 导入资产信息 + * 导入亲属资产信息 */ - @Operation(summary = "导入资产信息") - @PreAuthorize("@ss.hasAnyPermi('ccdi:employee:import,ccdi:staffFmyRelation:import')") - @Log(title = "资产信息", businessType = BusinessType.IMPORT) + @Operation(summary = "导入亲属资产信息") + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')") + @Log(title = "亲属资产信息", businessType = BusinessType.IMPORT) @PostMapping("/importData") public AjaxResult importData(MultipartFile file) throws Exception { List list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiAssetInfoExcel.class); @@ -74,8 +74,8 @@ public class CcdiAssetInfoController extends BaseController { /** * 查询导入状态 */ - @Operation(summary = "查询资产导入状态") - @PreAuthorize("@ss.hasAnyPermi('ccdi:employee:import,ccdi:staffFmyRelation:import')") + @Operation(summary = "查询亲属资产导入状态") + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')") @GetMapping("/importStatus/{taskId}") public AjaxResult getImportStatus(@PathVariable String taskId) { return success(assetInfoImportService.getImportStatus(taskId)); @@ -84,8 +84,8 @@ public class CcdiAssetInfoController extends BaseController { /** * 查询导入失败记录 */ - @Operation(summary = "查询资产导入失败记录") - @PreAuthorize("@ss.hasAnyPermi('ccdi:employee:import,ccdi:staffFmyRelation:import')") + @Operation(summary = "查询亲属资产导入失败记录") + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')") @GetMapping("/importFailures/{taskId}") public TableDataInfo getImportFailures( @PathVariable String taskId, diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportController.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportController.java new file mode 100644 index 0000000..658fd6c --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportController.java @@ -0,0 +1,101 @@ +package com.ruoyi.info.collection.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.BaseStaffAssetImportFailureVO; +import com.ruoyi.info.collection.domain.vo.ImportResultVO; +import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService; +import com.ruoyi.info.collection.utils.EasyExcelUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.ArrayList; +import java.util.List; + +/** + * 员工资产信息导入Controller + * + * @author ruoyi + * @date 2026-03-13 + */ +@Tag(name = "员工资产信息导入管理") +@RestController +@RequestMapping("/ccdi/baseStaff/asset") +public class CcdiBaseStaffAssetImportController extends BaseController { + + @Resource + private ICcdiBaseStaffAssetImportService baseStaffAssetImportService; + + /** + * 下载导入模板 + */ + @Operation(summary = "下载员工资产导入模板") + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiBaseStaffAssetInfoExcel.class, "员工资产信息"); + } + + /** + * 导入员工资产信息 + */ + @Operation(summary = "导入员工资产信息") + @PreAuthorize("@ss.hasPermi('ccdi:employee:import')") + @Log(title = "员工资产信息", businessType = BusinessType.IMPORT) + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file) throws Exception { + List list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiBaseStaffAssetInfoExcel.class); + if (list == null || list.isEmpty()) { + return warn("至少需要一条数据"); + } + + String taskId = baseStaffAssetImportService.importAssetInfo(list); + ImportResultVO result = new ImportResultVO(); + result.setTaskId(taskId); + result.setStatus("PROCESSING"); + result.setMessage("导入任务已提交,正在后台处理"); + return AjaxResult.success("导入任务已提交,正在后台处理", result); + } + + /** + * 查询导入状态 + */ + @Operation(summary = "查询员工资产导入状态") + @PreAuthorize("@ss.hasPermi('ccdi:employee:import')") + @GetMapping("/importStatus/{taskId}") + public AjaxResult getImportStatus(@PathVariable String taskId) { + return success(baseStaffAssetImportService.getImportStatus(taskId)); + } + + /** + * 查询导入失败记录 + */ + @Operation(summary = "查询员工资产导入失败记录") + @PreAuthorize("@ss.hasPermi('ccdi:employee:import')") + @GetMapping("/importFailures/{taskId}") + public TableDataInfo getImportFailures( + @PathVariable String taskId, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + List failures = baseStaffAssetImportService.getImportFailures(taskId); + int fromIndex = (pageNum - 1) * pageSize; + int toIndex = Math.min(fromIndex + pageSize, failures.size()); + if (fromIndex >= failures.size()) { + return getDataTable(new ArrayList<>(), failures.size()); + } + return getDataTable(failures.subList(fromIndex, toIndex), failures.size()); + } +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java index 3ffff18..0dc950b 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java @@ -24,8 +24,8 @@ public class CcdiAssetInfoExcel implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 资产实际持有人证件号 */ - @ExcelProperty(value = "资产实际持有人证件号*", index = 0) + /** 亲属证件号 */ + @ExcelProperty(value = "亲属证件号*", index = 0) @ColumnWidth(22) @Required @TextFormat diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiBaseStaffAssetInfoExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiBaseStaffAssetInfoExcel.java new file mode 100644 index 0000000..6eca761 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiBaseStaffAssetInfoExcel.java @@ -0,0 +1,89 @@ +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 com.ruoyi.common.annotation.TextFormat; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 员工资产信息Excel导入导出对象 + * + * @author ruoyi + * @date 2026-03-13 + */ +@Data +public class CcdiBaseStaffAssetInfoExcel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 员工身份证号 */ + @ExcelProperty(value = "员工身份证号*", index = 0) + @ColumnWidth(22) + @Required + @TextFormat + private String personId; + + /** 资产大类 */ + @ExcelProperty(value = "资产大类*", index = 1) + @ColumnWidth(16) + @Required + private String assetMainType; + + /** 资产小类 */ + @ExcelProperty(value = "资产小类*", index = 2) + @ColumnWidth(18) + @Required + private String assetSubType; + + /** 资产名称 */ + @ExcelProperty(value = "资产名称*", index = 3) + @ColumnWidth(24) + @Required + private String assetName; + + /** 产权占比 */ + @ExcelProperty(value = "产权占比", index = 4) + @ColumnWidth(12) + private BigDecimal ownershipRatio; + + /** 购买/评估日期 */ + @ExcelProperty(value = "购买/评估日期", index = 5) + @ColumnWidth(16) + private Date purchaseEvalDate; + + /** 资产原值 */ + @ExcelProperty(value = "资产原值", index = 6) + @ColumnWidth(16) + private BigDecimal originalValue; + + /** 当前估值 */ + @ExcelProperty(value = "当前估值*", index = 7) + @ColumnWidth(16) + @Required + private BigDecimal currentValue; + + /** 估值截止日期 */ + @ExcelProperty(value = "估值截止日期", index = 8) + @ColumnWidth(16) + private Date valuationDate; + + /** 资产状态 */ + @ExcelProperty(value = "资产状态*", index = 9) + @ColumnWidth(14) + @DictDropdown(dictType = "ccdi_asset_status") + @Required + private String assetStatus; + + /** 备注 */ + @ExcelProperty(value = "备注", index = 10) + @ColumnWidth(28) + private String remarks; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java index aad259e..4a23b58 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java @@ -15,8 +15,8 @@ import java.math.BigDecimal; @Schema(description = "亲属资产信息导入失败记录") public class AssetImportFailureVO { - /** 关系人证件号 */ - @Schema(description = "关系人证件号") + /** 亲属证件号 */ + @Schema(description = "亲属证件号") private String personId; /** 资产大类 */ diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/BaseStaffAssetImportFailureVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/BaseStaffAssetImportFailureVO.java new file mode 100644 index 0000000..90a7e55 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/BaseStaffAssetImportFailureVO.java @@ -0,0 +1,49 @@ +package com.ruoyi.info.collection.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 员工资产信息导入失败记录VO + * + * @author ruoyi + * @date 2026-03-13 + */ +@Data +@Schema(description = "员工资产信息导入失败记录") +public class BaseStaffAssetImportFailureVO { + + /** 员工身份证号 */ + @Schema(description = "员工身份证号") + private String personId; + + /** 资产大类 */ + @Schema(description = "资产大类") + private String assetMainType; + + /** 资产小类 */ + @Schema(description = "资产小类") + private String assetSubType; + + /** 资产名称 */ + @Schema(description = "资产名称") + private String assetName; + + /** 产权占比 */ + @Schema(description = "产权占比") + private BigDecimal ownershipRatio; + + /** 当前估值 */ + @Schema(description = "当前估值") + private BigDecimal currentValue; + + /** 资产状态 */ + @Schema(description = "资产状态") + private String assetStatus; + + /** 错误信息 */ + @Schema(description = "错误信息") + private String errorMessage; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java index 93356fc..759994f 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java @@ -75,14 +75,6 @@ public interface CcdiAssetInfoMapper extends BaseMapper { */ int insertBatch(@Param("list") List list); - /** - * 按资产实际持有人证件号查询员工本人归属候选 - * - * @param personIds 资产实际持有人证件号列表 - * @return 归属映射 - */ - List> selectOwnerCandidatesByPersonIds(@Param("personIds") List personIds); - /** * 按关系人证件号查询归属员工候选 * @@ -90,4 +82,12 @@ public interface CcdiAssetInfoMapper extends BaseMapper { * @return 归属映射 */ List> selectOwnerCandidatesByRelationCertNos(@Param("relationCertNos") List relationCertNos); + + /** + * 按员工身份证号查询员工本人归属候选 + * + * @param idCards 员工身份证号列表 + * @return 归属映射 + */ + List> selectOwnerCandidatesByBaseStaffIdCards(@Param("idCards") List idCards); } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoImportService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoImportService.java index 0c223cd..d2f52af 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoImportService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoImportService.java @@ -7,7 +7,7 @@ import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import java.util.List; /** - * 员工资产信息异步导入 服务层 + * 亲属资产信息异步导入 服务层 * * @author ruoyi * @date 2026-03-12 @@ -23,7 +23,7 @@ public interface ICcdiAssetInfoImportService { String importAssetInfo(List excelList); /** - * 异步导入员工资产数据 + * 异步导入亲属资产数据 * * @param excelList Excel实体列表 * @param taskId 任务ID diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffAssetImportService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffAssetImportService.java new file mode 100644 index 0000000..990d1df --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffAssetImportService.java @@ -0,0 +1,49 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.BaseStaffAssetImportFailureVO; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; + +import java.util.List; + +/** + * 员工资产信息异步导入 服务层 + * + * @author ruoyi + * @date 2026-03-13 + */ +public interface ICcdiBaseStaffAssetImportService { + + /** + * 启动异步导入任务 + * + * @param excelList Excel实体列表 + * @return 任务ID + */ + String importAssetInfo(List excelList); + + /** + * 异步导入员工资产数据 + * + * @param excelList Excel实体列表 + * @param taskId 任务ID + * @param userName 用户名 + */ + void importAssetInfoAsync(List excelList, String taskId, String userName); + + /** + * 查询导入状态 + * + * @param taskId 任务ID + * @return 导入状态 + */ + ImportStatusVO getImportStatus(String taskId); + + /** + * 查询导入失败记录 + * + * @param taskId 任务ID + * @return 失败记录列表 + */ + List getImportFailures(String taskId); +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java index 12c8737..3079054 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java @@ -27,7 +27,6 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; /** * 亲属资产信息异步导入服务层处理 @@ -97,10 +96,10 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi validateExcel(excel); Set familyIds = ownerMap.get(excel.getPersonId()); if (familyIds == null || familyIds.isEmpty()) { - throw new RuntimeException("未找到资产归属员工"); + throw new RuntimeException("未找到亲属资产归属员工"); } if (familyIds.size() > 1) { - throw new RuntimeException("资产归属员工不唯一"); + throw new RuntimeException("亲属资产归属员工不唯一"); } CcdiAssetInfo assetInfo = new CcdiAssetInfo(); @@ -167,16 +166,7 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi if (personIds == null || personIds.isEmpty()) { return result; } - List> selfMappings = assetInfoMapper.selectOwnerCandidatesByPersonIds(personIds); - mergeOwnerMappings(result, selfMappings); - - Set selfMatchedIds = result.keySet(); - List relationPersonIds = personIds.stream() - .filter(personId -> !selfMatchedIds.contains(personId)) - .toList(); - if (!relationPersonIds.isEmpty()) { - mergeOwnerMappings(result, assetInfoMapper.selectOwnerCandidatesByRelationCertNos(relationPersonIds)); - } + mergeOwnerMappings(result, assetInfoMapper.selectOwnerCandidatesByRelationCertNos(personIds)); return result; } @@ -196,7 +186,7 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi private void validateExcel(CcdiAssetInfoExcel excel) { if (StringUtils.isEmpty(excel.getPersonId())) { - throw new RuntimeException("资产实际持有人证件号不能为空"); + throw new RuntimeException("亲属证件号不能为空"); } if (StringUtils.isEmpty(excel.getAssetMainType())) { throw new RuntimeException("资产大类不能为空"); diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java new file mode 100644 index 0000000..1d21ec6 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java @@ -0,0 +1,226 @@ +package com.ruoyi.info.collection.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.BaseStaffAssetImportFailureVO; +import com.ruoyi.info.collection.domain.vo.ImportResult; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; +import com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper; +import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * 员工资产信息异步导入服务层处理 + * + * @author ruoyi + * @date 2026-03-13 + */ +@Service +@EnableAsync +public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetImportService { + + private static final String STATUS_KEY_PREFIX = "import:baseStaffAsset:"; + + @Resource + private CcdiAssetInfoMapper assetInfoMapper; + + @Resource + private RedisTemplate redisTemplate; + + @Lazy + @Resource + private ICcdiBaseStaffAssetImportService baseStaffAssetImportService; + + @Override + @Transactional + public String importAssetInfo(List excelList) { + if (excelList == null || excelList.isEmpty()) { + throw new RuntimeException("至少需要一条数据"); + } + + String taskId = UUID.randomUUID().toString(); + Map statusData = new HashMap<>(); + statusData.put("taskId", taskId); + statusData.put("status", "PROCESSING"); + statusData.put("totalCount", excelList.size()); + statusData.put("successCount", 0); + statusData.put("failureCount", 0); + statusData.put("progress", 0); + statusData.put("startTime", System.currentTimeMillis()); + statusData.put("message", "正在处理..."); + + String statusKey = STATUS_KEY_PREFIX + taskId; + redisTemplate.opsForHash().putAll(statusKey, statusData); + redisTemplate.expire(statusKey, 7, TimeUnit.DAYS); + + baseStaffAssetImportService.importAssetInfoAsync(excelList, taskId, currentUserName()); + return taskId; + } + + @Override + @Async + @Transactional + public void importAssetInfoAsync(List excelList, String taskId, String userName) { + List successList = new ArrayList<>(); + List failures = new ArrayList<>(); + + List personIds = excelList.stream() + .map(CcdiBaseStaffAssetInfoExcel::getPersonId) + .filter(StringUtils::isNotEmpty) + .distinct() + .toList(); + + Map> ownerMap = buildOwnerMap(personIds); + + for (CcdiBaseStaffAssetInfoExcel excel : excelList) { + try { + validateExcel(excel); + Set familyIds = ownerMap.get(excel.getPersonId()); + if (familyIds == null || familyIds.isEmpty()) { + throw new RuntimeException("员工资产导入仅支持员工本人证件号"); + } + + CcdiAssetInfo assetInfo = new CcdiAssetInfo(); + BeanUtils.copyProperties(excel, assetInfo); + assetInfo.setFamilyId(excel.getPersonId()); + assetInfo.setPersonId(excel.getPersonId()); + assetInfo.setCreateBy(userName); + assetInfo.setUpdateBy(userName); + successList.add(assetInfo); + } catch (Exception e) { + BaseStaffAssetImportFailureVO failureVO = new BaseStaffAssetImportFailureVO(); + BeanUtils.copyProperties(excel, failureVO); + failureVO.setErrorMessage(e.getMessage()); + failures.add(failureVO); + } + } + + if (!successList.isEmpty()) { + assetInfoMapper.insertBatch(successList); + } + + if (!failures.isEmpty()) { + redisTemplate.opsForValue().set(STATUS_KEY_PREFIX + taskId + ":failures", failures, 7, TimeUnit.DAYS); + } + + ImportResult result = new ImportResult(); + result.setTotalCount(excelList.size()); + result.setSuccessCount(successList.size()); + result.setFailureCount(failures.size()); + updateImportStatus(taskId, failures.isEmpty() ? "SUCCESS" : "PARTIAL_SUCCESS", result); + } + + @Override + public ImportStatusVO getImportStatus(String taskId) { + String key = STATUS_KEY_PREFIX + taskId; + if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) { + throw new RuntimeException("任务不存在或已过期"); + } + + Map statusMap = redisTemplate.opsForHash().entries(key); + ImportStatusVO statusVO = new ImportStatusVO(); + statusVO.setTaskId((String) statusMap.get("taskId")); + statusVO.setStatus((String) statusMap.get("status")); + statusVO.setTotalCount((Integer) statusMap.get("totalCount")); + statusVO.setSuccessCount((Integer) statusMap.get("successCount")); + statusVO.setFailureCount((Integer) statusMap.get("failureCount")); + statusVO.setProgress((Integer) statusMap.get("progress")); + statusVO.setStartTime((Long) statusMap.get("startTime")); + statusVO.setEndTime((Long) statusMap.get("endTime")); + statusVO.setMessage((String) statusMap.get("message")); + return statusVO; + } + + @Override + public List getImportFailures(String taskId) { + Object failuresObj = redisTemplate.opsForValue().get(STATUS_KEY_PREFIX + taskId + ":failures"); + if (failuresObj == null) { + return List.of(); + } + return JSON.parseArray(JSON.toJSONString(failuresObj), BaseStaffAssetImportFailureVO.class); + } + + private Map> buildOwnerMap(List personIds) { + Map> result = new LinkedHashMap<>(); + if (personIds == null || personIds.isEmpty()) { + return result; + } + mergeOwnerMappings(result, assetInfoMapper.selectOwnerCandidatesByBaseStaffIdCards(personIds)); + return result; + } + + private void mergeOwnerMappings(Map> result, List> mappings) { + if (mappings == null) { + return; + } + for (Map mapping : mappings) { + String personId = mapping.get("personId"); + String familyId = mapping.get("familyId"); + if (StringUtils.isEmpty(personId) || StringUtils.isEmpty(familyId)) { + continue; + } + result.computeIfAbsent(personId, key -> new java.util.LinkedHashSet<>()).add(familyId); + } + } + + private void validateExcel(CcdiBaseStaffAssetInfoExcel excel) { + if (StringUtils.isEmpty(excel.getPersonId())) { + throw new RuntimeException("员工身份证号不能为空"); + } + if (StringUtils.isEmpty(excel.getAssetMainType())) { + throw new RuntimeException("资产大类不能为空"); + } + if (StringUtils.isEmpty(excel.getAssetSubType())) { + throw new RuntimeException("资产小类不能为空"); + } + if (StringUtils.isEmpty(excel.getAssetName())) { + throw new RuntimeException("资产名称不能为空"); + } + if (excel.getCurrentValue() == null) { + throw new RuntimeException("当前估值不能为空"); + } + if (StringUtils.isEmpty(excel.getAssetStatus())) { + throw new RuntimeException("资产状态不能为空"); + } + } + + private void updateImportStatus(String taskId, String status, ImportResult result) { + Map statusData = new HashMap<>(); + statusData.put("status", status); + statusData.put("successCount", result.getSuccessCount()); + statusData.put("failureCount", result.getFailureCount()); + statusData.put("progress", 100); + statusData.put("endTime", System.currentTimeMillis()); + statusData.put("message", "SUCCESS".equals(status) + ? "全部成功!共导入" + result.getTotalCount() + "条数据" + : "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); + redisTemplate.opsForHash().putAll(STATUS_KEY_PREFIX + taskId, statusData); + } + + private String currentUserName() { + try { + return SecurityUtils.getUsername(); + } catch (Exception e) { + return "system"; + } + } +} diff --git a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml index cc6dfbc..a88d29b 100644 --- a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml +++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml @@ -77,17 +77,6 @@ - - + + diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiAssetInfoControllerTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiAssetInfoControllerTest.java index c2c2e1f..79b145f 100644 --- a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiAssetInfoControllerTest.java +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiAssetInfoControllerTest.java @@ -109,11 +109,11 @@ class CcdiAssetInfoControllerTest { } @Test - void importTemplate_shouldUseGenericAssetTemplateName() { + void importTemplate_shouldUseFamilyAssetTemplateName() { try (MockedStatic mocked = mockStatic(EasyExcelUtil.class)) { controller.importTemplate(null); - mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(null, CcdiAssetInfoExcel.class, "资产信息")); + mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(null, CcdiAssetInfoExcel.class, "亲属资产信息")); } } } diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportControllerTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportControllerTest.java new file mode 100644 index 0000000..07b64b4 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportControllerTest.java @@ -0,0 +1,119 @@ +package com.ruoyi.info.collection.controller; + +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.BaseStaffAssetImportFailureVO; +import com.ruoyi.info.collection.domain.vo.ImportResultVO; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; +import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService; +import com.ruoyi.info.collection.utils.EasyExcelUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CcdiBaseStaffAssetImportControllerTest { + + @InjectMocks + private CcdiBaseStaffAssetImportController controller; + + @Mock + private ICcdiBaseStaffAssetImportService baseStaffAssetImportService; + + @Test + void importData_shouldReturnWarnWhenExcelHasNoRows() throws Exception { + MockMultipartFile file = new MockMultipartFile( + "file", + "base-staff-asset-empty.xlsx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "empty".getBytes(StandardCharsets.UTF_8) + ); + + try (MockedStatic mocked = mockStatic(EasyExcelUtil.class)) { + mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class))) + .thenReturn(List.of()); + + AjaxResult result = controller.importData(file); + + assertEquals(HttpStatus.WARN, result.get(AjaxResult.CODE_TAG)); + assertEquals("至少需要一条数据", result.get(AjaxResult.MSG_TAG)); + } + } + + @Test + void importData_shouldReturnSuccessWhenTaskCreated() throws Exception { + MockMultipartFile file = new MockMultipartFile( + "file", + "base-staff-asset.xlsx", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "asset".getBytes(StandardCharsets.UTF_8) + ); + CcdiBaseStaffAssetInfoExcel excel = new CcdiBaseStaffAssetInfoExcel(); + excel.setPersonId("320101199001010011"); + when(baseStaffAssetImportService.importAssetInfo(List.of(excel))).thenReturn("task-1"); + + try (MockedStatic mocked = mockStatic(EasyExcelUtil.class)) { + mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class))) + .thenReturn(List.of(excel)); + + AjaxResult result = controller.importData(file); + + assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG)); + assertEquals("导入任务已提交,正在后台处理", result.get(AjaxResult.MSG_TAG)); + ImportResultVO data = (ImportResultVO) result.get(AjaxResult.DATA_TAG); + assertEquals("task-1", data.getTaskId()); + } + } + + @Test + void getImportStatus_shouldDelegateToImportService() { + ImportStatusVO statusVO = new ImportStatusVO(); + statusVO.setTaskId("task-2"); + when(baseStaffAssetImportService.getImportStatus("task-2")).thenReturn(statusVO); + + AjaxResult result = controller.getImportStatus("task-2"); + + assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG)); + assertEquals(statusVO, result.get(AjaxResult.DATA_TAG)); + } + + @Test + void getImportFailures_shouldReturnPagedRows() { + BaseStaffAssetImportFailureVO failure1 = new BaseStaffAssetImportFailureVO(); + failure1.setPersonId("A1"); + BaseStaffAssetImportFailureVO failure2 = new BaseStaffAssetImportFailureVO(); + failure2.setPersonId("A2"); + when(baseStaffAssetImportService.getImportFailures("task-3")).thenReturn(List.of(failure1, failure2)); + + TableDataInfo result = controller.getImportFailures("task-3", 2, 1); + + assertEquals(2, result.getTotal()); + assertEquals(1, result.getRows().size()); + assertEquals("A2", ((BaseStaffAssetImportFailureVO) result.getRows().get(0)).getPersonId()); + } + + @Test + void importTemplate_shouldUseBaseStaffAssetTemplateName() { + try (MockedStatic mocked = mockStatic(EasyExcelUtil.class)) { + controller.importTemplate(null); + + mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(null, CcdiBaseStaffAssetInfoExcel.class, "员工资产信息")); + } + } +} diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java index 38e66c0..fb2b27c 100644 --- a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java @@ -97,19 +97,21 @@ class CcdiAssetInfoImportServiceImplTest { } @Test - void importAssetInfoAsync_shouldResolveFamilyIdFromEmployeeIdCardBeforeFamilyRelation() { + void importAssetInfoAsync_shouldFailWhenEmployeeIdCardIsUsedForFamilyAssetImport() { CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产"); when(redisTemplate.opsForHash()).thenReturn(hashOperations); - when(assetInfoMapper.selectOwnerCandidatesByPersonIds(List.of("320101199001010011"))) - .thenReturn(List.of(owner("320101199001010011", "320101199001010011"))); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199001010011"))) + .thenReturn(List.of()); service.importAssetInfoAsync(List.of(excel), "task-self", "tester"); - ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); - verify(assetInfoMapper).insertBatch(captor.capture()); - assertEquals("320101199001010011", captor.getValue().get(0).getFamilyId()); - assertEquals("320101199001010011", captor.getValue().get(0).getPersonId()); - verify(assetInfoMapper, never()).selectOwnerCandidatesByRelationCertNos(any()); + verify(assetInfoMapper, never()).insertBatch(any()); + ArgumentCaptor failureCaptor = ArgumentCaptor.forClass(Object.class); + verify(valueOperations).set(eq("import:assetInfo:task-self:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS)); + AssetImportFailureVO failure = (AssetImportFailureVO) ((List) failureCaptor.getValue()).get(0); + assertEquals("320101199001010011", failure.getPersonId()); + assertTrue(failure.getErrorMessage().contains("未找到亲属资产归属员工")); } @Test @@ -150,7 +152,7 @@ class CcdiAssetInfoImportServiceImplTest { assertEquals(1, failures.size()); AssetImportFailureVO failure = (AssetImportFailureVO) failures.get(0); assertEquals("320101199001010099", failure.getPersonId()); - assertTrue(failure.getErrorMessage().contains("未找到资产归属员工")); + assertTrue(failure.getErrorMessage().contains("未找到亲属资产归属员工")); } @Test @@ -170,7 +172,7 @@ class CcdiAssetInfoImportServiceImplTest { ArgumentCaptor failureCaptor = ArgumentCaptor.forClass(Object.class); verify(valueOperations).set(eq("import:assetInfo:task-4:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS)); AssetImportFailureVO failure = (AssetImportFailureVO) ((List) failureCaptor.getValue()).get(0); - assertTrue(failure.getErrorMessage().contains("资产归属员工不唯一")); + assertTrue(failure.getErrorMessage().contains("亲属资产归属员工不唯一")); } @Test @@ -191,7 +193,7 @@ class CcdiAssetInfoImportServiceImplTest { )); AssetImportFailureVO failureVO = new AssetImportFailureVO(); failureVO.setPersonId("320101199001010099"); - failureVO.setErrorMessage("未找到资产归属员工"); + failureVO.setErrorMessage("未找到亲属资产归属员工"); when(valueOperations.get("import:assetInfo:task-5:failures")).thenReturn(List.of(failureVO)); ImportStatusVO statusVO = service.getImportStatus("task-5"); diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java new file mode 100644 index 0000000..09349a9 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java @@ -0,0 +1,145 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.BaseStaffAssetImportFailureVO; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; +import com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper; +import com.ruoyi.info.collection.service.impl.CcdiBaseStaffAssetImportServiceImpl; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyMap; +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 CcdiBaseStaffAssetImportServiceImplTest { + + @InjectMocks + private CcdiBaseStaffAssetImportServiceImpl service; + + @Mock + private CcdiAssetInfoMapper assetInfoMapper; + + @Mock + private RedisTemplate redisTemplate; + + @Mock + private ICcdiBaseStaffAssetImportService baseStaffAssetImportService; + + @Mock + private HashOperations hashOperations; + + @Mock + private ValueOperations valueOperations; + + @Test + void importAssetInfo_shouldUseDedicatedBaseStaffAssetTaskKeys() { + List excelList = List.of(buildExcel("320101199001010011", "房产")); + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + + String taskId = service.importAssetInfo(excelList); + + verify(hashOperations).putAll(eq("import:baseStaffAsset:" + taskId), anyMap()); + verify(redisTemplate).expire("import:baseStaffAsset:" + taskId, 7, TimeUnit.DAYS); + verify(baseStaffAssetImportService).importAssetInfoAsync(eq(excelList), eq(taskId), any()); + } + + @Test + void importAssetInfoAsync_shouldImportWhenEmployeeIdCardExists() { + CcdiBaseStaffAssetInfoExcel excel = buildExcel("320101199001010011", "房产"); + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + when(assetInfoMapper.selectOwnerCandidatesByBaseStaffIdCards(List.of("320101199001010011"))) + .thenReturn(List.of(owner("320101199001010011", "320101199001010011"))); + + service.importAssetInfoAsync(List.of(excel), "task-1", "tester"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(assetInfoMapper).insertBatch(captor.capture()); + assertEquals("320101199001010011", captor.getValue().get(0).getFamilyId()); + assertEquals("320101199001010011", captor.getValue().get(0).getPersonId()); + } + + @Test + void importAssetInfoAsync_shouldFailWhenFamilyCertificateIsUsed() { + CcdiBaseStaffAssetInfoExcel excel = buildExcel("320101199201010022", "车辆"); + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(assetInfoMapper.selectOwnerCandidatesByBaseStaffIdCards(List.of("320101199201010022"))) + .thenReturn(List.of()); + + service.importAssetInfoAsync(List.of(excel), "task-2", "tester"); + + verify(assetInfoMapper, never()).insertBatch(any()); + ArgumentCaptor 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("320101199201010022", failure.getPersonId()); + assertTrue(failure.getErrorMessage().contains("员工资产导入仅支持员工本人证件号")); + } + + @Test + void getImportStatusAndFailures_shouldUseBaseStaffAssetPrefixes() { + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(redisTemplate.hasKey("import:baseStaffAsset:task-3")).thenReturn(true); + when(hashOperations.entries("import:baseStaffAsset:task-3")).thenReturn(Map.of( + "taskId", "task-3", + "status", "SUCCESS", + "totalCount", 1, + "successCount", 1, + "failureCount", 0, + "progress", 100, + "startTime", 1L, + "endTime", 2L, + "message", "全部成功" + )); + BaseStaffAssetImportFailureVO failureVO = new BaseStaffAssetImportFailureVO(); + failureVO.setPersonId("320101199001010099"); + failureVO.setErrorMessage("员工资产导入仅支持员工本人证件号"); + when(valueOperations.get("import:baseStaffAsset:task-3:failures")).thenReturn(List.of(failureVO)); + + ImportStatusVO statusVO = service.getImportStatus("task-3"); + List failures = service.getImportFailures("task-3"); + + assertEquals("task-3", statusVO.getTaskId()); + assertEquals("SUCCESS", statusVO.getStatus()); + assertNotNull(failures); + assertEquals(1, failures.size()); + assertEquals("320101199001010099", failures.get(0).getPersonId()); + } + + private CcdiBaseStaffAssetInfoExcel buildExcel(String personId, String assetMainType) { + CcdiBaseStaffAssetInfoExcel excel = new CcdiBaseStaffAssetInfoExcel(); + excel.setPersonId(personId); + excel.setAssetMainType(assetMainType); + excel.setAssetSubType(assetMainType + "小类"); + excel.setAssetName(assetMainType + "名称"); + excel.setCurrentValue(new BigDecimal("100.00")); + excel.setAssetStatus("正常"); + return excel; + } + + private Map owner(String personId, String familyId) { + return Map.of("personId", personId, "familyId", familyId); + } +} diff --git a/docs/plans/2026-03-13-employee-family-asset-import-split-design.md b/docs/plans/2026-03-13-employee-family-asset-import-split-design.md index 67d49ac..1012a9c 100644 --- a/docs/plans/2026-03-13-employee-family-asset-import-split-design.md +++ b/docs/plans/2026-03-13-employee-family-asset-import-split-design.md @@ -239,3 +239,10 @@ - 独立提交员工资产导入新增改动 - 独立提交亲属资产导入收敛改动 - 任一阶段出现回归,可按提交粒度回退 + +## 实现状态 + +- 2026-03-13 已完成后端拆分实现 +- 已新增员工资产独立导入接口 `/ccdi/baseStaff/asset/*` +- 已将 `/ccdi/assetInfo/*` 收敛为亲属资产专用接口 +- 已通过后端定向测试验证员工与亲属两套导入链路、模板名称和失败文案拆分生效