From b7db711906017e7bd0736f1117007145f293ab38 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Fri, 24 Apr 2026 08:55:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=91=98=E5=B7=A5=E4=BA=B2?= =?UTF-8?q?=E5=B1=9E=E5=AE=9E=E4=BD=93=E5=85=B3=E8=81=94=E6=94=B9=E9=80=A0?= =?UTF-8?q?=E5=B9=B6=E6=B8=85=E7=90=86=E6=97=A7=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...CcdiStaffEnterpriseRelationController.java | 54 ++-- .../CcdiStaffEnterpriseRelationAddDTO.java | 12 +- .../CcdiStaffEnterpriseRelationEditDTO.java | 8 +- .../CcdiStaffEnterpriseRelationQueryDTO.java | 16 +- .../CcdiStaffEnterpriseRelationExcel.java | 10 +- .../CcdiStaffEnterpriseRelationOptionVO.java | 37 +++ .../vo/CcdiStaffEnterpriseRelationVO.java | 22 +- ...taffEnterpriseRelationImportFailureVO.java | 12 +- .../CcdiStaffEnterpriseRelationMapper.java | 17 ++ ...iStaffEnterpriseRelationImportService.java | 2 +- .../ICcdiStaffEnterpriseRelationService.java | 9 + ...ffEnterpriseRelationImportServiceImpl.java | 145 +++++---- ...cdiStaffEnterpriseRelationServiceImpl.java | 44 ++- .../impl/CcdiStaffFmyRelationServiceImpl.java | 9 +- .../CcdiStaffEnterpriseRelationMapper.xml | 55 +++- ...CcdiStaffEnterpriseRelationMapperTest.java | 148 +++++++++ ...terpriseRelationImportServiceImplTest.java | 138 +++++++++ ...taffEnterpriseRelationServiceImplTest.java | 156 ++++++++++ .../CcdiStaffFmyRelationServiceImplTest.java | 34 +++ ...mily-enterprise-relation-implementation.md | 156 ++++++++++ ...enterprise-relation-browser-test-record.md | 82 +++++ .../src/api/ccdiStaffEnterpriseRelation.js | 21 +- .../ccdiStaffEnterpriseRelation/index.vue | 288 ++++++++++++------ ...-legacy-staff-enterprise-relation-data.sql | 10 + ...-rename-staff-enterprise-relation-menu.sql | 32 ++ 25 files changed, 1298 insertions(+), 219 deletions(-) create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffEnterpriseRelationOptionVO.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiStaffEnterpriseRelationMapperTest.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationImportServiceImplTest.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationServiceImplTest.java create mode 100644 docs/reports/implementation/2026-04-23-staff-family-enterprise-relation-implementation.md create mode 100644 docs/tests/records/2026-04-23-staff-family-enterprise-relation-browser-test-record.md create mode 100644 sql/migration/2026-04-23-clean-legacy-staff-enterprise-relation-data.sql create mode 100644 sql/migration/2026-04-23-rename-staff-enterprise-relation-menu.sql diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffEnterpriseRelationController.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffEnterpriseRelationController.java index 1ced06e7..e8b738d6 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffEnterpriseRelationController.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffEnterpriseRelationController.java @@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; +import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO; import com.ruoyi.info.collection.domain.vo.ImportResultVO; import com.ruoyi.info.collection.domain.vo.ImportStatusVO; @@ -33,12 +34,12 @@ import java.util.ArrayList; import java.util.List; /** - * 员工实体关系信息Controller + * 员工亲属实体关联Controller * * @author ruoyi * @date 2026-02-09 */ -@Tag(name = "员工实体关系信息管理") +@Tag(name = "员工亲属实体关联管理") @RestController @RequestMapping("/ccdi/staffEnterpriseRelation") public class CcdiStaffEnterpriseRelationController extends BaseController { @@ -50,9 +51,9 @@ public class CcdiStaffEnterpriseRelationController extends BaseController { private ICcdiStaffEnterpriseRelationImportService relationImportService; /** - * 查询员工实体关系列表 + * 查询员工亲属实体关联列表 */ - @Operation(summary = "查询员工实体关系列表") + @Operation(summary = "查询员工亲属实体关联列表") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:list')") @GetMapping("/list") public TableDataInfo list(CcdiStaffEnterpriseRelationQueryDTO queryDTO) { @@ -64,9 +65,20 @@ public class CcdiStaffEnterpriseRelationController extends BaseController { } /** - * 获取员工实体关系详细信息 + * 查询有效员工亲属下拉列表 */ - @Operation(summary = "获取员工实体关系详细信息") + @Operation(summary = "查询有效员工亲属下拉列表") + @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:list')") + @GetMapping("/familyOptions") + public AjaxResult familyOptions(@RequestParam(required = false) String query) { + List list = relationService.selectFamilyOptions(query); + return success(list); + } + + /** + * 获取员工亲属实体关联详细信息 + */ + @Operation(summary = "获取员工亲属实体关联详细信息") @Parameter(name = "id", description = "主键ID", required = true) @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:query')") @GetMapping(value = "/{id}") @@ -75,34 +87,34 @@ public class CcdiStaffEnterpriseRelationController extends BaseController { } /** - * 新增员工实体关系 + * 新增员工亲属实体关联 */ - @Operation(summary = "新增员工实体关系") + @Operation(summary = "新增员工亲属实体关联") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:add')") - @Log(title = "员工实体关系信息", businessType = BusinessType.INSERT) + @Log(title = "员工亲属实体关联", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@Validated @RequestBody CcdiStaffEnterpriseRelationAddDTO addDTO) { return toAjax(relationService.insertRelation(addDTO)); } /** - * 修改员工实体关系 + * 修改员工亲属实体关联 */ - @Operation(summary = "修改员工实体关系") + @Operation(summary = "修改员工亲属实体关联") @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:edit')") - @Log(title = "员工实体关系信息", businessType = BusinessType.UPDATE) + @Log(title = "员工亲属实体关联", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@Validated @RequestBody CcdiStaffEnterpriseRelationEditDTO editDTO) { return toAjax(relationService.updateRelation(editDTO)); } /** - * 删除员工实体关系 + * 删除员工亲属实体关联 */ - @Operation(summary = "删除员工实体关系") + @Operation(summary = "删除员工亲属实体关联") @Parameter(name = "ids", description = "主键ID数组", required = true) @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:remove')") - @Log(title = "员工实体关系信息", businessType = BusinessType.DELETE) + @Log(title = "员工亲属实体关联", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public AjaxResult remove(@PathVariable Long[] ids) { return toAjax(relationService.deleteRelationByIds(ids)); @@ -115,16 +127,16 @@ public class CcdiStaffEnterpriseRelationController extends BaseController { @Operation(summary = "下载导入模板") @PostMapping("/importTemplate") public void importTemplate(HttpServletResponse response) { - EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffEnterpriseRelationExcel.class, "员工实体关系信息"); + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffEnterpriseRelationExcel.class, "员工亲属实体关联"); } /** - * 异步导入员工实体关系 + * 异步导入员工亲属实体关联 */ - @Operation(summary = "异步导入员工实体关系") + @Operation(summary = "异步导入员工亲属实体关联") @Parameter(name = "file", description = "导入文件", required = true) @PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:import')") - @Log(title = "员工实体关系信息", businessType = BusinessType.IMPORT) + @Log(title = "员工亲属实体关联", businessType = BusinessType.IMPORT) @PostMapping("/importData") public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception { List list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffEnterpriseRelationExcel.class); @@ -140,9 +152,9 @@ public class CcdiStaffEnterpriseRelationController extends BaseController { ImportResultVO result = new ImportResultVO(); result.setTaskId(taskId); result.setStatus("PROCESSING"); - result.setMessage("导入任务已提交,正在后台处理"); + result.setMessage("员工亲属实体关联导入任务已提交,正在后台处理"); - return AjaxResult.success("导入任务已提交,正在后台处理", result); + return AjaxResult.success("员工亲属实体关联导入任务已提交,正在后台处理", result); } /** diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationAddDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationAddDTO.java index 4e758ed7..326d7f3e 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationAddDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationAddDTO.java @@ -10,22 +10,22 @@ import java.io.Serial; import java.io.Serializable; /** - * 员工实体关系信息新增DTO + * 员工亲属实体关联新增DTO * * @author ruoyi * @date 2026-02-09 */ @Data -@Schema(description = "员工实体关系信息新增") +@Schema(description = "员工亲属实体关联新增") public class CcdiStaffEnterpriseRelationAddDTO implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 身份证号 */ - @NotBlank(message = "身份证号不能为空") - @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确") - @Schema(description = "身份证号") + /** 亲属身份证号 */ + @NotBlank(message = "亲属身份证号不能为空") + @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "亲属身份证号格式不正确") + @Schema(description = "亲属身份证号") private String personId; /** 关联人在企业的职务 */ diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationEditDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationEditDTO.java index 150e2865..d562832e 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationEditDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationEditDTO.java @@ -10,13 +10,13 @@ import java.io.Serial; import java.io.Serializable; /** - * 员工实体关系信息编辑DTO + * 员工亲属实体关联编辑DTO * * @author ruoyi * @date 2026-02-09 */ @Data -@Schema(description = "员工实体关系信息编辑") +@Schema(description = "员工亲属实体关联编辑") public class CcdiStaffEnterpriseRelationEditDTO implements Serializable { @Serial @@ -27,8 +27,8 @@ public class CcdiStaffEnterpriseRelationEditDTO implements Serializable { @Schema(description = "主键ID") private Long id; - /** 身份证号 */ - @Schema(description = "身份证号(不可修改)") + /** 亲属身份证号 */ + @Schema(description = "亲属身份证号(不可修改)") private String personId; /** 关联人在企业的职务 */ diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationQueryDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationQueryDTO.java index b22195b6..f2902383 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationQueryDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffEnterpriseRelationQueryDTO.java @@ -7,22 +7,30 @@ import java.io.Serial; import java.io.Serializable; /** - * 员工实体关系信息查询DTO + * 员工亲属实体关联查询DTO * * @author ruoyi * @date 2026-02-09 */ @Data -@Schema(description = "员工实体关系信息查询条件") +@Schema(description = "员工亲属实体关联查询条件") public class CcdiStaffEnterpriseRelationQueryDTO implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 身份证号 */ - @Schema(description = "身份证号") + /** 亲属身份证号 */ + @Schema(description = "亲属身份证号") private String personId; + /** 亲属姓名 */ + @Schema(description = "亲属姓名") + private String relationName; + + /** 关联员工 */ + @Schema(description = "关联员工") + private String staffPersonName; + /** 统一社会信用代码 */ @Schema(description = "统一社会信用代码") private String socialCreditCode; diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffEnterpriseRelationExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffEnterpriseRelationExcel.java index 0038167d..8fcd35bd 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffEnterpriseRelationExcel.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffEnterpriseRelationExcel.java @@ -10,23 +10,23 @@ import java.io.Serial; import java.io.Serializable; /** - * 员工实体关系信息Excel导入导出对象 + * 员工亲属实体关联Excel导入导出对象 * * @author ruoyi * @date 2026-02-09 */ @Data -@Schema(description = "员工实体关系信息Excel导入导出对象") +@Schema(description = "员工亲属实体关联Excel导入导出对象") public class CcdiStaffEnterpriseRelationExcel implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 身份证号 */ - @ExcelProperty(value = "身份证号", index = 0) + /** 亲属身份证号 */ + @ExcelProperty(value = "亲属身份证号", index = 0) @ColumnWidth(20) @Required - @Schema(description = "身份证号") + @Schema(description = "亲属身份证号") private String personId; /** 统一社会信用代码 */ diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffEnterpriseRelationOptionVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffEnterpriseRelationOptionVO.java new file mode 100644 index 00000000..aaf64b7f --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffEnterpriseRelationOptionVO.java @@ -0,0 +1,37 @@ +package com.ruoyi.info.collection.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 员工亲属实体关联下拉选项VO + * + * @author ruoyi + * @date 2026-04-23 + */ +@Data +@Schema(description = "员工亲属实体关联下拉选项") +public class CcdiStaffEnterpriseRelationOptionVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 亲属身份证号 */ + @Schema(description = "亲属身份证号") + private String relationCertNo; + + /** 亲属姓名 */ + @Schema(description = "亲属姓名") + private String relationName; + + /** 关联员工身份证号 */ + @Schema(description = "关联员工身份证号") + private String staffPersonId; + + /** 关联员工姓名 */ + @Schema(description = "关联员工姓名") + private String staffPersonName; +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffEnterpriseRelationVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffEnterpriseRelationVO.java index 53fd8af9..95f02030 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffEnterpriseRelationVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffEnterpriseRelationVO.java @@ -9,13 +9,13 @@ import java.io.Serializable; import java.util.Date; /** - * 员工实体关系信息VO + * 员工亲属实体关联VO * * @author ruoyi * @date 2026-02-09 */ @Data -@Schema(description = "员工实体关系信息") +@Schema(description = "员工亲属实体关联") public class CcdiStaffEnterpriseRelationVO implements Serializable { @Serial @@ -25,13 +25,21 @@ public class CcdiStaffEnterpriseRelationVO implements Serializable { @Schema(description = "主键ID") private Long id; - /** 身份证号 */ - @Schema(description = "身份证号") + /** 亲属身份证号 */ + @Schema(description = "亲属身份证号") private String personId; - /** 员工姓名 */ - @Schema(description = "员工姓名") - private String personName; + /** 亲属姓名 */ + @Schema(description = "亲属姓名") + private String relationName; + + /** 关联员工身份证号 */ + @Schema(description = "关联员工身份证号") + private String staffPersonId; + + /** 关联员工姓名 */ + @Schema(description = "关联员工姓名") + private String staffPersonName; /** 关联人在企业的职务 */ @Schema(description = "关联人在企业的职务") diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/StaffEnterpriseRelationImportFailureVO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/StaffEnterpriseRelationImportFailureVO.java index 8da53428..7acc4d02 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/StaffEnterpriseRelationImportFailureVO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/StaffEnterpriseRelationImportFailureVO.java @@ -7,22 +7,26 @@ import java.io.Serial; import java.io.Serializable; /** - * 员工实体关系信息导入失败记录VO + * 员工亲属实体关联导入失败记录VO * * @author ruoyi * @date 2026-02-09 */ @Data -@Schema(description = "员工实体关系信息导入失败记录") +@Schema(description = "员工亲属实体关联导入失败记录") public class StaffEnterpriseRelationImportFailureVO implements Serializable { @Serial private static final long serialVersionUID = 1L; - /** 身份证号 */ - @Schema(description = "身份证号") + /** 亲属身份证号 */ + @Schema(description = "亲属身份证号") private String personId; + /** 亲属姓名 */ + @Schema(description = "亲属姓名") + private String relationName; + /** 统一社会信用代码 */ @Schema(description = "统一社会信用代码") private String socialCreditCode; diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiStaffEnterpriseRelationMapper.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiStaffEnterpriseRelationMapper.java index 8aeaaa96..f9c23087 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiStaffEnterpriseRelationMapper.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiStaffEnterpriseRelationMapper.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; +import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -38,6 +39,14 @@ public interface CcdiStaffEnterpriseRelationMapper extends BaseMapper selectFamilyOptions(@Param("query") String query); + /** * 判断身份证号和统一社会信用代码的组合是否已存在 * @@ -57,6 +66,14 @@ public interface CcdiStaffEnterpriseRelationMapper extends BaseMapper batchExistsByCombinations(@Param("combinations") List combinations); + /** + * 根据亲属身份证号批量置无效 + * + * @param personId 亲属身份证号 + * @return 影响行数 + */ + int invalidateByFamilyCertNo(@Param("personId") String personId); + /** * 批量插入员工实体关系数据 * diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffEnterpriseRelationImportService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffEnterpriseRelationImportService.java index ab75d2c2..c2a24903 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffEnterpriseRelationImportService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffEnterpriseRelationImportService.java @@ -7,7 +7,7 @@ import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureV import java.util.List; /** - * 员工实体关系信息异步导入服务层 + * 员工亲属实体关联异步导入服务层 * * @author ruoyi * @date 2026-02-09 diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffEnterpriseRelationService.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffEnterpriseRelationService.java index 87f5766b..039939d6 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffEnterpriseRelationService.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffEnterpriseRelationService.java @@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; +import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO; import java.util.List; @@ -42,6 +43,14 @@ public interface ICcdiStaffEnterpriseRelationService { */ List selectRelationListForExport(CcdiStaffEnterpriseRelationQueryDTO queryDTO); + /** + * 查询有效员工亲属下拉选项 + * + * @param query 搜索关键词 + * @return 下拉选项 + */ + List selectFamilyOptions(String query); + /** * 查询员工实体关系详情 * diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationImportServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationImportServiceImpl.java index dfd55d3e..e80a01fc 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationImportServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationImportServiceImpl.java @@ -2,14 +2,14 @@ package com.ruoyi.info.collection.service.impl; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.ruoyi.info.collection.domain.CcdiBaseStaff; import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation; +import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; import com.ruoyi.info.collection.domain.vo.ImportResult; import com.ruoyi.info.collection.domain.vo.ImportStatusVO; import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureVO; -import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper; +import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper; import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper; import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService; import com.ruoyi.info.collection.utils.ImportLogUtils; @@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** - * 员工实体关系信息异步导入服务层处理 + * 员工亲属实体关联异步导入服务层处理 * * @author ruoyi * @date 2026-02-09 @@ -47,7 +47,7 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE private RedisTemplate redisTemplate; @Resource - private CcdiBaseStaffMapper baseStaffMapper; + private CcdiStaffFmyRelationMapper familyRelationMapper; @Override @Async @@ -56,37 +56,48 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE long startTime = System.currentTimeMillis(); // 记录导入开始 - ImportLogUtils.logImportStart(log, taskId, "员工实体关系", excelList.size(), userName); + ImportLogUtils.logImportStart(log, taskId, "员工亲属实体关联", excelList.size(), userName); List newRecords = new ArrayList<>(); List failures = new ArrayList<>(); - // 批量验证员工身份证号是否存在 Set excelPersonIds = excelList.stream() .map(CcdiStaffEnterpriseRelationExcel::getPersonId) .filter(StringUtils::isNotEmpty) .collect(Collectors.toSet()); - Set existingPersonIds = new HashSet<>(); + Map validFamilies = new HashMap<>(); + Set knownFamilyCertNos = new HashSet<>(); + Map familyNameMap = new HashMap<>(); if (!excelPersonIds.isEmpty()) { - ImportLogUtils.logBatchQueryStart(log, taskId, "员工身份证号", excelPersonIds.size()); + ImportLogUtils.logBatchQueryStart(log, taskId, "员工亲属关系", excelPersonIds.size()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.select(CcdiBaseStaff::getIdCard) - .in(CcdiBaseStaff::getIdCard, excelPersonIds); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.select( + CcdiStaffFmyRelation::getRelationCertNo, + CcdiStaffFmyRelation::getRelationName, + CcdiStaffFmyRelation::getPersonId, + CcdiStaffFmyRelation::getStatus, + CcdiStaffFmyRelation::getIsEmpFamily + ) + .in(CcdiStaffFmyRelation::getRelationCertNo, excelPersonIds); - List existingStaff = baseStaffMapper.selectList(wrapper); - existingPersonIds = existingStaff.stream() - .map(CcdiBaseStaff::getIdCard) - .collect(Collectors.toSet()); + List familyRelations = familyRelationMapper.selectList(wrapper); + for (CcdiStaffFmyRelation familyRelation : familyRelations) { + knownFamilyCertNos.add(familyRelation.getRelationCertNo()); + familyNameMap.putIfAbsent(familyRelation.getRelationCertNo(), familyRelation.getRelationName()); + if (Boolean.TRUE.equals(familyRelation.getIsEmpFamily()) && Integer.valueOf(1).equals(familyRelation.getStatus())) { + validFamilies.putIfAbsent(familyRelation.getRelationCertNo(), familyRelation); + } + } - ImportLogUtils.logBatchQueryComplete(log, taskId, "员工身份证号", existingPersonIds.size()); + ImportLogUtils.logBatchQueryComplete(log, taskId, "员工亲属关系", familyRelations.size()); } // 批量查询已存在的person_id + social_credit_code组合 - ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的员工企业关系组合", excelList.size()); + ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的员工亲属实体关联组合", excelList.size()); Set existingCombinations = getExistingCombinations(excelList); - ImportLogUtils.logBatchQueryComplete(log, taskId, "员工企业关系组合", existingCombinations.size()); + ImportLogUtils.logBatchQueryComplete(log, taskId, "员工亲属实体关联组合", existingCombinations.size()); // 用于跟踪Excel文件内已处理的组合 Set processedCombinations = new HashSet<>(); @@ -103,41 +114,18 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE // 验证数据 validateRelationData(addDTO); - // 身份证号存在性检查(在基本验证之后) - if (!existingPersonIds.contains(excel.getPersonId())) { - throw new RuntimeException(String.format( - "第%d行: 身份证号[%s]不存在于员工信息表中,请先添加员工信息", - i + 1, excel.getPersonId())); - } + CcdiStaffFmyRelation familyRelation = validFamilies.get(excel.getPersonId()); + CcdiStaffEnterpriseRelation relation = validateAndBuildEntity( + excel, + familyRelation, + knownFamilyCertNos, + existingCombinations, + processedCombinations, + userName + ); - String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode(); - - CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation(); - BeanUtils.copyProperties(excel, relation); - - if (existingCombinations.contains(combination)) { - // 组合已存在,直接报错 - throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合已存在,请勿重复导入", - excel.getPersonId(), excel.getSocialCreditCode())); - } else if (processedCombinations.contains(combination)) { - // Excel文件内部重复 - throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合在导入文件中重复,已跳过此条记录", - excel.getPersonId(), excel.getSocialCreditCode())); - } else { - relation.setCreatedBy(userName); - relation.setUpdatedBy(userName); - - // 设置默认值 - relation.setStatus(1); - relation.setIsEmployee(0); - relation.setIsEmpFamily(1); - relation.setIsCustomer(0); - relation.setIsCustFamily(0); - relation.setDataSource("IMPORT"); - - newRecords.add(relation); - processedCombinations.add(combination); // 标记为已处理 - } + newRecords.add(relation); + processedCombinations.add(excel.getPersonId() + "|" + excel.getSocialCreditCode()); // 记录进度 ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(), @@ -146,11 +134,12 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE } catch (Exception e) { StaffEnterpriseRelationImportFailureVO failure = new StaffEnterpriseRelationImportFailureVO(); BeanUtils.copyProperties(excel, failure); + failure.setRelationName(familyNameMap.get(excel.getPersonId())); failure.setErrorMessage(e.getMessage()); failures.add(failure); // 记录验证失败日志 - String keyData = String.format("身份证号=%s, 统一社会信用代码=%s, 企业名称=%s", + String keyData = String.format("亲属身份证号=%s, 统一社会信用代码=%s, 企业名称=%s", excel.getPersonId(), excel.getSocialCreditCode(), excel.getEnterpriseName()); ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData); } @@ -166,7 +155,7 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE // 保存失败记录到Redis if (!failures.isEmpty()) { try { - String failuresKey = "import:staffEnterpriseRelation:" + taskId + ":failures"; + String failuresKey = "import:staffEnterpriseRelation:" + taskId + ":failures"; redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size()); } catch (Exception e) { @@ -185,7 +174,7 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE // 记录导入完成 long duration = System.currentTimeMillis() - startTime; - ImportLogUtils.logImportComplete(log, taskId, "员工实体关系", + ImportLogUtils.logImportComplete(log, taskId, "员工亲属实体关联", excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration); } @@ -251,9 +240,9 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE statusData.put("endTime", System.currentTimeMillis()); if ("SUCCESS".equals(status)) { - statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据"); + statusData.put("message", "员工亲属实体关联导入全部成功!共导入" + result.getTotalCount() + "条数据"); } else { - statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); + statusData.put("message", "员工亲属实体关联导入成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); } redisTemplate.opsForHash().putAll(key, statusData); @@ -297,14 +286,14 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE } /** - * 验证员工实体关系数据 + * 验证员工亲属实体关联基础数据 * * @param addDTO 新增DTO */ private void validateRelationData(CcdiStaffEnterpriseRelationAddDTO addDTO) { // 验证必填字段 if (StringUtils.isEmpty(addDTO.getPersonId())) { - throw new RuntimeException("身份证号不能为空"); + throw new RuntimeException("亲属身份证号不能为空"); } if (StringUtils.isEmpty(addDTO.getSocialCreditCode())) { throw new RuntimeException("统一社会信用代码不能为空"); @@ -313,9 +302,9 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE throw new RuntimeException("企业名称不能为空"); } - // 验证身份证号格式(18位) + // 验证亲属身份证号格式(18位) if (!addDTO.getPersonId().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$")) { - throw new RuntimeException("身份证号格式不正确,必须为18位有效身份证号"); + throw new RuntimeException("亲属身份证号格式不正确,必须为18位有效身份证号"); } // 验证统一社会信用代码格式(18位) @@ -331,4 +320,38 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE throw new RuntimeException("企业名称长度不能超过200个字符"); } } + + CcdiStaffEnterpriseRelation validateAndBuildEntity(CcdiStaffEnterpriseRelationExcel excel, + CcdiStaffFmyRelation familyRelation, + Set knownFamilyCertNos, + Set existingCombinations, + Set processedCombinations, + String userName) { + String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode(); + + if (familyRelation == null) { + if (knownFamilyCertNos.contains(excel.getPersonId())) { + throw new RuntimeException("亲属身份证号[" + excel.getPersonId() + "]不是有效员工亲属,请先维护有效的员工亲属关系"); + } + throw new RuntimeException("亲属身份证号[" + excel.getPersonId() + "]不存在,请先维护员工亲属关系"); + } + if (existingCombinations.contains(combination)) { + throw new RuntimeException("亲属身份证号[" + excel.getPersonId() + "]和统一社会信用代码[" + excel.getSocialCreditCode() + "]的组合已存在,请勿重复导入"); + } + if (processedCombinations.contains(combination)) { + throw new RuntimeException("亲属身份证号[" + excel.getPersonId() + "]和统一社会信用代码[" + excel.getSocialCreditCode() + "]的组合在导入文件中重复,已跳过此条记录"); + } + + CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation(); + BeanUtils.copyProperties(excel, relation); + relation.setCreatedBy(userName); + relation.setUpdatedBy(userName); + relation.setStatus(1); + relation.setIsEmployee(0); + relation.setIsEmpFamily(1); + relation.setIsCustomer(0); + relation.setIsCustFamily(0); + relation.setDataSource("IMPORT"); + return relation; + } } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationServiceImpl.java index 6c6fa0fb..69e2c8da 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationServiceImpl.java @@ -3,11 +3,14 @@ package com.ruoyi.info.collection.service.impl; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation; +import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; +import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO; +import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper; import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper; import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService; import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationService; @@ -37,6 +40,9 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr @Resource private CcdiStaffEnterpriseRelationMapper relationMapper; + @Resource + private CcdiStaffFmyRelationMapper familyRelationMapper; + @Resource private ICcdiStaffEnterpriseRelationImportService relationImportService; @@ -86,6 +92,11 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr }).collect(Collectors.toList()); } + @Override + public java.util.List selectFamilyOptions(String query) { + return relationMapper.selectFamilyOptions(query); + } + /** * 查询员工实体关系详情 * @@ -106,16 +117,15 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr @Override @Transactional public int insertRelation(CcdiStaffEnterpriseRelationAddDTO addDTO) { - // 检查身份证号+统一社会信用代码唯一性 + validateEffectiveFamily(addDTO.getPersonId()); + if (relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())) { - throw new RuntimeException("该身份证号和统一社会信用代码组合已存在"); + throw new RuntimeException("该亲属身份证号和统一社会信用代码组合已存在"); } CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation(); BeanUtils.copyProperties(addDTO, relation); - // 设置默认值 - // 新增时强制设置状态为有效 relation.setStatus(1); if (relation.getIsEmployee() == null) { @@ -159,7 +169,7 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr updateWrapper.set(editDTO.getRemark() != null, CcdiStaffEnterpriseRelation::getRemark, editDTO.getRemark()); // 注意:以下字段不可修改 - // - personId(身份证号,业务主键) + // - personId(亲属身份证号,业务主键) // - socialCreditCode(统一社会信用代码,业务主键) // - dataSource(数据来源,系统字段) // - isEmployee(是否为员工,系统字段) @@ -224,4 +234,28 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr return taskId; } + + private CcdiStaffFmyRelation validateEffectiveFamily(String familyCertNo) { + CcdiStaffFmyRelation validFamily = familyRelationMapper.selectOne( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(CcdiStaffFmyRelation::getRelationCertNo, familyCertNo) + .eq(CcdiStaffFmyRelation::getIsEmpFamily, Boolean.TRUE) + .eq(CcdiStaffFmyRelation::getStatus, 1) + .last("LIMIT 1") + ); + if (validFamily != null) { + return validFamily; + } + + CcdiStaffFmyRelation existingFamily = familyRelationMapper.selectOne( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .select(CcdiStaffFmyRelation::getId) + .eq(CcdiStaffFmyRelation::getRelationCertNo, familyCertNo) + .last("LIMIT 1") + ); + if (existingFamily == null) { + throw new RuntimeException("亲属身份证号[" + familyCertNo + "]不存在,请先维护员工亲属关系"); + } + throw new RuntimeException("亲属身份证号[" + familyCertNo + "]不是有效员工亲属,请先维护有效的员工亲属关系"); + } } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java index 4eea61c2..29b14b10 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java @@ -3,12 +3,13 @@ package com.ruoyi.info.collection.service.impl; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.info.collection.domain.CcdiAssetInfo; import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation; -import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO; import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel; +import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO; import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO; +import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper; import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper; import com.ruoyi.info.collection.service.ICcdiAssetInfoService; import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService; @@ -50,6 +51,9 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer @Resource private ICcdiAssetInfoService assetInfoService; + @Resource + private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper; + /** * 查询员工亲属关系列表 * @@ -161,6 +165,9 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation(); BeanUtils.copyProperties(editDTO, relation); int result = relationMapper.updateById(relation); + if (Integer.valueOf(1).equals(existing.getStatus()) && Integer.valueOf(0).equals(editDTO.getStatus())) { + staffEnterpriseRelationMapper.invalidateByFamilyCertNo(existing.getRelationCertNo()); + } assetInfoService.replaceByFamilyIdAndPersonId(editDTO.getPersonId(), editDTO.getRelationCertNo(), editDTO.getAssetInfoList()); return result; } diff --git a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiStaffEnterpriseRelationMapper.xml b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiStaffEnterpriseRelationMapper.xml index 54e000a5..9a246319 100644 --- a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiStaffEnterpriseRelationMapper.xml +++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiStaffEnterpriseRelationMapper.xml @@ -8,7 +8,9 @@ - + + + @@ -28,17 +30,28 @@ SELECT - ser.id, ser.person_id, bs.name as person_name, ser.relation_person_post, + ser.id, ser.person_id, sfr.relation_name, sfr.person_id AS staff_person_id, bs.name AS staff_person_name, + ser.relation_person_post, ser.social_credit_code, ser.enterprise_name, ser.status, ser.remark, ser.data_source, ser.is_employee, ser.is_emp_family, ser.is_customer, ser.is_cust_family, ser.created_by, ser.create_time, ser.updated_by, ser.update_time FROM ccdi_staff_enterprise_relation ser - LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card + LEFT JOIN ccdi_staff_fmy_relation sfr + ON ser.person_id = sfr.relation_cert_no + AND sfr.is_emp_family = 1 + LEFT JOIN ccdi_base_staff bs ON sfr.person_id = bs.id_card WHERE ser.id = #{id} + + + + + UPDATE ccdi_staff_enterprise_relation + SET status = 0, + update_time = NOW() + WHERE person_id = #{personId} + AND status != 0 + + INSERT INTO ccdi_staff_enterprise_relation diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiStaffEnterpriseRelationMapperTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiStaffEnterpriseRelationMapperTest.java new file mode 100644 index 00000000..44d2e019 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiStaffEnterpriseRelationMapperTest.java @@ -0,0 +1,148 @@ +package com.ruoyi.info.collection.mapper; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.github.pagehelper.parser.defaults.DefaultCountSqlParser; +import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO; +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.apache.ibatis.type.TypeAliasRegistry; +import org.junit.jupiter.api.Test; + +import javax.sql.DataSource; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CcdiStaffEnterpriseRelationMapperTest { + + private static final String RESOURCE = "mapper/info/collection/CcdiStaffEnterpriseRelationMapper.xml"; + + @Test + void selectRelationPage_shouldJoinFamilyRelationAndStaff() throws Exception { + MappedStatement mappedStatement = loadMappedStatement( + "com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper.selectRelationPage"); + + CcdiStaffEnterpriseRelationQueryDTO queryDTO = new CcdiStaffEnterpriseRelationQueryDTO(); + queryDTO.setRelationName("李"); + queryDTO.setStaffPersonName("张"); + String sql = renderSql(mappedStatement, Map.of( + "page", new Page<>(1, 10), + "query", queryDTO + )); + String countSql = normalizeSql(new DefaultCountSqlParser().getSmartCountSql(sql, "0")); + + assertTrue(sql.contains("LEFT JOIN ccdi_staff_fmy_relation sfr"), sql); + assertTrue(sql.contains("LEFT JOIN ccdi_base_staff bs ON sfr.person_id = bs.id_card"), sql); + assertTrue(sql.contains("sfr.relation_name"), sql); + assertTrue(sql.contains("staff_person_id"), sql); + assertTrue(sql.contains("bs.name AS staff_person_name"), sql); + assertTrue(sql.contains("sfr.relation_name LIKE CONCAT('%', ?, '%')"), sql); + assertTrue(sql.contains("bs.name LIKE CONCAT('%', ?, '%')"), sql); + assertFalse(countSql.contains("1AND"), countSql); + } + + @Test + void selectFamilyOptions_shouldOnlyQueryEffectiveEmployeeFamilies() throws Exception { + MappedStatement mappedStatement = loadMappedStatement( + "com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper.selectFamilyOptions"); + + String sql = renderSql(mappedStatement, Map.of("query", "320101")); + + assertTrue(sql.contains("sfr.is_emp_family = 1"), sql); + assertTrue(sql.contains("sfr.status = 1"), sql); + assertTrue(sql.contains("sfr.relation_cert_no LIKE CONCAT('%', ?, '%')"), sql); + } + + @Test + void mapperXml_shouldContainInvalidateByFamilyCertNo() throws Exception { + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) { + String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + assertTrue(xml.contains(""), xml); + assertTrue(xml.contains("WHERE person_id = #{personId}"), xml); + assertTrue(xml.contains("SET status = 0"), xml); + } + } + + private MappedStatement loadMappedStatement(String statementId) throws Exception { + Configuration configuration = new Configuration(); + configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource())); + registerTypeAliases(configuration.getTypeAliasRegistry()); + configuration.getLanguageRegistry().register(XMLLanguageDriver.class); + configuration.addMapper(CcdiStaffEnterpriseRelationMapper.class); + + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) { + XMLMapperBuilder xmlMapperBuilder = + new XMLMapperBuilder(inputStream, configuration, RESOURCE, configuration.getSqlFragments()); + xmlMapperBuilder.parse(); + } + return configuration.getMappedStatement(statementId); + } + + private String renderSql(MappedStatement mappedStatement, Map params) { + BoundSql boundSql = mappedStatement.getBoundSql(new HashMap<>(params)); + return normalizeSql(boundSql.getSql()); + } + + private String normalizeSql(String sql) { + return sql.replaceAll("\\s+", " ").trim(); + } + + private void registerTypeAliases(TypeAliasRegistry typeAliasRegistry) { + typeAliasRegistry.registerAlias("map", Map.class); + } + + private static class NoOpDataSource implements DataSource { + + @Override + public java.sql.Connection getConnection() { + throw new UnsupportedOperationException("Not required for SQL rendering tests"); + } + + @Override + public java.sql.Connection getConnection(String username, String password) { + throw new UnsupportedOperationException("Not required for SQL rendering tests"); + } + + @Override + public java.io.PrintWriter getLogWriter() { + return null; + } + + @Override + public void setLogWriter(java.io.PrintWriter out) { + } + + @Override + public void setLoginTimeout(int seconds) { + } + + @Override + public int getLoginTimeout() { + return 0; + } + + @Override + public java.util.logging.Logger getParentLogger() { + return java.util.logging.Logger.getGlobal(); + } + + @Override + public T 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/CcdiStaffEnterpriseRelationImportServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationImportServiceImplTest.java new file mode 100644 index 00000000..2660ea69 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationImportServiceImplTest.java @@ -0,0 +1,138 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation; +import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation; +import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel; +import com.ruoyi.info.collection.service.impl.CcdiStaffEnterpriseRelationImportServiceImpl; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CcdiStaffEnterpriseRelationImportServiceImplTest { + + private final CcdiStaffEnterpriseRelationImportServiceImpl service = new CcdiStaffEnterpriseRelationImportServiceImpl(); + + @Test + void validateAndBuildEntity_shouldBuildImportEntityForValidFamily() throws Exception { + CcdiStaffEnterpriseRelationExcel excel = buildExcel(); + CcdiStaffFmyRelation familyRelation = buildFamily(); + + CcdiStaffEnterpriseRelation entity = invokeValidateAndBuildEntity( + excel, + familyRelation, + Set.of(excel.getPersonId()), + Set.of(), + new HashSet<>(), + "admin" + ); + + assertEquals("IMPORT", entity.getDataSource()); + assertEquals(1, entity.getStatus()); + assertEquals(1, entity.getIsEmpFamily()); + assertEquals("admin", entity.getCreatedBy()); + } + + @Test + void validateAndBuildEntity_shouldRejectWhenFamilyDoesNotExist() throws Exception { + CcdiStaffEnterpriseRelationExcel excel = buildExcel(); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> invokeValidateAndBuildEntity(excel, null, Set.of(), Set.of(), new HashSet<>(), "admin")); + + assertEquals("亲属身份证号[" + excel.getPersonId() + "]不存在,请先维护员工亲属关系", exception.getMessage()); + } + + @Test + void validateAndBuildEntity_shouldRejectWhenFamilyIsInvalid() throws Exception { + CcdiStaffEnterpriseRelationExcel excel = buildExcel(); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> invokeValidateAndBuildEntity(excel, null, Set.of(excel.getPersonId()), Set.of(), new HashSet<>(), "admin")); + + assertEquals("亲属身份证号[" + excel.getPersonId() + "]不是有效员工亲属,请先维护有效的员工亲属关系", exception.getMessage()); + } + + @Test + void validateAndBuildEntity_shouldRejectWhenCombinationAlreadyExistsInDatabase() throws Exception { + CcdiStaffEnterpriseRelationExcel excel = buildExcel(); + CcdiStaffFmyRelation familyRelation = buildFamily(); + String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode(); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> invokeValidateAndBuildEntity(excel, familyRelation, Set.of(excel.getPersonId()), Set.of(combination), new HashSet<>(), "admin")); + + assertEquals("亲属身份证号[" + excel.getPersonId() + "]和统一社会信用代码[" + excel.getSocialCreditCode() + "]的组合已存在,请勿重复导入", exception.getMessage()); + } + + @Test + void validateAndBuildEntity_shouldRejectWhenCombinationAlreadyExistsInExcel() throws Exception { + CcdiStaffEnterpriseRelationExcel excel = buildExcel(); + CcdiStaffFmyRelation familyRelation = buildFamily(); + String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode(); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> invokeValidateAndBuildEntity(excel, familyRelation, Set.of(excel.getPersonId()), Set.of(), new HashSet<>(Set.of(combination)), "admin")); + + assertEquals("亲属身份证号[" + excel.getPersonId() + "]和统一社会信用代码[" + excel.getSocialCreditCode() + "]的组合在导入文件中重复,已跳过此条记录", exception.getMessage()); + } + + private CcdiStaffEnterpriseRelation invokeValidateAndBuildEntity(CcdiStaffEnterpriseRelationExcel excel, + CcdiStaffFmyRelation familyRelation, + Set knownFamilyCertNos, + Set existingCombinations, + Set processedCombinations, + String userName) throws Exception { + Method method = CcdiStaffEnterpriseRelationImportServiceImpl.class.getDeclaredMethod( + "validateAndBuildEntity", + CcdiStaffEnterpriseRelationExcel.class, + CcdiStaffFmyRelation.class, + Set.class, + Set.class, + Set.class, + String.class + ); + method.setAccessible(true); + try { + return (CcdiStaffEnterpriseRelation) method.invoke( + service, + excel, + familyRelation, + knownFamilyCertNos, + existingCombinations, + processedCombinations, + userName + ); + } catch (InvocationTargetException ex) { + Throwable targetException = ex.getTargetException(); + if (targetException instanceof RuntimeException runtimeException) { + throw runtimeException; + } + throw ex; + } + } + + private CcdiStaffEnterpriseRelationExcel buildExcel() { + CcdiStaffEnterpriseRelationExcel excel = new CcdiStaffEnterpriseRelationExcel(); + excel.setPersonId("320101199001010022"); + excel.setSocialCreditCode("91310000123456789A"); + excel.setEnterpriseName("测试企业"); + excel.setRelationPersonPost("董事"); + return excel; + } + + private CcdiStaffFmyRelation buildFamily() { + CcdiStaffFmyRelation familyRelation = new CcdiStaffFmyRelation(); + familyRelation.setPersonId("320101199001010011"); + familyRelation.setRelationCertNo("320101199001010022"); + familyRelation.setRelationName("李四"); + familyRelation.setStatus(1); + familyRelation.setIsEmpFamily(true); + return familyRelation; + } +} diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationServiceImplTest.java new file mode 100644 index 00000000..0876ac80 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationServiceImplTest.java @@ -0,0 +1,156 @@ +package com.ruoyi.info.collection.service; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation; +import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation; +import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO; +import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO; +import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO; +import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper; +import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper; +import com.ruoyi.info.collection.service.impl.CcdiStaffEnterpriseRelationServiceImpl; +import org.apache.ibatis.builder.MapperBuilderAssistant; +import org.apache.ibatis.session.Configuration; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CcdiStaffEnterpriseRelationServiceImplTest { + + @BeforeAll + static void initTableInfo() { + registerTableInfo(CcdiStaffEnterpriseRelation.class, CcdiStaffEnterpriseRelationMapper.class.getName()); + registerTableInfo(CcdiStaffFmyRelation.class, CcdiStaffFmyRelationMapper.class.getName()); + } + + @InjectMocks + private CcdiStaffEnterpriseRelationServiceImpl service; + + @Mock + private CcdiStaffEnterpriseRelationMapper relationMapper; + + @Mock + private CcdiStaffFmyRelationMapper familyRelationMapper; + + @Mock + private ICcdiStaffEnterpriseRelationImportService relationImportService; + + @Mock + private RedisTemplate redisTemplate; + + @Test + void insertRelation_shouldAllowValidFamily() { + CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto(); + CcdiStaffFmyRelation familyRelation = new CcdiStaffFmyRelation(); + familyRelation.setRelationCertNo(addDTO.getPersonId()); + familyRelation.setRelationName("李四"); + familyRelation.setPersonId("320101199001010011"); + + when(familyRelationMapper.selectOne(any())).thenReturn(familyRelation); + when(relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())).thenReturn(false); + when(relationMapper.insert(any(CcdiStaffEnterpriseRelation.class))).thenReturn(1); + + int result = service.insertRelation(addDTO); + + assertEquals(1, result); + ArgumentCaptor captor = ArgumentCaptor.forClass(CcdiStaffEnterpriseRelation.class); + verify(relationMapper).insert(captor.capture()); + assertEquals(1, captor.getValue().getStatus()); + assertEquals("MANUAL", captor.getValue().getDataSource()); + assertEquals(1, captor.getValue().getIsEmpFamily()); + } + + @Test + void insertRelation_shouldRejectInvalidFamily() { + CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto(); + + when(familyRelationMapper.selectOne(any())) + .thenReturn(null) + .thenReturn(new CcdiStaffFmyRelation()); + + RuntimeException exception = assertThrows(RuntimeException.class, () -> service.insertRelation(addDTO)); + + assertEquals("亲属身份证号[" + addDTO.getPersonId() + "]不是有效员工亲属,请先维护有效的员工亲属关系", exception.getMessage()); + } + + @Test + void insertRelation_shouldRejectMissingFamily() { + CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto(); + + when(familyRelationMapper.selectOne(any())).thenReturn(null); + + RuntimeException exception = assertThrows(RuntimeException.class, () -> service.insertRelation(addDTO)); + + assertEquals("亲属身份证号[" + addDTO.getPersonId() + "]不存在,请先维护员工亲属关系", exception.getMessage()); + } + + @Test + void updateRelation_shouldExecuteEditFlow() { + CcdiStaffEnterpriseRelationEditDTO editDTO = new CcdiStaffEnterpriseRelationEditDTO(); + editDTO.setId(1L); + editDTO.setPersonId("320101199001010022"); + editDTO.setSocialCreditCode("91310000123456789A"); + editDTO.setEnterpriseName("测试企业"); + editDTO.setRelationPersonPost("董事"); + editDTO.setStatus(0); + editDTO.setRemark("测试备注"); + + when(relationMapper.update(isNull(), any(LambdaUpdateWrapper.class))).thenReturn(1); + + int result = service.updateRelation(editDTO); + + assertEquals(1, result); + verify(relationMapper).update(isNull(), any(LambdaUpdateWrapper.class)); + } + + @Test + void selectFamilyOptions_shouldDelegateToMapper() { + CcdiStaffEnterpriseRelationOptionVO option = new CcdiStaffEnterpriseRelationOptionVO(); + option.setRelationCertNo("320101199001010022"); + option.setRelationName("李四"); + option.setStaffPersonId("320101199001010011"); + option.setStaffPersonName("张三"); + List expected = List.of(option); + + when(relationMapper.selectFamilyOptions("320101")).thenReturn(expected); + + List result = service.selectFamilyOptions("320101"); + + assertSame(expected, result); + } + + private CcdiStaffEnterpriseRelationAddDTO buildAddDto() { + CcdiStaffEnterpriseRelationAddDTO addDTO = new CcdiStaffEnterpriseRelationAddDTO(); + addDTO.setPersonId("320101199001010022"); + addDTO.setSocialCreditCode("91310000123456789A"); + addDTO.setEnterpriseName("测试企业"); + addDTO.setRelationPersonPost("董事"); + return addDTO; + } + + private static void registerTableInfo(Class entityClass, String namespace) { + if (TableInfoHelper.getTableInfo(entityClass) != null) { + return; + } + MapperBuilderAssistant assistant = new MapperBuilderAssistant(new Configuration(), ""); + assistant.setCurrentNamespace(namespace); + TableInfoHelper.initTableInfo(assistant, entityClass); + } +} diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffFmyRelationServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffFmyRelationServiceImplTest.java index c4ac1a26..7a35dcc9 100644 --- a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffFmyRelationServiceImplTest.java +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffFmyRelationServiceImplTest.java @@ -6,6 +6,7 @@ import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO; import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO; import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO; +import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper; import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper; import com.ruoyi.info.collection.service.impl.CcdiStaffFmyRelationServiceImpl; import org.junit.jupiter.api.Test; @@ -46,6 +47,9 @@ class CcdiStaffFmyRelationServiceImplTest { @Mock private ICcdiAssetInfoService assetInfoService; + @Mock + private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper; + @Test void selectRelationById_shouldAggregateAssetInfoList() { CcdiStaffFmyRelationVO relationVO = new CcdiStaffFmyRelationVO(); @@ -171,6 +175,36 @@ class CcdiStaffFmyRelationServiceImplTest { verify(assetInfoService).replaceByFamilyIdAndPersonId("320101199001010011", "A123456789", editDTO.getAssetInfoList()); } + @Test + void updateRelation_shouldInvalidateEnterpriseRelationsWhenFamilyBecomesInvalid() { + CcdiStaffFmyRelation existing = new CcdiStaffFmyRelation(); + existing.setId(10L); + existing.setRelationCertType("护照"); + existing.setRelationCertNo("A123456789"); + existing.setStatus(1); + + CcdiStaffFmyRelationEditDTO editDTO = new CcdiStaffFmyRelationEditDTO(); + editDTO.setId(10L); + editDTO.setPersonId("320101199001010011"); + editDTO.setRelationType("配偶"); + editDTO.setRelationName("李四"); + editDTO.setRelationCertType("护照"); + editDTO.setRelationCertNo("A123456789"); + editDTO.setStatus(0); + editDTO.setAssetInfoList(List.of(buildAssetDto("车辆"))); + + when(relationMapper.selectById(10L)).thenReturn(existing); + when(relationMapper.updateById(any(CcdiStaffFmyRelation.class))).thenReturn(1); + + int result = service.updateRelation(editDTO); + + assertEquals(1, result); + var order = inOrder(relationMapper, staffEnterpriseRelationMapper, assetInfoService); + order.verify(relationMapper).updateById(any(CcdiStaffFmyRelation.class)); + order.verify(staffEnterpriseRelationMapper).invalidateByFamilyCertNo("A123456789"); + order.verify(assetInfoService).replaceByFamilyIdAndPersonId("320101199001010011", "A123456789", editDTO.getAssetInfoList()); + } + @Test void deleteRelationByIds_shouldDeleteRelativeAssetsBeforeDeletingRelations() { CcdiStaffFmyRelation relation1 = new CcdiStaffFmyRelation(); diff --git a/docs/reports/implementation/2026-04-23-staff-family-enterprise-relation-implementation.md b/docs/reports/implementation/2026-04-23-staff-family-enterprise-relation-implementation.md new file mode 100644 index 00000000..79ec8fa4 --- /dev/null +++ b/docs/reports/implementation/2026-04-23-staff-family-enterprise-relation-implementation.md @@ -0,0 +1,156 @@ +# 员工亲属实体关联实施记录 + +## 1. 变更背景 + +根据以下实施计划执行员工亲属实体关联改造: + +- 后端计划:`docs/plans/backend/2026-04-23-staff-family-enterprise-relation-backend-implementation.md` +- 前端计划:`docs/plans/frontend/2026-04-23-staff-family-enterprise-relation-frontend-implementation.md` + +本次改造目标是在不新增平行模块和表结构的前提下,将原员工实体关系维护切换为员工亲属实体关联维护。 + +## 2. 后端实施内容 + +### 2.1 契约对象调整 + +- 调整 `CcdiStaffEnterpriseRelationQueryDTO`,新增亲属姓名、关联员工查询字段 +- 调整 `CcdiStaffEnterpriseRelationAddDTO` / `CcdiStaffEnterpriseRelationEditDTO` 的注释与校验提示,明确 `personId` 为亲属身份证号 +- 调整 `CcdiStaffEnterpriseRelationVO`,新增: + - `relationName` + - `staffPersonId` + - `staffPersonName` +- 新增 `CcdiStaffEnterpriseRelationOptionVO` 作为有效亲属下拉返回对象 +- 调整 `StaffEnterpriseRelationImportFailureVO`,补充 `relationName` +- 调整 `CcdiStaffEnterpriseRelationExcel`,将模板列头切换为亲属语义 + +### 2.2 Mapper 与查询链路 + +- 在 `CcdiStaffEnterpriseRelationMapper` 中新增: + - `selectFamilyOptions` + - `invalidateByFamilyCertNo` +- 重写 `CcdiStaffEnterpriseRelationMapper.xml` 中的列表与详情 SQL +- 查询链路改为: + - `ccdi_staff_enterprise_relation` + - `LEFT JOIN ccdi_staff_fmy_relation` + - `LEFT JOIN ccdi_base_staff` +- 查询条件支持: + - 亲属身份证号 + - 亲属姓名 + - 关联员工姓名/身份证号 + - 统一社会信用代码 + - 企业名称 + - 状态 + +### 2.3 Service / Controller 改造 + +- `CcdiStaffEnterpriseRelationServiceImpl` 新增有效亲属校验逻辑 +- 新增有效亲属下拉接口 `/ccdi/staffEnterpriseRelation/familyOptions` +- 新增和导入均改为以有效员工亲属为准入条件 +- 控制器 Swagger 标题、日志标题、模板标题和导入返回文案切换为员工亲属实体关联口径 + +### 2.4 异步导入改造 + +- 导入逻辑取消对 `ccdi_base_staff` 的存在性校验 +- 改为批量读取 `ccdi_staff_fmy_relation`,区分: + - 亲属不存在 + - 亲属无效 + - 库内重复 + - 文件内重复 +- 失败记录中回填亲属姓名 +- 导入状态文案切换为员工亲属实体关联口径 + +### 2.5 亲属失效联动 + +- 在 `CcdiStaffFmyRelationServiceImpl.updateRelation` 中识别“有效 -> 无效”状态变更 +- 当亲属关系失效时,调用 `invalidateByFamilyCertNo` 将对应实体关联批量置为无效 +- 不实现“无效 -> 有效”反向恢复 + +## 3. 前端实施内容 + +### 3.1 API 与页面语义切换 + +- `ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js` 新增 `listFamilyOptions(query)` +- `ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue` 切换为员工亲属实体关联页面 + +### 3.2 页面改造点 + +- 查询区切换为: + - 亲属身份证号 + - 亲属姓名 + - 关联员工 + - 统一社会信用代码 + - 企业名称 + - 状态 +- 列表列切换为: + - 亲属身份证号 + - 亲属姓名 + - 关联员工 + - 企业名称 + - 关联人在企业的职务 + - 状态 + - 数据来源 + - 创建时间 +- 新增/编辑弹窗切换为有效亲属远程下拉选择 +- 选中亲属后自动带出亲属姓名和关联员工 +- 详情弹窗切换为亲属口径展示 +- 导入标题、模板文件名、通知文案、失败记录标题切换为亲属语义 +- 本地缓存 key 切换为 `staff_family_enterprise_relation_import_last_task` +- 新增菜单迁移脚本 `sql/migration/2026-04-23-rename-staff-enterprise-relation-menu.sql` + - 将 `sys_menu` 中“员工实体关系/员工实体关系维护”主菜单改名为“员工亲属实体关联” + - 将对应查询、新增、修改、删除、导入、导出按钮权限菜单名称同步切换为亲属语义 + +## 4. 验证结果 + +### 4.1 后端验证 + +- 通过定向测试: + +```bash +mvn -pl ccdi-info-collection -am -Dtest=CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest,CcdiStaffFmyRelationServiceImplTest,CcdiStaffEnterpriseRelationMapperTest -Dsurefire.failIfNoSpecifiedTests=false test +``` + +- 通过编译验证: + +```bash +mvn -pl ccdi-info-collection -am -DskipTests compile +``` + +### 4.2 前端验证 + +- 通过 Node 版本校验: + +```bash +cd ruoyi-ui +source ~/.nvm/nvm.sh && nvm use +``` + +- 通过前端构建: + +```bash +npm run build:prod +``` + +- 已完成真实页面基础访问与展示项核对,详见: + +`docs/tests/records/2026-04-23-staff-family-enterprise-relation-browser-test-record.md` + +### 4.3 旧数据清理 + +- 新增并执行脚本: + +```bash +bin/mysql_utf8_exec.sh sql/migration/2026-04-23-clean-legacy-staff-enterprise-relation-data.sql +``` + +- 清理口径: + - 删除 `ccdi_staff_enterprise_relation` 中无法匹配任何 `ccdi_staff_fmy_relation.is_emp_family = 1` 记录的旧数据 +- 本次清理结果: + - 清理前总数:15 + - 可匹配亲属数:0 + - 清理后剩余总数:0 + +## 5. 风险与后续 + +- 历史员工本人语义数据未迁移,列表中旧记录的亲属姓名和关联员工可能为空,符合本次设计范围 +- 浏览器导入实操验证受后端测试进程稳定性影响,尚未完成完整上传闭环 +- 若继续补齐导入实操测试,需要先确保后端测试进程在桌面会话中可稳定保活 diff --git a/docs/tests/records/2026-04-23-staff-family-enterprise-relation-browser-test-record.md b/docs/tests/records/2026-04-23-staff-family-enterprise-relation-browser-test-record.md new file mode 100644 index 00000000..d59c6d18 --- /dev/null +++ b/docs/tests/records/2026-04-23-staff-family-enterprise-relation-browser-test-record.md @@ -0,0 +1,82 @@ +# 员工亲属实体关联浏览器测试记录 + +## 1. 测试目标 + +- 验证员工实体关系页面已切换为员工亲属实体关联页面 +- 验证查询区、列表列、详情弹窗、导入入口文案是否已切换为亲属语义 +- 验证页面通过真实前端服务和真实后端服务访问,不使用 prototype 页面 + +## 2. 测试环境 + +- 测试时间:2026-04-23 +- 前端:`ruoyi-ui` +- 前端 Node 版本:`v14.21.3` +- 前端访问地址: + - 首次稳定访问:`http://localhost:1025/` + - 后续 dev server 热重启后访问:`http://127.0.0.1:1027/` +- 后端访问地址:`http://localhost:62318` +- 浏览器驱动:Playwright CLI + +## 3. 执行过程 + +### 3.1 前端构建 + +- 执行 `source ~/.nvm/nvm.sh && nvm use` +- 执行 `npm run build:prod` +- 构建结果:成功 +- 备注:存在既有包体积告警,不影响本次功能构建结果 + +### 3.2 真实页面访问 + +- 使用 Playwright 打开真实前端页面并进入系统首页 +- 通过菜单进入“信息维护” +- 成功访问员工实体关系业务页,页面地址为 `/maintain/staffEnterpriseRelation` + +### 3.3 已验证项 + +- 查询区已切换为以下字段: + - 亲属身份证号 + - 亲属姓名 + - 关联员工 + - 统一社会信用代码 + - 企业名称 + - 状态 +- 列表表头已切换为以下字段: + - 亲属身份证号 + - 亲属姓名 + - 关联员工 + - 企业名称 + - 关联人在企业的职务 + - 状态 + - 数据来源 + - 创建时间 +- 页面按钮文案已切换为亲属语义: + - 新增 + - 导入 + - 查看导入失败记录 +- 后端日志已确认列表查询走新 SQL: + - `ccdi_staff_enterprise_relation ser` + - `LEFT JOIN ccdi_staff_fmy_relation sfr ON ser.person_id = sfr.relation_cert_no AND sfr.is_emp_family = 1` + - `LEFT JOIN ccdi_base_staff bs ON sfr.person_id = bs.id_card` + +## 4. 结果与发现 + +### 4.1 页面表现 + +- 页面主体已按本次需求切换为员工亲属实体关联口径 +- 历史数据因未迁移旧员工本人语义记录,当前列表中的亲属姓名和关联员工存在空值/`-` +- 该现象符合设计文档中“不迁移旧的员工本人实体关联历史数据、不设计旧数据兼容展示逻辑”的范围说明 + +### 4.2 阻塞项 + +- 浏览器测试阶段,后端通过 `bin/restart_java_backend.sh` 启动后可正常服务,但在桌面会话中会跟随当前测试会话退出,导致后端在一段时间后自动关闭 +- 受该稳定性问题影响,本轮未能完成以下真实页面闭环验证: + - 新增弹窗中的有效亲属下拉选择 + - 编辑弹窗完整提交流程 + - 通过页面下载模板并完成三类导入样本上传 + - 失败记录弹窗完整回查 + +## 5. 结论 + +- 已完成真实页面基础访问与核心展示项核对,确认本次前端改造已切换到亲属语义 +- 后续若继续执行导入实操验证,需先确保后端测试进程在桌面会话中可稳定保活,再继续完成导入样本生成、上传与回收验证 diff --git a/ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js b/ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js index f98181ca..058bdd84 100644 --- a/ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js +++ b/ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js @@ -1,6 +1,6 @@ import request from '@/utils/request' -// 查询员工实体关系列表 +// 查询员工亲属实体关系列表 export function listRelation(query) { return request({ url: '/ccdi/staffEnterpriseRelation/list', @@ -9,7 +9,16 @@ export function listRelation(query) { }) } -// 查询员工实体关系详情 +// 查询有效员工亲属下拉选项 +export function listFamilyOptions(query) { + return request({ + url: '/ccdi/staffEnterpriseRelation/familyOptions', + method: 'get', + params: { query } + }) +} + +// 查询员工亲属实体关系详情 export function getRelation(id) { return request({ url: '/ccdi/staffEnterpriseRelation/' + id, @@ -17,7 +26,7 @@ export function getRelation(id) { }) } -// 新增员工实体关系 +// 新增员工亲属实体关系 export function addRelation(data) { return request({ url: '/ccdi/staffEnterpriseRelation', @@ -26,7 +35,7 @@ export function addRelation(data) { }) } -// 修改员工实体关系 +// 修改员工亲属实体关系 export function updateRelation(data) { return request({ url: '/ccdi/staffEnterpriseRelation', @@ -35,7 +44,7 @@ export function updateRelation(data) { }) } -// 删除员工实体关系 +// 删除员工亲属实体关系 export function delRelation(ids) { return request({ url: '/ccdi/staffEnterpriseRelation/' + ids, @@ -51,7 +60,7 @@ export function importTemplate() { }) } -// 导入员工实体关系 +// 导入员工亲属实体关系 export function importData(file) { const formData = new FormData() formData.append('file', file) diff --git a/ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue b/ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue index 2b00be7f..4d242134 100644 --- a/ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue +++ b/ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue @@ -1,39 +1,73 @@