From bac3cf094ee3ddf119edb4e79e035e6b4472d3fe Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Thu, 12 Mar 2026 16:33:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=91=98=E5=B7=A5=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E4=BF=A1=E6=81=AF=E5=90=8E=E7=AB=AF=E5=AE=9E=E6=96=BD?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ccdi-info-collection/pom.xml | 7 + .../controller/CcdiAssetInfoController.java | 102 ++++++++ .../info/collection/domain/CcdiAssetInfo.java | 83 +++++++ .../domain/dto/CcdiAssetInfoDTO.java | 86 +++++++ .../domain/dto/CcdiBaseStaffAddDTO.java | 4 + .../domain/dto/CcdiBaseStaffEditDTO.java | 4 + .../domain/excel/CcdiAssetInfoExcel.java | 85 +++++++ .../domain/vo/AssetImportFailureVO.java | 49 ++++ .../collection/domain/vo/CcdiAssetInfoVO.java | 70 ++++++ .../collection/domain/vo/CcdiBaseStaffVO.java | 4 + .../mapper/CcdiAssetInfoMapper.java | 73 ++++++ .../service/ICcdiAssetInfoImportService.java | 49 ++++ .../service/ICcdiAssetInfoService.java | 47 ++++ .../impl/CcdiAssetInfoImportServiceImpl.java | 227 ++++++++++++++++++ .../impl/CcdiAssetInfoServiceImpl.java | 84 +++++++ .../impl/CcdiBaseStaffServiceImpl.java | 34 ++- .../info/collection/CcdiAssetInfoMapper.xml | 96 ++++++++ .../mapper/CcdiAssetInfoMapperTest.java | 175 ++++++++++++++ .../CcdiAssetInfoImportServiceImplTest.java | 213 ++++++++++++++++ .../service/CcdiAssetInfoServiceImplTest.java | 105 ++++++++ ...iBaseStaffServiceAssetAggregationTest.java | 45 ++++ .../service/CcdiBaseStaffServiceImplTest.java | 185 ++++++++++++++ 22 files changed, 1825 insertions(+), 2 deletions(-) create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiAssetInfo.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiAssetInfoDTO.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiAssetInfoVO.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoImportService.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoService.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoServiceImpl.java create mode 100644 ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapperTest.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoServiceImplTest.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceAssetAggregationTest.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceImplTest.java diff --git a/ccdi-info-collection/pom.xml b/ccdi-info-collection/pom.xml index df57343..34473cc 100644 --- a/ccdi-info-collection/pom.xml +++ b/ccdi-info-collection/pom.xml @@ -45,6 +45,13 @@ springdoc-openapi-starter-webmvc-ui + + + org.springframework.boot + spring-boot-starter-test + test + + 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 new file mode 100644 index 0000000..3f58e33 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java @@ -0,0 +1,102 @@ +package com.ruoyi.info.collection.controller; + +import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.AssetImportFailureVO; +import com.ruoyi.info.collection.domain.vo.ImportResultVO; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; +import com.ruoyi.info.collection.service.ICcdiAssetInfoImportService; +import com.ruoyi.info.collection.utils.EasyExcelUtil; +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 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-12 + */ +@Tag(name = "员工资产信息管理") +@RestController +@RequestMapping("/ccdi/assetInfo") +public class CcdiAssetInfoController extends BaseController { + + @Resource + private ICcdiAssetInfoImportService assetInfoImportService; + + /** + * 下载导入模板 + */ + @Operation(summary = "下载资产导入模板") + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiAssetInfoExcel.class, "员工资产信息"); + } + + /** + * 导入员工资产信息 + */ + @Operation(summary = "导入员工资产信息") + @PreAuthorize("@ss.hasPermi('ccdi:baseStaff:import')") + @Log(title = "员工资产信息", businessType = BusinessType.IMPORT) + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file) throws Exception { + List list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiAssetInfoExcel.class); + if (list == null || list.isEmpty()) { + return error("至少需要一条数据"); + } + + String taskId = assetInfoImportService.importAssetInfo(list); + ImportResultVO result = new ImportResultVO(); + result.setTaskId(taskId); + result.setStatus("PROCESSING"); + result.setMessage("导入任务已提交,正在后台处理"); + return AjaxResult.success("导入任务已提交,正在后台处理", result); + } + + /** + * 查询导入状态 + */ + @Operation(summary = "查询员工资产导入状态") + @PreAuthorize("@ss.hasPermi('ccdi:baseStaff:import')") + @GetMapping("/importStatus/{taskId}") + public AjaxResult getImportStatus(@PathVariable String taskId) { + return success(assetInfoImportService.getImportStatus(taskId)); + } + + /** + * 查询导入失败记录 + */ + @Operation(summary = "查询员工资产导入失败记录") + @PreAuthorize("@ss.hasPermi('ccdi:baseStaff:import')") + @GetMapping("/importFailures/{taskId}") + public TableDataInfo getImportFailures( + @PathVariable String taskId, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + List failures = assetInfoImportService.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/CcdiAssetInfo.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiAssetInfo.java new file mode 100644 index 0000000..4370429 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiAssetInfo.java @@ -0,0 +1,83 @@ +package com.ruoyi.info.collection.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 员工资产信息对象 ccdi_asset_info + * + * @author ruoyi + * @date 2026-03-12 + */ +@Data +@TableName("ccdi_asset_info") +public class CcdiAssetInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 资产ID */ + @TableId(type = IdType.AUTO) + private Long assetId; + + /** 归属员工身份证号 */ + private String familyId; + + /** 资产实际持有人身份证号 */ + private String personId; + + /** 资产大类 */ + private String assetMainType; + + /** 资产小类 */ + private String assetSubType; + + /** 资产名称 */ + private String assetName; + + /** 产权占比 */ + private BigDecimal ownershipRatio; + + /** 购买/评估日期 */ + private Date purchaseEvalDate; + + /** 资产原值 */ + private BigDecimal originalValue; + + /** 当前估值 */ + private BigDecimal currentValue; + + /** 估值截止日期 */ + private Date valuationDate; + + /** 资产状态 */ + private String assetStatus; + + /** 备注 */ + private String remarks; + + /** 创建者 */ + @TableField(fill = FieldFill.INSERT) + private String createBy; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** 更新者 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateBy; + + /** 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiAssetInfoDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiAssetInfoDTO.java new file mode 100644 index 0000000..03b2fa5 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiAssetInfoDTO.java @@ -0,0 +1,86 @@ +package com.ruoyi.info.collection.domain.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 员工资产信息DTO + * + * @author ruoyi + * @date 2026-03-12 + */ +@Data +@Schema(description = "员工资产信息") +public class CcdiAssetInfoDTO 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 = "资产实际持有人身份证号") + private String personId; + + /** 资产大类 */ + @NotBlank(message = "资产大类不能为空") + @Size(max = 20, message = "资产大类长度不能超过20个字符") + @Schema(description = "资产大类") + private String assetMainType; + + /** 资产小类 */ + @NotBlank(message = "资产小类不能为空") + @Size(max = 50, message = "资产小类长度不能超过50个字符") + @Schema(description = "资产小类") + private String assetSubType; + + /** 资产名称 */ + @NotBlank(message = "资产名称不能为空") + @Size(max = 200, message = "资产名称长度不能超过200个字符") + @Schema(description = "资产名称") + private String assetName; + + /** 产权占比 */ + @Schema(description = "产权占比") + private BigDecimal ownershipRatio; + + /** 购买/评估日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "购买/评估日期") + private Date purchaseEvalDate; + + /** 资产原值 */ + @Schema(description = "资产原值") + private BigDecimal originalValue; + + /** 当前估值 */ + @NotNull(message = "当前估值不能为空") + @Schema(description = "当前估值") + private BigDecimal currentValue; + + /** 估值截止日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "估值截止日期") + private Date valuationDate; + + /** 资产状态 */ + @NotBlank(message = "资产状态不能为空") + @Size(max = 10, message = "资产状态长度不能超过10个字符") + @Schema(description = "资产状态") + private String assetStatus; + + /** 备注 */ + @Size(max = 500, message = "备注长度不能超过500个字符") + @Schema(description = "备注") + private String remarks; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffAddDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffAddDTO.java index 73517c6..eb34a0f 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffAddDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffAddDTO.java @@ -9,6 +9,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; +import java.util.List; /** * 员工信息新增 DTO @@ -51,4 +52,7 @@ public class CcdiBaseStaffAddDTO implements Serializable { /** 状态 */ @NotBlank(message = "状态不能为空") private String status; + + /** 资产信息列表 */ + private List assetInfoList; } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffEditDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffEditDTO.java index 688eadc..a2c4c61 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffEditDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffEditDTO.java @@ -9,6 +9,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; +import java.util.List; /** * 员工信息编辑 DTO @@ -49,4 +50,7 @@ public class CcdiBaseStaffEditDTO implements Serializable { /** 状态 */ private String status; + + /** 资产信息列表 */ + private List assetInfoList; } 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 new file mode 100644 index 0000000..1091983 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java @@ -0,0 +1,85 @@ +package com.ruoyi.info.collection.domain.excel; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.ruoyi.common.annotation.Required; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 员工资产信息Excel导入导出对象 + * + * @author ruoyi + * @date 2026-03-12 + */ +@Data +public class CcdiAssetInfoExcel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 资产实际持有人身份证号 */ + @ExcelProperty(value = "资产实际持有人身份证号*", index = 0) + @ColumnWidth(22) + @Required + 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) + @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 new file mode 100644 index 0000000..6347c6b --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.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-12 + */ +@Data +@Schema(description = "员工资产信息导入失败记录") +public class AssetImportFailureVO { + + /** 资产实际持有人身份证号 */ + @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/domain/vo/CcdiAssetInfoVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiAssetInfoVO.java new file mode 100644 index 0000000..5ca36f5 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiAssetInfoVO.java @@ -0,0 +1,70 @@ +package com.ruoyi.info.collection.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 员工资产信息VO + * + * @author ruoyi + * @date 2026-03-12 + */ +@Data +@Schema(description = "员工资产信息") +public class CcdiAssetInfoVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 资产实际持有人身份证号 */ + @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; + + /** 购买/评估日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "购买/评估日期") + private Date purchaseEvalDate; + + /** 资产原值 */ + @Schema(description = "资产原值") + private BigDecimal originalValue; + + /** 当前估值 */ + @Schema(description = "当前估值") + private BigDecimal currentValue; + + /** 估值截止日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "估值截止日期") + private Date valuationDate; + + /** 资产状态 */ + @Schema(description = "资产状态") + private String assetStatus; + + /** 备注 */ + @Schema(description = "备注") + private String remarks; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiBaseStaffVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiBaseStaffVO.java index 18a9ad2..abfa2b2 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiBaseStaffVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiBaseStaffVO.java @@ -5,6 +5,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.Date; +import java.util.List; /** * 员工信息 VO @@ -56,4 +57,7 @@ public class CcdiBaseStaffVO implements Serializable { /** 更新者 */ private String updateBy; + + /** 资产信息列表 */ + private List assetInfoList; } 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 new file mode 100644 index 0000000..ccd4098 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java @@ -0,0 +1,73 @@ +package com.ruoyi.info.collection.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * 员工资产信息 数据层 + * + * @author ruoyi + * @date 2026-03-12 + */ +public interface CcdiAssetInfoMapper extends BaseMapper { + + /** + * 按归属员工身份证号查询资产列表 + * + * @param familyId 归属员工身份证号 + * @return 资产列表 + */ + List selectByFamilyId(@Param("familyId") String familyId); + + /** + * 按归属员工身份证号删除资产 + * + * @param familyId 归属员工身份证号 + * @return 影响行数 + */ + int deleteByFamilyId(@Param("familyId") String familyId); + + /** + * 批量删除归属员工资产 + * + * @param familyIds 归属员工身份证号列表 + * @return 影响行数 + */ + int deleteByFamilyIds(@Param("familyIds") List familyIds); + + /** + * 按资产实际持有人身份证号查询资产列表 + * + * @param personId 资产实际持有人身份证号 + * @return 资产列表 + */ + List selectByPersonId(@Param("personId") String personId); + + /** + * 批量插入资产数据 + * + * @param list 资产列表 + * @return 影响行数 + */ + int insertBatch(@Param("list") List list); + + /** + * 按员工身份证号查询归属信息 + * + * @param personIds 资产实际持有人身份证号列表 + * @return 归属映射 + */ + List> selectOwnerByEmployeeIdCards(@Param("personIds") List personIds); + + /** + * 按员工家庭关系身份证号查询归属信息 + * + * @param personIds 资产实际持有人身份证号列表 + * @return 归属映射 + */ + List> selectOwnerByFamilyRelationIdCards(@Param("personIds") List personIds); +} 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 new file mode 100644 index 0000000..0c223cd --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoImportService.java @@ -0,0 +1,49 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.AssetImportFailureVO; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; + +import java.util.List; + +/** + * 员工资产信息异步导入 服务层 + * + * @author ruoyi + * @date 2026-03-12 + */ +public interface ICcdiAssetInfoImportService { + + /** + * 启动异步导入任务 + * + * @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/ICcdiAssetInfoService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoService.java new file mode 100644 index 0000000..bdd79b0 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoService.java @@ -0,0 +1,47 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO; + +import java.util.List; + +/** + * 员工资产信息 服务层 + * + * @author ruoyi + * @date 2026-03-12 + */ +public interface ICcdiAssetInfoService { + + /** + * 按归属员工身份证号查询资产列表 + * + * @param familyId 归属员工身份证号 + * @return 资产列表 + */ + List selectByFamilyId(String familyId); + + /** + * 按归属员工身份证号覆盖资产列表 + * + * @param familyId 归属员工身份证号 + * @param assetInfoList 资产列表 + */ + void replaceByFamilyId(String familyId, List assetInfoList); + + /** + * 删除单个员工资产 + * + * @param familyId 归属员工身份证号 + * @return 影响行数 + */ + int deleteByFamilyId(String familyId); + + /** + * 批量删除员工资产 + * + * @param familyIds 归属员工身份证号列表 + * @return 影响行数 + */ + int deleteByFamilyIds(List familyIds); +} 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 new file mode 100644 index 0000000..7ae25a8 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java @@ -0,0 +1,227 @@ +package com.ruoyi.info.collection.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.AssetImportFailureVO; +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.ICcdiAssetInfoImportService; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +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; +import java.util.stream.Collectors; + +/** + * 员工资产信息异步导入服务层处理 + * + * @author ruoyi + * @date 2026-03-12 + */ +@Service +@EnableAsync +public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportService { + + private static final String STATUS_KEY_PREFIX = "import:assetInfo:"; + + @Resource + private CcdiAssetInfoMapper assetInfoMapper; + + @Resource + private RedisTemplate redisTemplate; + + @Lazy + @Resource + private ICcdiAssetInfoImportService assetInfoImportService; + + @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); + + assetInfoImportService.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(CcdiAssetInfoExcel::getPersonId) + .filter(StringUtils::isNotEmpty) + .distinct() + .toList(); + + Map> ownerMap = buildOwnerMap(personIds); + + for (CcdiAssetInfoExcel excel : excelList) { + try { + validateExcel(excel); + Set familyIds = ownerMap.get(excel.getPersonId()); + if (familyIds == null || familyIds.isEmpty()) { + throw new RuntimeException("未找到资产归属员工"); + } + if (familyIds.size() > 1) { + throw new RuntimeException("资产归属员工不唯一"); + } + + CcdiAssetInfo assetInfo = new CcdiAssetInfo(); + BeanUtils.copyProperties(excel, assetInfo); + assetInfo.setFamilyId(familyIds.iterator().next()); + assetInfo.setCreateBy(userName); + assetInfo.setUpdateBy(userName); + successList.add(assetInfo); + } catch (Exception e) { + AssetImportFailureVO failureVO = new AssetImportFailureVO(); + 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), AssetImportFailureVO.class); + } + + private Map> buildOwnerMap(List personIds) { + Map> result = new LinkedHashMap<>(); + mergeOwnerMappings(result, assetInfoMapper.selectOwnerByEmployeeIdCards(personIds)); + mergeOwnerMappings(result, assetInfoMapper.selectOwnerByFamilyRelationIdCards(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(CcdiAssetInfoExcel 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/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoServiceImpl.java new file mode 100644 index 0000000..f28a4b1 --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoServiceImpl.java @@ -0,0 +1,84 @@ +package com.ruoyi.info.collection.service.impl; + +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO; +import com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper; +import com.ruoyi.info.collection.service.ICcdiAssetInfoService; +import com.ruoyi.common.utils.StringUtils; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; + +/** + * 员工资产信息 服务层处理 + * + * @author ruoyi + * @date 2026-03-12 + */ +@Service +public class CcdiAssetInfoServiceImpl implements ICcdiAssetInfoService { + + @Resource + private CcdiAssetInfoMapper assetInfoMapper; + + @Override + public List selectByFamilyId(String familyId) { + return assetInfoMapper.selectByFamilyId(familyId); + } + + @Override + @Transactional + public void replaceByFamilyId(String familyId, List assetInfoList) { + assetInfoMapper.deleteByFamilyId(familyId); + if (assetInfoList == null || assetInfoList.isEmpty()) { + return; + } + + List saveList = assetInfoList.stream() + .filter(item -> !isEmptyRow(item)) + .map(item -> toEntity(familyId, item)) + .toList(); + + if (!saveList.isEmpty()) { + assetInfoMapper.insertBatch(saveList); + } + } + + @Override + public int deleteByFamilyId(String familyId) { + return assetInfoMapper.deleteByFamilyId(familyId); + } + + @Override + public int deleteByFamilyIds(List familyIds) { + if (familyIds == null || familyIds.isEmpty()) { + return 0; + } + return assetInfoMapper.deleteByFamilyIds(familyIds); + } + + private CcdiAssetInfo toEntity(String familyId, CcdiAssetInfoDTO dto) { + CcdiAssetInfo assetInfo = new CcdiAssetInfo(); + BeanUtils.copyProperties(dto, assetInfo); + assetInfo.setFamilyId(familyId); + return assetInfo; + } + + private boolean isEmptyRow(CcdiAssetInfoDTO dto) { + return StringUtils.isEmpty(dto.getPersonId()) + && StringUtils.isEmpty(dto.getAssetMainType()) + && StringUtils.isEmpty(dto.getAssetSubType()) + && StringUtils.isEmpty(dto.getAssetName()) + && dto.getCurrentValue() == null + && StringUtils.isEmpty(dto.getAssetStatus()) + && dto.getOwnershipRatio() == null + && dto.getPurchaseEvalDate() == null + && dto.getOriginalValue() == null + && dto.getValuationDate() == null + && StringUtils.isEmpty(dto.getRemarks()); + } +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffServiceImpl.java index 302a99e..8e43d8c 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffServiceImpl.java @@ -7,10 +7,12 @@ 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.CcdiBaseStaffExcel; +import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO; import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO; import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO; import com.ruoyi.info.collection.enums.EmployeeStatus; import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper; +import com.ruoyi.info.collection.service.ICcdiAssetInfoService; import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService; import com.ruoyi.info.collection.service.ICcdiBaseStaffService; import com.ruoyi.common.utils.StringUtils; @@ -40,6 +42,9 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService { @Resource private RedisTemplate redisTemplate; + @Resource + private ICcdiAssetInfoService assetInfoService; + /** * 查询员工列表 * @@ -104,7 +109,15 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService { @Override public CcdiBaseStaffVO selectBaseStaffById(Long staffId) { CcdiBaseStaff staff = baseStaffMapper.selectById(staffId); - return convertToVO(staff); + CcdiBaseStaffVO vo = convertToVO(staff); + if (staff != null) { + vo.setAssetInfoList(assetInfoService.selectByFamilyId(staff.getIdCard()).stream().map(asset -> { + CcdiAssetInfoVO assetInfoVO = new CcdiAssetInfoVO(); + BeanUtils.copyProperties(asset, assetInfoVO); + return assetInfoVO; + }).toList()); + } + return vo; } /** @@ -131,6 +144,7 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService { CcdiBaseStaff staff = new CcdiBaseStaff(); BeanUtils.copyProperties(addDTO, staff); int result = baseStaffMapper.insert(staff); + assetInfoService.replaceByFamilyId(addDTO.getIdCard(), addDTO.getAssetInfoList()); return result; } @@ -144,6 +158,11 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService { @Override @Transactional public int updateBaseStaff(CcdiBaseStaffEditDTO editDTO) { + CcdiBaseStaff existing = baseStaffMapper.selectById(editDTO.getStaffId()); + if (existing == null) { + throw new RuntimeException("员工不存在"); + } + // 检查身份证号唯一性(排除自己) if (StringUtils.isNotEmpty(editDTO.getIdCard())) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); @@ -158,6 +177,11 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService { BeanUtils.copyProperties(editDTO, staff); int result = baseStaffMapper.updateById(staff); + if (!StringUtils.equals(existing.getIdCard(), editDTO.getIdCard())) { + assetInfoService.deleteByFamilyId(existing.getIdCard()); + } + assetInfoService.replaceByFamilyId(editDTO.getIdCard(), editDTO.getAssetInfoList()); + return result; } @@ -170,7 +194,13 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService { @Override @Transactional public int deleteBaseStaffByIds(Long[] staffIds) { - return baseStaffMapper.deleteBatchIds(List.of(staffIds)); + List idList = List.of(staffIds); + List familyIds = baseStaffMapper.selectBatchIds(idList).stream() + .map(CcdiBaseStaff::getIdCard) + .filter(StringUtils::isNotEmpty) + .toList(); + assetInfoService.deleteByFamilyIds(familyIds); + return baseStaffMapper.deleteBatchIds(idList); } /** 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 new file mode 100644 index 0000000..59c8e3b --- /dev/null +++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DELETE FROM ccdi_asset_info + WHERE family_id = #{familyId} + + + + DELETE FROM ccdi_asset_info + WHERE family_id IN + + #{familyId} + + + + + INSERT INTO ccdi_asset_info + (family_id, person_id, asset_main_type, asset_sub_type, asset_name, + ownership_ratio, purchase_eval_date, original_value, current_value, + valuation_date, asset_status, remarks, create_by, create_time, update_by, update_time) + VALUES + + (#{item.familyId}, #{item.personId}, #{item.assetMainType}, #{item.assetSubType}, #{item.assetName}, + #{item.ownershipRatio}, #{item.purchaseEvalDate}, #{item.originalValue}, #{item.currentValue}, + #{item.valuationDate}, #{item.assetStatus}, #{item.remarks}, #{item.createBy}, NOW(), #{item.updateBy}, NOW()) + + + + + + + + diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapperTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapperTest.java new file mode 100644 index 0000000..b68db32 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapperTest.java @@ -0,0 +1,175 @@ +package com.ruoyi.info.collection.mapper; + +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +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.math.BigDecimal; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CcdiAssetInfoMapperTest { + + private static final String RESOURCE = "mapper/info/collection/CcdiAssetInfoMapper.xml"; + + @Test + void selectByFamilyId_shouldFilterByFamilyId() throws Exception { + MappedStatement mappedStatement = loadMappedStatement( + "com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.selectByFamilyId"); + + String sql = renderSql(mappedStatement, Map.of("familyId", "320101199001010011")); + + assertTrue(sql.contains("FROM ccdi_asset_info"), sql); + assertTrue(sql.contains("WHERE family_id = ?"), sql); + } + + @Test + void selectByPersonId_shouldFilterByPersonId() throws Exception { + MappedStatement mappedStatement = loadMappedStatement( + "com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.selectByPersonId"); + + String sql = renderSql(mappedStatement, Map.of("personId", "320101199201010022")); + + assertTrue(sql.contains("FROM ccdi_asset_info"), sql); + assertTrue(sql.contains("WHERE person_id = ?"), sql); + } + + @Test + void deleteByFamilyIds_shouldRenderInClause() throws Exception { + MappedStatement mappedStatement = loadMappedStatement( + "com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.deleteByFamilyIds"); + + String sql = renderSql(mappedStatement, Map.of("familyIds", List.of("A", "B"))); + + assertTrue(sql.contains("DELETE FROM ccdi_asset_info"), sql); + assertTrue(sql.contains("family_id IN"), sql); + assertFalse(sql.contains("IN ()"), sql); + } + + @Test + void insertBatch_shouldIncludeAllBusinessColumns() throws Exception { + MappedStatement mappedStatement = loadMappedStatement( + "com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.insertBatch"); + + CcdiAssetInfo assetInfo = new CcdiAssetInfo(); + assetInfo.setFamilyId("320101199001010011"); + assetInfo.setPersonId("320101199201010022"); + assetInfo.setAssetMainType("车辆"); + assetInfo.setAssetSubType("小汽车"); + assetInfo.setAssetName("家庭车辆"); + assetInfo.setCurrentValue(new BigDecimal("100000.00")); + assetInfo.setAssetStatus("正常"); + + String sql = renderSql(mappedStatement, Map.of("list", List.of(assetInfo))); + + assertTrue(sql.contains("INSERT INTO ccdi_asset_info"), sql); + assertTrue(sql.contains("family_id"), sql); + assertTrue(sql.contains("person_id"), sql); + assertTrue(sql.contains("asset_main_type"), sql); + assertTrue(sql.contains("current_value"), sql); + assertTrue(sql.contains("asset_status"), sql); + } + + @Test + void ownerLookupQueries_shouldResolveFromEmployeeAndFamilyRelation() throws Exception { + MappedStatement employeeStatement = loadMappedStatement( + "com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.selectOwnerByEmployeeIdCards"); + MappedStatement familyStatement = loadMappedStatement( + "com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.selectOwnerByFamilyRelationIdCards"); + + String employeeSql = renderSql(employeeStatement, Map.of("personIds", List.of("A"))); + String familySql = renderSql(familyStatement, Map.of("personIds", List.of("B"))); + + assertTrue(employeeSql.contains("FROM ccdi_base_staff"), employeeSql); + assertTrue(employeeSql.contains("id_card AS personId"), employeeSql); + assertTrue(employeeSql.contains("id_card AS familyId"), employeeSql); + + assertTrue(familySql.contains("FROM ccdi_staff_fmy_relation"), familySql); + assertTrue(familySql.contains("relation_cert_no AS personId"), familySql); + assertTrue(familySql.contains("person_id AS familyId"), familySql); + assertTrue(familySql.contains("is_emp_family = 1"), familySql); + } + + 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(CcdiAssetInfoMapper.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 params) { + BoundSql boundSql = mappedStatement.getBoundSql(new HashMap<>(params)); + return boundSql.getSql().replaceAll("\\s+", " ").trim(); + } + + private void registerTypeAliases(TypeAliasRegistry typeAliasRegistry) { + typeAliasRegistry.registerAlias("map", Map.class); + } + + private static class NoOpDataSource implements DataSource { + + @Override + public java.sql.Connection getConnection() { + throw new UnsupportedOperationException("Not required for SQL rendering tests"); + } + + @Override + public java.sql.Connection getConnection(String username, String password) { + throw new UnsupportedOperationException("Not required for SQL rendering tests"); + } + + @Override + public java.io.PrintWriter getLogWriter() { + return null; + } + + @Override + public void setLogWriter(java.io.PrintWriter out) { + } + + @Override + public void setLoginTimeout(int seconds) { + } + + @Override + public int getLoginTimeout() { + return 0; + } + + @Override + public java.util.logging.Logger getParentLogger() { + return java.util.logging.Logger.getGlobal(); + } + + @Override + public T unwrap(Class iface) { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public boolean isWrapperFor(Class iface) { + return false; + } + } +} 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 new file mode 100644 index 0000000..55cc199 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java @@ -0,0 +1,213 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel; +import com.ruoyi.info.collection.domain.vo.AssetImportFailureVO; +import com.ruoyi.info.collection.domain.vo.ImportStatusVO; +import com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper; +import com.ruoyi.info.collection.service.impl.CcdiAssetInfoImportServiceImpl; +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.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +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.anyLong; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CcdiAssetInfoImportServiceImplTest { + + @InjectMocks + private CcdiAssetInfoImportServiceImpl service; + + @Mock + private CcdiAssetInfoMapper assetInfoMapper; + + @Mock + private RedisTemplate redisTemplate; + + @Mock + private ICcdiAssetInfoImportService assetInfoImportService; + + @Mock + private HashOperations hashOperations; + + @Mock + private ValueOperations valueOperations; + + @Test + void assetInfoExcel_shouldExcludeAssetIdAndFamilyId() { + Set fieldNames = Arrays.stream(CcdiAssetInfoExcel.class.getDeclaredFields()) + .map(field -> field.getName()) + .collect(Collectors.toSet()); + + assertFalse(fieldNames.contains("assetId")); + assertFalse(fieldNames.contains("familyId")); + assertTrue(fieldNames.contains("personId")); + } + + @Test + void importAssetInfo_shouldUseDedicatedAssetTaskKeys() { + List excelList = List.of(buildExcel("320101199001010011", "房产")); + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + + String taskId = service.importAssetInfo(excelList); + + verify(hashOperations).putAll(eq("import:assetInfo:" + taskId), anyMap()); + verify(redisTemplate).expire("import:assetInfo:" + taskId, 7, TimeUnit.DAYS); + verify(assetInfoImportService).importAssetInfoAsync(eq(excelList), eq(taskId), any()); + } + + @Test + void importAssetInfoAsync_shouldResolveFamilyIdFromEmployeeIdCard() { + CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产"); + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + when(assetInfoMapper.selectOwnerByEmployeeIdCards(List.of("320101199001010011"))) + .thenReturn(List.of(owner("320101199001010011", "320101199001010011"))); + when(assetInfoMapper.selectOwnerByFamilyRelationIdCards(List.of("320101199001010011"))) + .thenReturn(List.of()); + + 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_shouldResolveFamilyIdFromFamilyRelationIdCard() { + CcdiAssetInfoExcel excel = buildExcel("320101199201010022", "车辆"); + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + when(assetInfoMapper.selectOwnerByEmployeeIdCards(List.of("320101199201010022"))) + .thenReturn(List.of()); + when(assetInfoMapper.selectOwnerByFamilyRelationIdCards(List.of("320101199201010022"))) + .thenReturn(List.of(owner("320101199201010022", "320101199001010011"))); + + service.importAssetInfoAsync(List.of(excel), "task-2", "tester"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(assetInfoMapper).insertBatch(captor.capture()); + assertEquals("320101199001010011", captor.getValue().get(0).getFamilyId()); + assertEquals("320101199201010022", captor.getValue().get(0).getPersonId()); + } + + @Test + void importAssetInfoAsync_shouldStoreFailureRowsOnlyForBadRecords() { + CcdiAssetInfoExcel good = buildExcel("320101199001010011", "房产"); + CcdiAssetInfoExcel bad = buildExcel("320101199001010099", "车辆"); + + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(assetInfoMapper.selectOwnerByEmployeeIdCards(List.of("320101199001010011", "320101199001010099"))) + .thenReturn(List.of(owner("320101199001010011", "320101199001010011"))); + when(assetInfoMapper.selectOwnerByFamilyRelationIdCards(List.of("320101199001010011", "320101199001010099"))) + .thenReturn(List.of()); + + service.importAssetInfoAsync(List.of(good, bad), "task-3", "tester"); + + ArgumentCaptor> insertCaptor = ArgumentCaptor.forClass(List.class); + verify(assetInfoMapper).insertBatch(insertCaptor.capture()); + assertEquals(1, insertCaptor.getValue().size()); + assertEquals("320101199001010011", insertCaptor.getValue().get(0).getFamilyId()); + + ArgumentCaptor failureCaptor = ArgumentCaptor.forClass(Object.class); + verify(valueOperations).set(eq("import:assetInfo:task-3:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS)); + List failures = (List) failureCaptor.getValue(); + assertEquals(1, failures.size()); + AssetImportFailureVO failure = (AssetImportFailureVO) failures.get(0); + assertEquals("320101199001010099", failure.getPersonId()); + assertTrue(failure.getErrorMessage().contains("未找到资产归属员工")); + } + + @Test + void importAssetInfoAsync_shouldFailWhenOwnerIsAmbiguous() { + CcdiAssetInfoExcel excel = buildExcel("320101199201010022", "车辆"); + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(assetInfoMapper.selectOwnerByEmployeeIdCards(List.of("320101199201010022"))) + .thenReturn(List.of( + owner("320101199201010022", "320101199001010011"), + owner("320101199201010022", "320101199001010033") + )); + when(assetInfoMapper.selectOwnerByFamilyRelationIdCards(List.of("320101199201010022"))) + .thenReturn(List.of()); + + service.importAssetInfoAsync(List.of(excel), "task-4", "tester"); + + verify(assetInfoMapper, never()).insertBatch(any()); + 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("资产归属员工不唯一")); + } + + @Test + void getImportStatusAndFailures_shouldUseAssetPrefixes() { + when(redisTemplate.opsForHash()).thenReturn(hashOperations); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(redisTemplate.hasKey("import:assetInfo:task-5")).thenReturn(true); + when(hashOperations.entries("import:assetInfo:task-5")).thenReturn(Map.of( + "taskId", "task-5", + "status", "SUCCESS", + "totalCount", 1, + "successCount", 1, + "failureCount", 0, + "progress", 100, + "startTime", 1L, + "endTime", 2L, + "message", "全部成功" + )); + AssetImportFailureVO failureVO = new AssetImportFailureVO(); + failureVO.setPersonId("320101199001010099"); + failureVO.setErrorMessage("未找到资产归属员工"); + when(valueOperations.get("import:assetInfo:task-5:failures")).thenReturn(List.of(failureVO)); + + ImportStatusVO statusVO = service.getImportStatus("task-5"); + List failures = service.getImportFailures("task-5"); + + assertEquals("task-5", statusVO.getTaskId()); + assertEquals("SUCCESS", statusVO.getStatus()); + assertNotNull(failures); + assertEquals(1, failures.size()); + assertEquals("320101199001010099", failures.get(0).getPersonId()); + } + + private CcdiAssetInfoExcel buildExcel(String personId, String assetMainType) { + CcdiAssetInfoExcel excel = new CcdiAssetInfoExcel(); + 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/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoServiceImplTest.java new file mode 100644 index 0000000..bdd049d --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoServiceImplTest.java @@ -0,0 +1,105 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO; +import com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper; +import com.ruoyi.info.collection.service.impl.CcdiAssetInfoServiceImpl; +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 java.math.BigDecimal; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.anyList; +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 CcdiAssetInfoServiceImplTest { + + @InjectMocks + private CcdiAssetInfoServiceImpl service; + + @Mock + private CcdiAssetInfoMapper assetInfoMapper; + + @Test + void selectByFamilyId_shouldReturnMapperResult() { + List expected = List.of(new CcdiAssetInfo()); + when(assetInfoMapper.selectByFamilyId("320101199001010011")).thenReturn(expected); + + List result = service.selectByFamilyId("320101199001010011"); + + assertSame(expected, result); + } + + @Test + void replaceByFamilyId_shouldDeleteThenInsertNormalizedRows() { + CcdiAssetInfoDTO selfOwnedAsset = buildDto("320101199001010011", "房产"); + CcdiAssetInfoDTO familyOwnedAsset = buildDto("320101199201010022", "车辆"); + + service.replaceByFamilyId("320101199001010011", List.of(selfOwnedAsset, familyOwnedAsset)); + + verify(assetInfoMapper).deleteByFamilyId("320101199001010011"); + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(assetInfoMapper).insertBatch(captor.capture()); + + List savedList = captor.getValue(); + assertEquals(2, savedList.size()); + assertEquals("320101199001010011", savedList.get(0).getFamilyId()); + assertEquals("320101199001010011", savedList.get(0).getPersonId()); + assertEquals("320101199001010011", savedList.get(1).getFamilyId()); + assertEquals("320101199201010022", savedList.get(1).getPersonId()); + assertEquals("房产", savedList.get(0).getAssetMainType()); + assertEquals("车辆", savedList.get(1).getAssetMainType()); + } + + @Test + void replaceByFamilyId_shouldIgnoreEmptyRows() { + CcdiAssetInfoDTO emptyRow = new CcdiAssetInfoDTO(); + + service.replaceByFamilyId("320101199001010011", List.of(emptyRow)); + + verify(assetInfoMapper).deleteByFamilyId("320101199001010011"); + verify(assetInfoMapper, never()).insertBatch(anyList()); + } + + @Test + void deleteByFamilyId_shouldDelegateToMapper() { + when(assetInfoMapper.deleteByFamilyId("320101199001010011")).thenReturn(1); + + int result = service.deleteByFamilyId("320101199001010011"); + + assertEquals(1, result); + } + + @Test + void deleteByFamilyIds_shouldDelegateToMapper() { + List familyIds = List.of("320101199001010011", "320101199001010033"); + when(assetInfoMapper.deleteByFamilyIds(familyIds)).thenReturn(2); + + int result = service.deleteByFamilyIds(familyIds); + + assertEquals(2, result); + verify(assetInfoMapper).deleteByFamilyIds(eq(familyIds)); + } + + private CcdiAssetInfoDTO buildDto(String personId, String assetMainType) { + CcdiAssetInfoDTO dto = new CcdiAssetInfoDTO(); + dto.setPersonId(personId); + dto.setAssetMainType(assetMainType); + dto.setAssetSubType(assetMainType + "小类"); + dto.setAssetName(assetMainType + "名称"); + dto.setCurrentValue(new BigDecimal("100.00")); + dto.setAssetStatus("正常"); + return dto; + } +} diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceAssetAggregationTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceAssetAggregationTest.java new file mode 100644 index 0000000..d668760 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceAssetAggregationTest.java @@ -0,0 +1,45 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO; +import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO; +import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO; +import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO; +import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertSame; + +class CcdiBaseStaffServiceAssetAggregationTest { + + @Test + void addDto_shouldExposeAssetInfoList() { + CcdiBaseStaffAddDTO addDTO = new CcdiBaseStaffAddDTO(); + List assetInfoList = List.of(new CcdiAssetInfoDTO()); + + addDTO.setAssetInfoList(assetInfoList); + + assertSame(assetInfoList, addDTO.getAssetInfoList()); + } + + @Test + void editDto_shouldExposeAssetInfoList() { + CcdiBaseStaffEditDTO editDTO = new CcdiBaseStaffEditDTO(); + List assetInfoList = List.of(new CcdiAssetInfoDTO()); + + editDTO.setAssetInfoList(assetInfoList); + + assertSame(assetInfoList, editDTO.getAssetInfoList()); + } + + @Test + void staffVo_shouldExposeAssetInfoList() { + CcdiBaseStaffVO staffVO = new CcdiBaseStaffVO(); + List assetInfoList = List.of(new CcdiAssetInfoVO()); + + staffVO.setAssetInfoList(assetInfoList); + + assertSame(assetInfoList, staffVO.getAssetInfoList()); + } +} diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceImplTest.java new file mode 100644 index 0000000..b199f23 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceImplTest.java @@ -0,0 +1,185 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.CcdiAssetInfo; +import com.ruoyi.info.collection.domain.CcdiBaseStaff; +import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO; +import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO; +import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO; +import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO; +import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper; +import com.ruoyi.info.collection.service.impl.CcdiBaseStaffServiceImpl; +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.math.BigDecimal; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.any; +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 CcdiBaseStaffServiceImplTest { + + @InjectMocks + private CcdiBaseStaffServiceImpl service; + + @Mock + private CcdiBaseStaffMapper baseStaffMapper; + + @Mock + private ICcdiBaseStaffImportService importAsyncService; + + @Mock + private RedisTemplate redisTemplate; + + @Mock + private ICcdiAssetInfoService assetInfoService; + + @Test + void insertBaseStaff_shouldPersistEmployeeThenReplaceAssetsUsingEmployeeIdCard() { + CcdiBaseStaffAddDTO addDTO = new CcdiBaseStaffAddDTO(); + addDTO.setStaffId(1001L); + addDTO.setName("张三"); + addDTO.setDeptId(10L); + addDTO.setIdCard("320101199001010011"); + addDTO.setPhone("13812345678"); + addDTO.setStatus("0"); + addDTO.setAssetInfoList(List.of( + buildAssetDto("320101199001010011", "房产"), + buildAssetDto("320101199201010022", "车辆") + )); + + when(baseStaffMapper.selectById(1001L)).thenReturn(null); + when(baseStaffMapper.selectCount(any())).thenReturn(0L); + when(baseStaffMapper.insert(any(CcdiBaseStaff.class))).thenReturn(1); + + int result = service.insertBaseStaff(addDTO); + + assertEquals(1, result); + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(assetInfoService).replaceByFamilyId(eq("320101199001010011"), captor.capture()); + List savedAssets = captor.getValue(); + assertEquals(2, savedAssets.size()); + assertEquals("320101199001010011", savedAssets.get(0).getPersonId()); + assertEquals("320101199201010022", savedAssets.get(1).getPersonId()); + } + + @Test + void updateBaseStaff_shouldReplaceAssetsForCurrentIdCard() { + CcdiBaseStaff existing = new CcdiBaseStaff(); + existing.setStaffId(1001L); + existing.setIdCard("320101199001010011"); + + CcdiBaseStaffEditDTO editDTO = new CcdiBaseStaffEditDTO(); + editDTO.setStaffId(1001L); + editDTO.setDeptId(10L); + editDTO.setName("张三"); + editDTO.setIdCard("320101199001010011"); + editDTO.setPhone("13812345678"); + editDTO.setStatus("0"); + editDTO.setAssetInfoList(List.of(buildAssetDto("320101199201010022", "车辆"))); + + when(baseStaffMapper.selectById(1001L)).thenReturn(existing); + when(baseStaffMapper.selectCount(any())).thenReturn(0L); + when(baseStaffMapper.updateById(any(CcdiBaseStaff.class))).thenReturn(1); + + int result = service.updateBaseStaff(editDTO); + + assertEquals(1, result); + verify(assetInfoService, never()).deleteByFamilyId("320101199001010011"); + verify(assetInfoService).replaceByFamilyId("320101199001010011", editDTO.getAssetInfoList()); + } + + @Test + void updateBaseStaff_shouldDeleteOldAssetsWhenIdCardChanges() { + CcdiBaseStaff existing = new CcdiBaseStaff(); + existing.setStaffId(1001L); + existing.setIdCard("320101199001010099"); + + CcdiBaseStaffEditDTO editDTO = new CcdiBaseStaffEditDTO(); + editDTO.setStaffId(1001L); + editDTO.setDeptId(10L); + editDTO.setName("张三"); + editDTO.setIdCard("320101199001010011"); + editDTO.setPhone("13812345678"); + editDTO.setStatus("0"); + editDTO.setAssetInfoList(List.of(buildAssetDto("320101199201010022", "车辆"))); + + when(baseStaffMapper.selectById(1001L)).thenReturn(existing); + when(baseStaffMapper.selectCount(any())).thenReturn(0L); + when(baseStaffMapper.updateById(any(CcdiBaseStaff.class))).thenReturn(1); + + service.updateBaseStaff(editDTO); + + verify(assetInfoService).deleteByFamilyId("320101199001010099"); + verify(assetInfoService).replaceByFamilyId("320101199001010011", editDTO.getAssetInfoList()); + } + + @Test + void selectBaseStaffById_shouldReturnAssetInfoList() { + CcdiBaseStaff staff = new CcdiBaseStaff(); + staff.setStaffId(1001L); + staff.setName("张三"); + staff.setIdCard("320101199001010011"); + staff.setStatus("0"); + + CcdiAssetInfo assetInfo = new CcdiAssetInfo(); + assetInfo.setFamilyId("320101199001010011"); + assetInfo.setPersonId("320101199201010022"); + assetInfo.setAssetMainType("车辆"); + assetInfo.setAssetSubType("小汽车"); + assetInfo.setAssetName("家庭车辆"); + assetInfo.setCurrentValue(new BigDecimal("100000.00")); + assetInfo.setAssetStatus("正常"); + + when(baseStaffMapper.selectById(1001L)).thenReturn(staff); + when(assetInfoService.selectByFamilyId("320101199001010011")).thenReturn(List.of(assetInfo)); + + CcdiBaseStaffVO result = service.selectBaseStaffById(1001L); + + assertNotNull(result.getAssetInfoList()); + assertEquals(1, result.getAssetInfoList().size()); + assertEquals("320101199201010022", result.getAssetInfoList().get(0).getPersonId()); + assertEquals("车辆", result.getAssetInfoList().get(0).getAssetMainType()); + } + + @Test + void deleteBaseStaffByIds_shouldCascadeDeleteAssets() { + CcdiBaseStaff staff1 = new CcdiBaseStaff(); + staff1.setStaffId(1001L); + staff1.setIdCard("320101199001010011"); + CcdiBaseStaff staff2 = new CcdiBaseStaff(); + staff2.setStaffId(1002L); + staff2.setIdCard("320101199001010022"); + + when(baseStaffMapper.selectBatchIds(List.of(1001L, 1002L))).thenReturn(List.of(staff1, staff2)); + when(baseStaffMapper.deleteBatchIds(List.of(1001L, 1002L))).thenReturn(2); + + int result = service.deleteBaseStaffByIds(new Long[]{1001L, 1002L}); + + assertEquals(2, result); + verify(assetInfoService).deleteByFamilyIds(List.of("320101199001010011", "320101199001010022")); + } + + private CcdiAssetInfoDTO buildAssetDto(String personId, String assetMainType) { + CcdiAssetInfoDTO dto = new CcdiAssetInfoDTO(); + dto.setPersonId(personId); + dto.setAssetMainType(assetMainType); + dto.setAssetSubType(assetMainType + "小类"); + dto.setAssetName(assetMainType + "名称"); + dto.setCurrentValue(new BigDecimal("100.00")); + dto.setAssetStatus("正常"); + return dto; + } +}