diff --git a/doc/数据库文档/fix_collation_staff_fmy_relation.sql b/doc/数据库文档/fix_collation_staff_fmy_relation.sql new file mode 100644 index 0000000..eb2a218 --- /dev/null +++ b/doc/数据库文档/fix_collation_staff_fmy_relation.sql @@ -0,0 +1,27 @@ +-- 修复员工基础表和员工家庭关系表字符集排序规则 +-- 问题: ccdi_staff_fmy_relation.person_id (utf8mb4_general_ci) 与 ccdi_base_staff.id_card (utf8mb4_unicode_ci) 字符集不一致 +-- 解决方案: 统一为 utf8mb4_general_ci +-- 执行时间: 2026-02-09 + +USE `ruoyi-vue-pro`; + +-- 修改 ccdi_base_staff 表的 id_card 字段字符集,统一为 utf8mb4_general_ci +ALTER TABLE `ccdi_base_staff` +MODIFY COLUMN `id_card` VARCHAR(100) DEFAULT NULL COLLATE `utf8mb4_general_ci` COMMENT '身份证号'; + +-- 验证修改 +SELECT + TABLE_NAME, + COLUMN_NAME, + CHARACTER_SET_NAME, + COLLATION_NAME +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_SCHEMA = 'ruoyi-vue-pro' + AND TABLE_NAME IN ('ccdi_staff_fmy_relation', 'ccdi_base_staff') + AND COLUMN_NAME IN ('person_id', 'id_card') +ORDER BY + TABLE_NAME, COLUMN_NAME; + +-- 说明: 执行后两个表的字符集应该都显示为 utf8mb4_general_ci diff --git a/doc/数据库文档/verify_collation_fix.sql b/doc/数据库文档/verify_collation_fix.sql new file mode 100644 index 0000000..af0daa2 --- /dev/null +++ b/doc/数据库文档/verify_collation_fix.sql @@ -0,0 +1,120 @@ +-- ======================================== +-- 验证员工家庭关系表 JOIN 字符集问题修复 +-- ======================================== +-- 目的: 验证 ccdi_staff_fmy_relation 与 ccdi_base_staff 表的 JOIN 操作正常 +-- 创建时间: 2026-02-09 +-- ======================================== + +USE `ccdi`; + +-- 测试 1: 验证字符集统一性 +-- ======================================== +SELECT '=== 字符集验证 ===' AS test_section; + +SELECT + TABLE_NAME AS '表名', + COLUMN_NAME AS '字段名', + CHARACTER_SET_NAME AS '字符集', + COLLATION_NAME AS '排序规则', + CASE + WHEN COLLATION_NAME = 'utf8mb4_general_ci' THEN '✅ 正确' + ELSE '❌ 错误' + END AS '状态' +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_SCHEMA = 'ccdi' + AND TABLE_NAME IN ('ccdi_staff_fmy_relation', 'ccdi_base_staff') + AND COLUMN_NAME IN ('person_id', 'id_card') +ORDER BY + TABLE_NAME, COLUMN_NAME; + +-- 测试 2: 验证 JOIN 操作(模拟实际查询) +-- ======================================== +SELECT '=== JOIN 操作验证 ===' AS test_section; + +SELECT + r.id, + r.person_id, + s.name AS person_name, + r.relation_type, + r.relation_name, + r.gender, + s.id_card AS staff_id_card, + CASE + WHEN r.person_id = s.id_card THEN '✅ 匹配成功' + ELSE '❌ 匹配失败' + END AS join_status +FROM + ccdi_staff_fmy_relation r +LEFT JOIN ccdi_base_staff s ON r.person_id = s.id_card +WHERE + r.is_emp_family = 1 +LIMIT 5; + +-- 测试 3: 统计数据 +-- ======================================== +SELECT '=== 数据统计 ===' AS test_section; + +SELECT + COUNT(*) AS total_records, + COUNT(s.name) AS matched_records, + COUNT(*) - COUNT(s.name) AS unmatched_records, + CONCAT( + ROUND(COUNT(s.name) / COUNT(*) * 100, 2), + '%' + ) AS match_rate +FROM + ccdi_staff_fmy_relation r +LEFT JOIN ccdi_base_staff s ON r.person_id = s.id_card +WHERE + r.is_emp_family = 1; + +-- 测试 4: 查找未匹配的记录 +-- ======================================== +SELECT '=== 未匹配记录 ===' AS test_section; + +SELECT + r.id, + r.person_id, + r.relation_name, + '未找到对应员工信息' AS warning +FROM + ccdi_staff_fmy_relation r +LEFT JOIN ccdi_base_staff s ON r.person_id = s.id_card +WHERE + r.is_emp_family = 1 + AND s.id_card IS NULL +LIMIT 5; + +-- 测试 5: 字符集转换测试 +-- ======================================== +SELECT '=== 字符集转换测试 ===' AS test_section; + +SELECT + r.person_id, + s.id_card, + CHAR_LENGTH(r.person_id) AS len_person_id, + CHAR_LENGTH(s.id_card) AS len_id_card, + HEX(r.person_id) AS hex_person_id, + HEX(s.id_card) AS hex_id_card, + CASE + WHEN r.person_id = s.id_card THEN '✅ 完全相等' + ELSE '❌ 不相等' + END AS compare_result +FROM + ccdi_staff_fmy_relation r +INNER JOIN ccdi_base_staff s ON r.person_id = s.id_card +WHERE + r.is_emp_family = 1 +LIMIT 3; + +-- ======================================== +-- 验证结果说明: +-- ======================================== +-- 1. 字符集验证: 所有字段应显示 ✅ 正确 +-- 2. JOIN 操作: 应能正常执行,无字符集冲突错误 +-- 3. 数据统计: 显示匹配率 +-- 4. 未匹配记录: 显示哪些员工关系记录未关联到员工 +-- 5. 字符集转换: 验证字段在 JOIN 时能正确比较 +-- ======================================== diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiStaffFmyRelationController.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiStaffFmyRelationController.java new file mode 100644 index 0000000..479bd3e --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiStaffFmyRelationController.java @@ -0,0 +1,195 @@ +package com.ruoyi.ccdi.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationAddDTO; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationEditDTO; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationQueryDTO; +import com.ruoyi.ccdi.domain.excel.CcdiStaffFmyRelationExcel; +import com.ruoyi.ccdi.domain.vo.CcdiStaffFmyRelationVO; +import com.ruoyi.ccdi.domain.vo.ImportResultVO; +import com.ruoyi.ccdi.domain.vo.ImportStatusVO; +import com.ruoyi.ccdi.domain.vo.StaffFmyRelationImportFailureVO; +import com.ruoyi.ccdi.service.ICcdiStaffFmyRelationImportService; +import com.ruoyi.ccdi.service.ICcdiStaffFmyRelationService; +import com.ruoyi.ccdi.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.PageDomain; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.enums.BusinessType; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +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.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 员工亲属关系Controller + * + * @author ruoyi + * @date 2026-02-09 + */ +@Tag(name = "员工亲属关系管理") +@RestController +@RequestMapping("/ccdi/staffFmyRelation") +public class CcdiStaffFmyRelationController extends BaseController { + + @Resource + private ICcdiStaffFmyRelationService relationService; + + @Resource + private ICcdiStaffFmyRelationImportService relationImportService; + + /** + * 查询员工亲属关系列表 + */ + @Operation(summary = "查询员工亲属关系列表") + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:list')") + @GetMapping("/list") + public TableDataInfo list(CcdiStaffFmyRelationQueryDTO queryDTO) { + // 使用MyBatis Plus分页 + PageDomain pageDomain = TableSupport.buildPageRequest(); + Page page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); + Page result = relationService.selectRelationPage(page, queryDTO); + return getDataTable(result.getRecords(), result.getTotal()); + } + + /** + * 导出员工亲属关系列表 + */ + @Operation(summary = "导出员工亲属关系列表") + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:export')") + @Log(title = "员工亲属关系", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, CcdiStaffFmyRelationQueryDTO queryDTO) { + List list = relationService.selectRelationListForExport(queryDTO); + EasyExcelUtil.exportExcel(response, list, CcdiStaffFmyRelationExcel.class, "员工亲属关系信息"); + } + + /** + * 获取员工亲属关系详细信息 + */ + @Operation(summary = "获取员工亲属关系详细信息") + @Parameter(name = "id", description = "主键ID", required = true) + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable Long id) { + return success(relationService.selectRelationById(id)); + } + + /** + * 新增员工亲属关系 + */ + @Operation(summary = "新增员工亲属关系") + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:add')") + @Log(title = "员工亲属关系", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody CcdiStaffFmyRelationAddDTO addDTO) { + return toAjax(relationService.insertRelation(addDTO)); + } + + /** + * 修改员工亲属关系 + */ + @Operation(summary = "修改员工亲属关系") + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:edit')") + @Log(title = "员工亲属关系", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody CcdiStaffFmyRelationEditDTO editDTO) { + return toAjax(relationService.updateRelation(editDTO)); + } + + /** + * 删除员工亲属关系 + */ + @Operation(summary = "删除员工亲属关系") + @Parameter(name = "ids", description = "主键ID数组", required = true) + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:remove')") + @Log(title = "员工亲属关系", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(relationService.deleteRelationByIds(ids)); + } + + /** + * 下载带字典下拉框的导入模板 + * 使用@DictDropdown注解自动添加下拉框 + */ + @Operation(summary = "下载导入模板") + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffFmyRelationExcel.class, "员工亲属关系信息"); + } + + /** + * 异步导入员工亲属关系 + */ + @Operation(summary = "异步导入员工亲属关系") + @Parameter(name = "file", description = "导入文件", required = true) + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')") + @Log(title = "员工亲属关系", businessType = BusinessType.IMPORT) + @PostMapping("/importData") + public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception { + List list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffFmyRelationExcel.class); + + if (list == null || list.isEmpty()) { + return error("至少需要一条数据"); + } + + // 提交异步任务 + String taskId = relationService.importRelation(list); + + // 立即返回,不等待后台任务完成 + ImportResultVO result = new ImportResultVO(); + result.setTaskId(taskId); + result.setStatus("PROCESSING"); + result.setMessage("导入任务已提交,正在后台处理"); + + return AjaxResult.success("导入任务已提交,正在后台处理", result); + } + + /** + * 查询导入状态 + */ + @Operation(summary = "查询导入状态") + @Parameter(name = "taskId", description = "任务ID", required = true) + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')") + @GetMapping("/importStatus/{taskId}") + public AjaxResult getImportStatus(@PathVariable String taskId) { + ImportStatusVO statusVO = relationImportService.getImportStatus(taskId); + return success(statusVO); + } + + /** + * 查询导入失败记录 + */ + @Operation(summary = "查询导入失败记录") + @Parameter(name = "taskId", description = "任务ID", required = true) + @Parameter(name = "pageNum", description = "页码", required = false) + @Parameter(name = "pageSize", description = "每页条数", required = false) + @PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')") + @GetMapping("/importFailures/{taskId}") + public TableDataInfo getImportFailures( + @PathVariable String taskId, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + + List failures = relationImportService.getImportFailures(taskId); + + // 手动分页 + int fromIndex = (pageNum - 1) * pageSize; + int toIndex = Math.min(fromIndex + pageSize, failures.size()); + + List pageData = failures.subList(fromIndex, toIndex); + + return getDataTable(pageData, failures.size()); + } +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiStaffFmyRelation.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiStaffFmyRelation.java new file mode 100644 index 0000000..8577525 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiStaffFmyRelation.java @@ -0,0 +1,107 @@ +package com.ruoyi.ccdi.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 员工亲属关系对象 ccdi_staff_fmy_relation + * + * @author ruoyi + * @date 2026-02-09 + */ +@Data +public class CcdiStaffFmyRelation implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @TableId(type = IdType.AUTO) + private Long id; + + /** 员工身份证号 */ + private String personId; + + /** 关系类型 */ + private String relationType; + + /** 关系人姓名 */ + private String relationName; + + /** 性别:M-男,F-女,O-其他 */ + private String gender; + + /** 出生日期 */ + private Date birthDate; + + /** 关系人证件类型 */ + private String relationCertType; + + /** 关系人证件号码 */ + private String relationCertNo; + + /** 手机号码1 */ + private String mobilePhone1; + + /** 手机号码2 */ + private String mobilePhone2; + + /** 微信名称1 */ + private String wechatNo1; + + /** 微信名称2 */ + private String wechatNo2; + + /** 微信名称3 */ + private String wechatNo3; + + /** 详细联系地址 */ + private String contactAddress; + + /** 关系详细描述 */ + private String relationDesc; + + /** 生效日期 */ + private Date effectiveDate; + + /** 失效日期 */ + private Date invalidDate; + + /** 状态:0-无效,1-有效 */ + private Integer status; + + /** 备注 */ + private String remark; + + /** 数据来源:MANUAL-手工录入,IMPORT-导入 */ + private String dataSource; + + /** 是否是员工亲属:0-否,1-是 */ + private Boolean isEmpFamily; + + /** 是否是客户亲属:0-否,1-是 */ + private Boolean isCustFamily; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; + + /** 创建人 */ + @TableField(fill = FieldFill.INSERT) + private String createdBy; + + /** 更新人 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updatedBy; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationAddDTO.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationAddDTO.java new file mode 100644 index 0000000..51fd3fa --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationAddDTO.java @@ -0,0 +1,119 @@ +package com.ruoyi.ccdi.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.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 员工亲属关系新增DTO + * + * @author ruoyi + * @date 2026-02-09 + */ +@Data +@Schema(description = "员工亲属关系新增") +public class CcdiStaffFmyRelationAddDTO 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}[\\dXx]$", message = "员工身份证号格式不正确") + @Schema(description = "员工身份证号") + private String personId; + + /** 关系类型 */ + @NotBlank(message = "关系类型不能为空") + @Size(max = 50, message = "关系类型长度不能超过50个字符") + @Schema(description = "关系类型") + private String relationType; + + /** 关系人姓名 */ + @NotBlank(message = "关系人姓名不能为空") + @Size(max = 100, message = "关系人姓名长度不能超过100个字符") + @Schema(description = "关系人姓名") + private String relationName; + + /** 性别 */ + @Pattern(regexp = "^[MFO]$", message = "性别只能是M、F或O") + @Schema(description = "性别:M-男,F-女,O-其他") + private String gender; + + /** 出生日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "出生日期") + private Date birthDate; + + /** 关系人证件类型 */ + @NotBlank(message = "关系人证件类型不能为空") + @Size(max = 50, message = "关系人证件类型长度不能超过50个字符") + @Schema(description = "关系人证件类型") + private String relationCertType; + + /** 关系人证件号码 */ + @NotBlank(message = "关系人证件号码不能为空") + @Size(max = 100, message = "关系人证件号码长度不能超过100个字符") + @Schema(description = "关系人证件号码") + private String relationCertNo; + + /** 手机号码1 */ + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码1格式不正确") + @Schema(description = "手机号码1") + private String mobilePhone1; + + /** 手机号码2 */ + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码2格式不正确") + @Schema(description = "手机号码2") + private String mobilePhone2; + + /** 微信名称1 */ + @Size(max = 50, message = "微信名称1长度不能超过50个字符") + @Schema(description = "微信名称1") + private String wechatNo1; + + /** 微信名称2 */ + @Size(max = 50, message = "微信名称2长度不能超过50个字符") + @Schema(description = "微信名称2") + private String wechatNo2; + + /** 微信名称3 */ + @Size(max = 50, message = "微信名称3长度不能超过50个字符") + @Schema(description = "微信名称3") + private String wechatNo3; + + /** 详细联系地址 */ + @Size(max = 500, message = "详细联系地址长度不能超过500个字符") + @Schema(description = "详细联系地址") + private String contactAddress; + + /** 关系详细描述 */ + @Size(max = 500, message = "关系详细描述长度不能超过500个字符") + @Schema(description = "关系详细描述") + private String relationDesc; + + /** 生效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "生效日期") + private Date effectiveDate; + + /** 失效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "失效日期") + private Date invalidDate; + + /** 状态 */ + @Schema(description = "状态:0-无效,1-有效") + private Integer status; + + /** 备注 */ + @Schema(description = "备注") + private String remark; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationEditDTO.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationEditDTO.java new file mode 100644 index 0000000..defaed3 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationEditDTO.java @@ -0,0 +1,125 @@ +package com.ruoyi.ccdi.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.util.Date; + +/** + * 员工亲属关系编辑DTO + * + * @author ruoyi + * @date 2026-02-09 + */ +@Data +@Schema(description = "员工亲属关系编辑") +public class CcdiStaffFmyRelationEditDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @NotNull(message = "ID不能为空") + @Schema(description = "主键ID") + private Long id; + + /** 员工身份证号 */ + @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}[\\dXx]$", message = "员工身份证号格式不正确") + @Schema(description = "员工身份证号") + private String personId; + + /** 关系类型 */ + @NotBlank(message = "关系类型不能为空") + @Size(max = 50, message = "关系类型长度不能超过50个字符") + @Schema(description = "关系类型") + private String relationType; + + /** 关系人姓名 */ + @NotBlank(message = "关系人姓名不能为空") + @Size(max = 100, message = "关系人姓名长度不能超过100个字符") + @Schema(description = "关系人姓名") + private String relationName; + + /** 性别 */ + @Pattern(regexp = "^[MFO]$", message = "性别只能是M、F或O") + @Schema(description = "性别:M-男,F-女,O-其他") + private String gender; + + /** 出生日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "出生日期") + private Date birthDate; + + /** 关系人证件类型 */ + @NotBlank(message = "关系人证件类型不能为空") + @Size(max = 50, message = "关系人证件类型长度不能超过50个字符") + @Schema(description = "关系人证件类型") + private String relationCertType; + + /** 关系人证件号码 */ + @NotBlank(message = "关系人证件号码不能为空") + @Size(max = 100, message = "关系人证件号码长度不能超过100个字符") + @Schema(description = "关系人证件号码") + private String relationCertNo; + + /** 手机号码1 */ + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码1格式不正确") + @Schema(description = "手机号码1") + private String mobilePhone1; + + /** 手机号码2 */ + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码2格式不正确") + @Schema(description = "手机号码2") + private String mobilePhone2; + + /** 微信名称1 */ + @Size(max = 50, message = "微信名称1长度不能超过50个字符") + @Schema(description = "微信名称1") + private String wechatNo1; + + /** 微信名称2 */ + @Size(max = 50, message = "微信名称2长度不能超过50个字符") + @Schema(description = "微信名称2") + private String wechatNo2; + + /** 微信名称3 */ + @Size(max = 50, message = "微信名称3长度不能超过50个字符") + @Schema(description = "微信名称3") + private String wechatNo3; + + /** 详细联系地址 */ + @Size(max = 500, message = "详细联系地址长度不能超过500个字符") + @Schema(description = "详细联系地址") + private String contactAddress; + + /** 关系详细描述 */ + @Size(max = 500, message = "关系详细描述长度不能超过500个字符") + @Schema(description = "关系详细描述") + private String relationDesc; + + /** 生效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "生效日期") + private Date effectiveDate; + + /** 失效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "失效日期") + private Date invalidDate; + + /** 状态 */ + @Schema(description = "状态:0-无效,1-有效") + private Integer status; + + /** 备注 */ + @Schema(description = "备注") + private String remark; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationQueryDTO.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationQueryDTO.java new file mode 100644 index 0000000..4a502b8 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiStaffFmyRelationQueryDTO.java @@ -0,0 +1,57 @@ +package com.ruoyi.ccdi.domain.dto; + +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.util.Date; + +/** + * 员工亲属关系查询DTO + * + * @author ruoyi + * @date 2026-02-09 + */ +@Data +@Schema(description = "员工亲属关系查询") +public class CcdiStaffFmyRelationQueryDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 员工身份证号 */ + @Schema(description = "员工身份证号") + private String personId; + + /** 员工姓名 */ + @Schema(description = "员工姓名") + private String personName; + + /** 关系类型 */ + @Schema(description = "关系类型") + private String relationType; + + /** 关系人姓名 */ + @Schema(description = "关系人姓名") + private String relationName; + + /** 状态 */ + @Schema(description = "状态:0-无效,1-有效") + private Integer status; + + /** 数据来源 */ + @Schema(description = "数据来源") + private String dataSource; + + /** 生效日期开始 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "生效日期开始") + private Date effectiveDateStart; + + /** 生效日期结束 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "生效日期结束") + private Date effectiveDateEnd; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiStaffFmyRelationExcel.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiStaffFmyRelationExcel.java new file mode 100644 index 0000000..1ddc90f --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiStaffFmyRelationExcel.java @@ -0,0 +1,116 @@ +package com.ruoyi.ccdi.domain.excel; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.ruoyi.common.annotation.DictDropdown; +import com.ruoyi.common.annotation.Required; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 员工亲属关系Excel导入导出对象 + * + * @author ruoyi + * @date 2026-02-09 + */ +@Data +public class CcdiStaffFmyRelationExcel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 员工身份证号 */ + @ExcelProperty(value = "员工身份证号*", index = 0) + @ColumnWidth(20) + @Required + private String personId; + + /** 关系类型 */ + @ExcelProperty(value = "关系类型*", index = 1) + @ColumnWidth(15) + @DictDropdown(dictType = "ccdi_relation_type") + @Required + private String relationType; + + /** 关系人姓名 */ + @ExcelProperty(value = "关系人姓名*", index = 2) + @ColumnWidth(15) + @Required + private String relationName; + + /** 性别 */ + @ExcelProperty(value = "性别", index = 3) + @ColumnWidth(10) + @DictDropdown(dictType = "ccdi_indiv_gender") + private String gender; + + /** 关系人证件类型 */ + @ExcelProperty(value = "关系人证件类型*", index = 4) + @ColumnWidth(15) + @DictDropdown(dictType = "ccdi_certificate_type") + @Required + private String relationCertType; + + /** 关系人证件号码 */ + @ExcelProperty(value = "关系人证件号码*", index = 5) + @ColumnWidth(20) + @Required + private String relationCertNo; + + /** 手机号码1 */ + @ExcelProperty(value = "手机号码1", index = 6) + @ColumnWidth(15) + private String mobilePhone1; + + /** 手机号码2 */ + @ExcelProperty(value = "手机号码2", index = 7) + @ColumnWidth(15) + private String mobilePhone2; + + /** 微信名称1 */ + @ExcelProperty(value = "微信名称1", index = 8) + @ColumnWidth(15) + private String wechatNo1; + + /** 微信名称2 */ + @ExcelProperty(value = "微信名称2", index = 9) + @ColumnWidth(15) + private String wechatNo2; + + /** 微信名称3 */ + @ExcelProperty(value = "微信名称3", index = 10) + @ColumnWidth(15) + private String wechatNo3; + + /** 详细联系地址 */ + @ExcelProperty(value = "详细联系地址", index = 11) + @ColumnWidth(30) + private String contactAddress; + + /** 关系详细描述 */ + @ExcelProperty(value = "关系详细描述", index = 12) + @ColumnWidth(30) + private String relationDesc; + + /** 生效日期 */ + @ExcelProperty(value = "生效日期", index = 13) + @ColumnWidth(15) + private String effectiveDate; + + /** 失效日期 */ + @ExcelProperty(value = "失效日期", index = 14) + @ColumnWidth(15) + private String invalidDate; + + /** 状态 */ + @ExcelProperty(value = "状态", index = 15) + @ColumnWidth(10) + private Integer status; + + /** 备注 */ + @ExcelProperty(value = "备注", index = 16) + @ColumnWidth(30) + private String remark; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffFmyRelationVO.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffFmyRelationVO.java new file mode 100644 index 0000000..5bd044d --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiStaffFmyRelationVO.java @@ -0,0 +1,144 @@ +package com.ruoyi.ccdi.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.util.Date; + +/** + * 员工亲属关系VO + * + * @author ruoyi + * @date 2026-02-09 + */ +@Data +@Schema(description = "员工亲属关系") +public class CcdiStaffFmyRelationVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @Schema(description = "主键ID") + private Long id; + + /** 员工身份证号 */ + @Schema(description = "员工身份证号") + private String personId; + + /** 员工姓名 */ + @Schema(description = "员工姓名") + private String personName; + + /** 关系类型 */ + @Schema(description = "关系类型") + private String relationType; + + /** 关系人姓名 */ + @Schema(description = "关系人姓名") + private String relationName; + + /** 性别 */ + @Schema(description = "性别") + private String gender; + + /** 性别名称 */ + @Schema(description = "性别名称") + private String genderName; + + /** 出生日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "出生日期") + private Date birthDate; + + /** 关系人证件类型 */ + @Schema(description = "关系人证件类型") + private String relationCertType; + + /** 关系人证件号码 */ + @Schema(description = "关系人证件号码") + private String relationCertNo; + + /** 手机号码1 */ + @Schema(description = "手机号码1") + private String mobilePhone1; + + /** 手机号码2 */ + @Schema(description = "手机号码2") + private String mobilePhone2; + + /** 微信名称1 */ + @Schema(description = "微信名称1") + private String wechatNo1; + + /** 微信名称2 */ + @Schema(description = "微信名称2") + private String wechatNo2; + + /** 微信名称3 */ + @Schema(description = "微信名称3") + private String wechatNo3; + + /** 详细联系地址 */ + @Schema(description = "详细联系地址") + private String contactAddress; + + /** 关系详细描述 */ + @Schema(description = "关系详细描述") + private String relationDesc; + + /** 生效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "生效日期") + private Date effectiveDate; + + /** 失效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Schema(description = "失效日期") + private Date invalidDate; + + /** 状态 */ + @Schema(description = "状态") + private Integer status; + + /** 状态名称 */ + @Schema(description = "状态名称") + private String statusName; + + /** 备注 */ + @Schema(description = "备注") + private String remark; + + /** 数据来源 */ + @Schema(description = "数据来源") + private String dataSource; + + /** 是否是员工亲属 */ + @Schema(description = "是否是员工亲属") + private Boolean isEmpFamily; + + /** 是否是客户亲属 */ + @Schema(description = "是否是客户亲属") + private Boolean isCustFamily; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "创建时间") + private Date createTime; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Schema(description = "更新时间") + private Date updateTime; + + /** 创建人 */ + @Schema(description = "创建人") + private String createdBy; + + /** 更新人 */ + @Schema(description = "更新人") + private String updatedBy; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffFmyRelationMapper.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffFmyRelationMapper.java new file mode 100644 index 0000000..be9df62 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffFmyRelationMapper.java @@ -0,0 +1,53 @@ +package com.ruoyi.ccdi.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.CcdiStaffFmyRelation; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationQueryDTO; +import com.ruoyi.ccdi.domain.vo.CcdiStaffFmyRelationVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 员工亲属关系 数据层 + * + * @author ruoyi + * @date 2026-02-09 + */ +public interface CcdiStaffFmyRelationMapper extends BaseMapper { + + /** + * 分页查询员工亲属关系列表 + * + * @param page 分页对象 + * @param queryDTO 查询条件 + * @return 员工亲属关系VO分页结果 + */ + Page selectRelationPage(@Param("page") Page page, + @Param("query") CcdiStaffFmyRelationQueryDTO queryDTO); + + /** + * 查询员工亲属关系详情 + * + * @param id 主键ID + * @return 员工亲属关系VO + */ + CcdiStaffFmyRelationVO selectRelationById(@Param("id") Long id); + + /** + * 查询员工亲属关系列表(用于导出) + * + * @param queryDTO 查询条件 + * @return 员工亲属关系VO列表 + */ + List selectRelationListForExport(@Param("query") CcdiStaffFmyRelationQueryDTO queryDTO); + + /** + * 批量插入员工亲属关系数据 + * + * @param list 员工亲属关系列表 + * @return 插入行数 + */ + int insertBatch(@Param("list") List list); +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiStaffFmyRelationImportService.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiStaffFmyRelationImportService.java new file mode 100644 index 0000000..48064e4 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiStaffFmyRelationImportService.java @@ -0,0 +1,41 @@ +package com.ruoyi.ccdi.service; + +import com.ruoyi.ccdi.domain.excel.CcdiStaffFmyRelationExcel; +import com.ruoyi.ccdi.domain.vo.ImportStatusVO; +import com.ruoyi.ccdi.domain.vo.StaffFmyRelationImportFailureVO; + +import java.util.List; + +/** + * 员工亲属关系异步导入 服务层 + * + * @author ruoyi + * @date 2026-02-09 + */ +public interface ICcdiStaffFmyRelationImportService { + + /** + * 异步导入员工亲属关系数据 + * + * @param excelList Excel实体列表 + * @param taskId 任务ID + * @param userName 用户名 + */ + void importRelationAsync(List excelList, String taskId, String userName); + + /** + * 查询导入失败记录 + * + * @param taskId 任务ID + * @return 失败记录列表 + */ + List getImportFailures(String taskId); + + /** + * 查询导入状态 + * + * @param taskId 任务ID + * @return 导入状态信息 + */ + ImportStatusVO getImportStatus(String taskId); +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiStaffFmyRelationService.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiStaffFmyRelationService.java new file mode 100644 index 0000000..5b92b6e --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiStaffFmyRelationService.java @@ -0,0 +1,84 @@ +package com.ruoyi.ccdi.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationAddDTO; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationEditDTO; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationQueryDTO; +import com.ruoyi.ccdi.domain.excel.CcdiStaffFmyRelationExcel; +import com.ruoyi.ccdi.domain.vo.CcdiStaffFmyRelationVO; + +import java.util.List; + +/** + * 员工亲属关系 服务层 + * + * @author ruoyi + * @date 2026-02-09 + */ +public interface ICcdiStaffFmyRelationService { + + /** + * 查询员工亲属关系列表 + * + * @param queryDTO 查询条件 + * @return 员工亲属关系VO集合 + */ + List selectRelationList(CcdiStaffFmyRelationQueryDTO queryDTO); + + /** + * 分页查询员工亲属关系列表 + * + * @param page 分页对象 + * @param queryDTO 查询条件 + * @return 员工亲属关系VO分页结果 + */ + Page selectRelationPage(Page page, CcdiStaffFmyRelationQueryDTO queryDTO); + + /** + * 查询员工亲属关系详情 + * + * @param id 主键ID + * @return 员工亲属关系VO + */ + CcdiStaffFmyRelationVO selectRelationById(Long id); + + /** + * 新增员工亲属关系 + * + * @param addDTO 新增DTO + * @return 结果 + */ + int insertRelation(CcdiStaffFmyRelationAddDTO addDTO); + + /** + * 修改员工亲属关系 + * + * @param editDTO 编辑DTO + * @return 结果 + */ + int updateRelation(CcdiStaffFmyRelationEditDTO editDTO); + + /** + * 批量删除员工亲属关系 + * + * @param ids 需要删除的主键ID + * @return 结果 + */ + int deleteRelationByIds(Long[] ids); + + /** + * 查询员工亲属关系列表(用于导出) + * + * @param queryDTO 查询条件 + * @return 员工亲属关系Excel实体集合 + */ + List selectRelationListForExport(CcdiStaffFmyRelationQueryDTO queryDTO); + + /** + * 导入员工亲属关系数据(异步) + * + * @param excelList Excel实体列表 + * @return 任务ID + */ + String importRelation(List excelList); +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationImportServiceImpl.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationImportServiceImpl.java new file mode 100644 index 0000000..b9e5af7 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationImportServiceImpl.java @@ -0,0 +1,237 @@ +package com.ruoyi.ccdi.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.ccdi.domain.CcdiStaffFmyRelation; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationAddDTO; +import com.ruoyi.ccdi.domain.excel.CcdiStaffFmyRelationExcel; +import com.ruoyi.ccdi.domain.vo.ImportResult; +import com.ruoyi.ccdi.domain.vo.ImportStatusVO; +import com.ruoyi.ccdi.domain.vo.StaffFmyRelationImportFailureVO; +import com.ruoyi.ccdi.mapper.CcdiStaffFmyRelationMapper; +import com.ruoyi.ccdi.service.ICcdiStaffFmyRelationImportService; +import com.ruoyi.common.utils.StringUtils; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * 员工亲属关系异步导入服务层处理 + * + * @author ruoyi + * @date 2026-02-09 + */ +@Service +@EnableAsync +public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelationImportService { + + @Resource + private CcdiStaffFmyRelationMapper relationMapper; + + @Resource + private RedisTemplate redisTemplate; + + @Override + @Async + @Transactional + public void importRelationAsync(List excelList, String taskId, String userName) { + List newRecords = new ArrayList<>(); + List failures = new ArrayList<>(); + + // 用于跟踪Excel文件内已处理的唯一键(personId + relationCertNo) + Set processedKeys = new HashSet<>(); + + // 分类数据 + for (int i = 0; i < excelList.size(); i++) { + CcdiStaffFmyRelationExcel excel = excelList.get(i); + + try { + // 转换为AddDTO进行验证 + CcdiStaffFmyRelationAddDTO addDTO = new CcdiStaffFmyRelationAddDTO(); + BeanUtils.copyProperties(excel, addDTO); + + // 验证数据 + validateRelationData(addDTO); + + // 生成唯一键 + String uniqueKey = excel.getPersonId() + "|" + excel.getRelationCertNo(); + + // 检查是否在文件内重复 + if (processedKeys.contains(uniqueKey)) { + throw new RuntimeException(String.format("员工[%s]的关系人[%s]在导入文件中重复", excel.getPersonId(), excel.getRelationCertNo())); + } + + CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation(); + BeanUtils.copyProperties(excel, relation); + + relation.setCreatedBy(userName); + relation.setUpdatedBy(userName); + relation.setIsEmpFamily(true); + relation.setIsCustFamily(false); + relation.setDataSource("IMPORT"); + if (relation.getStatus() == null) { + relation.setStatus(1); + } + + newRecords.add(relation); + processedKeys.add(uniqueKey); + + } catch (Exception e) { + StaffFmyRelationImportFailureVO failure = new StaffFmyRelationImportFailureVO(); + BeanUtils.copyProperties(excel, failure); + failure.setErrorMessage(e.getMessage()); + failures.add(failure); + } + } + + // 批量插入新数据 + if (!newRecords.isEmpty()) { + saveBatch(newRecords, 500); + } + + // 保存失败记录到Redis + if (!failures.isEmpty()) { + String failuresKey = "import:staffFmyRelation:" + taskId + ":failures"; + redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); + } + + ImportResult result = new ImportResult(); + result.setTotalCount(excelList.size()); + result.setSuccessCount(newRecords.size()); + result.setFailureCount(failures.size()); + + // 更新最终状态 + String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"; + updateImportStatus(taskId, finalStatus, result); + } + + /** + * 获取导入失败记录 + * + * @param taskId 任务ID + * @return 失败记录列表 + */ + @Override + public List getImportFailures(String taskId) { + String key = "import:staffFmyRelation:" + taskId + ":failures"; + Object failuresObj = redisTemplate.opsForValue().get(key); + + if (failuresObj == null) { + return Collections.emptyList(); + } + + return JSON.parseArray(JSON.toJSONString(failuresObj), StaffFmyRelationImportFailureVO.class); + } + + /** + * 查询导入状态 + * + * @param taskId 任务ID + * @return 导入状态信息 + */ + @Override + public ImportStatusVO getImportStatus(String taskId) { + String key = "import:staffFmyRelation:" + taskId; + Boolean hasKey = redisTemplate.hasKey(key); + + if (Boolean.FALSE.equals(hasKey)) { + 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; + } + + /** + * 更新导入状态 + */ + private void updateImportStatus(String taskId, String status, ImportResult result) { + String key = "import:staffFmyRelation:" + taskId; + 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()); + + if ("SUCCESS".equals(status)) { + statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据"); + } else { + statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); + } + + redisTemplate.opsForHash().putAll(key, statusData); + } + + /** + * 批量保存 + */ + private void saveBatch(List list, int batchSize) { + // 使用真正的批量插入,分批次执行以提高性能 + for (int i = 0; i < list.size(); i += batchSize) { + int end = Math.min(i + batchSize, list.size()); + List subList = list.subList(i, end); + relationMapper.insertBatch(subList); + } + } + + /** + * 验证员工亲属关系数据 + * + * @param addDTO 新增DTO + */ + private void validateRelationData(CcdiStaffFmyRelationAddDTO addDTO) { + // 验证必填字段 + if (StringUtils.isEmpty(addDTO.getPersonId())) { + throw new RuntimeException("员工身份证号不能为空"); + } + if (StringUtils.isEmpty(addDTO.getRelationType())) { + throw new RuntimeException("关系类型不能为空"); + } + if (StringUtils.isEmpty(addDTO.getRelationName())) { + throw new RuntimeException("关系人姓名不能为空"); + } + if (StringUtils.isEmpty(addDTO.getRelationCertType())) { + throw new RuntimeException("关系人证件类型不能为空"); + } + if (StringUtils.isEmpty(addDTO.getRelationCertNo())) { + throw new RuntimeException("关系人证件号码不能为空"); + } + + // 验证身份证号格式 + 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}[\\dXx]$")) { + throw new RuntimeException("员工身份证号格式不正确"); + } + + // 验证手机号格式(如果提供) + if (StringUtils.isNotEmpty(addDTO.getMobilePhone1()) && !addDTO.getMobilePhone1().matches("^1[3-9]\\d{9}$")) { + throw new RuntimeException("手机号码1格式不正确"); + } + if (StringUtils.isNotEmpty(addDTO.getMobilePhone2()) && !addDTO.getMobilePhone2().matches("^1[3-9]\\d{9}$")) { + throw new RuntimeException("手机号码2格式不正确"); + } + + // 验证性别值(如果提供) + if (StringUtils.isNotEmpty(addDTO.getGender()) && !addDTO.getGender().matches("^[MFO]$")) { + throw new RuntimeException("性别只能是M、F或O"); + } + } +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java new file mode 100644 index 0000000..43f8ffa --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java @@ -0,0 +1,187 @@ +package com.ruoyi.ccdi.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.CcdiStaffFmyRelation; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationAddDTO; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationEditDTO; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationQueryDTO; +import com.ruoyi.ccdi.domain.excel.CcdiStaffFmyRelationExcel; +import com.ruoyi.ccdi.domain.vo.CcdiStaffFmyRelationVO; +import com.ruoyi.ccdi.mapper.CcdiStaffFmyRelationMapper; +import com.ruoyi.ccdi.service.ICcdiStaffFmyRelationImportService; +import com.ruoyi.ccdi.service.ICcdiStaffFmyRelationService; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 员工亲属关系 服务层处理 + * + * @author ruoyi + * @date 2026-02-09 + */ +@Service +public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationService { + + @Resource + private CcdiStaffFmyRelationMapper relationMapper; + + @Resource + private ICcdiStaffFmyRelationImportService relationImportService; + + @Resource + private RedisTemplate redisTemplate; + + /** + * 查询员工亲属关系列表 + * + * @param queryDTO 查询条件 + * @return 员工亲属关系VO集合 + */ + @Override + public java.util.List selectRelationList(CcdiStaffFmyRelationQueryDTO queryDTO) { + Page page = new Page<>(1, Integer.MAX_VALUE); + Page resultPage = relationMapper.selectRelationPage(page, queryDTO); + return resultPage.getRecords(); + } + + /** + * 分页查询员工亲属关系列表 + * + * @param page 分页对象 + * @param queryDTO 查询条件 + * @return 员工亲属关系VO分页结果 + */ + @Override + public Page selectRelationPage(Page page, CcdiStaffFmyRelationQueryDTO queryDTO) { + return relationMapper.selectRelationPage(page, queryDTO); + } + + /** + * 查询员工亲属关系列表(用于导出) + * + * @param queryDTO 查询条件 + * @return 员工亲属关系Excel实体集合 + */ + @Override + public java.util.List selectRelationListForExport(CcdiStaffFmyRelationQueryDTO queryDTO) { + return relationMapper.selectRelationListForExport(queryDTO).stream().map(vo -> { + CcdiStaffFmyRelationExcel excel = new CcdiStaffFmyRelationExcel(); + BeanUtils.copyProperties(vo, excel); + return excel; + }).collect(Collectors.toList()); + } + + /** + * 查询员工亲属关系详情 + * + * @param id 主键ID + * @return 员工亲属关系VO + */ + @Override + public CcdiStaffFmyRelationVO selectRelationById(Long id) { + return relationMapper.selectRelationById(id); + } + + /** + * 新增员工亲属关系 + * + * @param addDTO 新增DTO + * @return 结果 + */ + @Override + @Transactional + public int insertRelation(CcdiStaffFmyRelationAddDTO addDTO) { + CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation(); + BeanUtils.copyProperties(addDTO, relation); + + // 设置默认值 + relation.setIsEmpFamily(true); + relation.setIsCustFamily(false); + relation.setDataSource("MANUAL"); + if (relation.getStatus() == null) { + relation.setStatus(1); + } + + int result = relationMapper.insert(relation); + return result; + } + + /** + * 修改员工亲属关系 + * + * @param editDTO 编辑DTO + * @return 结果 + */ + @Override + @Transactional + public int updateRelation(CcdiStaffFmyRelationEditDTO editDTO) { + CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation(); + BeanUtils.copyProperties(editDTO, relation); + int result = relationMapper.updateById(relation); + return result; + } + + /** + * 批量删除员工亲属关系 + * + * @param ids 需要删除的主键ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRelationByIds(Long[] ids) { + return relationMapper.deleteBatchIds(java.util.List.of(ids)); + } + + /** + * 导入员工亲属关系数据(异步) + * + * @param excelList Excel实体列表 + * @return 任务ID + */ + @Override + @Transactional + public String importRelation(java.util.List excelList) { + if (StringUtils.isNull(excelList) || excelList.isEmpty()) { + throw new RuntimeException("至少需要一条数据"); + } + + // 生成任务ID + String taskId = UUID.randomUUID().toString(); + long startTime = System.currentTimeMillis(); + + // 获取当前用户名 + String userName = SecurityUtils.getUsername(); + + // 初始化Redis状态 + String statusKey = "import:staffFmyRelation:" + taskId; + 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", startTime); + statusData.put("message", "正在处理..."); + + redisTemplate.opsForHash().putAll(statusKey, statusData); + redisTemplate.expire(statusKey, 7, TimeUnit.DAYS); + + // 调用异步导入服务 + relationImportService.importRelationAsync(excelList, taskId, userName); + + return taskId; + } +} diff --git a/ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffFmyRelationMapper.xml b/ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffFmyRelationMapper.xml new file mode 100644 index 0000000..37e07da --- /dev/null +++ b/ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffFmyRelationMapper.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO ccdi_staff_fmy_relation + (person_id, relation_type, relation_name, gender, birth_date, relation_cert_type, relation_cert_no, + mobile_phone1, mobile_phone2, wechat_no1, wechat_no2, wechat_no3, contact_address, relation_desc, + effective_date, invalid_date, status, remark, data_source, is_emp_family, is_cust_family, + created_by, create_time, updated_by, update_time) + VALUES + + (#{item.personId}, #{item.relationType}, #{item.relationName}, #{item.gender}, + #{item.birthDate}, #{item.relationCertType}, #{item.relationCertNo}, #{item.mobilePhone1}, + #{item.mobilePhone2}, #{item.wechatNo1}, #{item.wechatNo2}, #{item.wechatNo3}, #{item.contactAddress}, + #{item.relationDesc}, #{item.effectiveDate}, #{item.invalidDate}, #{item.status}, #{item.remark}, + #{item.dataSource}, #{item.isEmpFamily}, #{item.isCustFamily}, #{item.createdBy}, NOW(), #{item.updatedBy}, NOW()) + + + + diff --git a/ruoyi-ui/src/api/ccdiStaffFmyRelation.js b/ruoyi-ui/src/api/ccdiStaffFmyRelation.js new file mode 100644 index 0000000..d082d17 --- /dev/null +++ b/ruoyi-ui/src/api/ccdiStaffFmyRelation.js @@ -0,0 +1,99 @@ +import request from '@/utils/request' + +// 查询员工亲属关系列表 +export function listRelation(query) { + return request({ + url: '/ccdi/staffFmyRelation/list', + method: 'get', + params: query + }) +} + +// 查询员工亲属关系详情 +export function getRelation(id) { + return request({ + url: '/ccdi/staffFmyRelation/' + id, + method: 'get' + }) +} + +// 新增员工亲属关系 +export function addRelation(data) { + return request({ + url: '/ccdi/staffFmyRelation', + method: 'post', + data: data + }) +} + +// 修改员工亲属关系 +export function updateRelation(data) { + return request({ + url: '/ccdi/staffFmyRelation', + method: 'put', + data: data + }) +} + +// 删除员工亲属关系 +export function delRelation(ids) { + return request({ + url: '/ccdi/staffFmyRelation/' + ids, + method: 'delete' + }) +} + +// 导出员工亲属关系 +export function exportRelation(query) { + return request({ + url: '/ccdi/staffFmyRelation/export', + method: 'post', + params: query + }) +} + +// 下载导入模板 +export function importTemplate() { + return request({ + url: '/ccdi/staffFmyRelation/importTemplate', + method: 'post' + }) +} + +// 导入员工亲属关系 +export function importData(file, updateSupport) { + const formData = new FormData() + formData.append('file', file) + formData.append('updateSupport', updateSupport) + return request({ + url: '/ccdi/staffFmyRelation/importData', + method: 'post', + data: formData + }) +} + +// 查询导入状态 +export function getImportStatus(taskId) { + return request({ + url: '/ccdi/staffFmyRelation/importStatus/' + taskId, + method: 'get' + }) +} + +// 查询导入失败记录 +export function getImportFailures(taskId, pageNum, pageSize) { + return request({ + url: '/ccdi/staffFmyRelation/importFailures/' + taskId, + method: 'get', + params: { pageNum, pageSize } + }) +} + +// 获取员工列表(用于下拉选择) +export function getStaffList(name) { + return request({ + url: '/ccdi/staffFmyRelation/staffList', + method: 'get', + params: { name } + }) +} diff --git a/ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue b/ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue new file mode 100644 index 0000000..3e2f759 --- /dev/null +++ b/ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue @@ -0,0 +1,988 @@ + + + + + diff --git a/sql/ccdi_staff_fmy_relation.sql b/sql/ccdi_staff_fmy_relation.sql new file mode 100644 index 0000000..c975b63 --- /dev/null +++ b/sql/ccdi_staff_fmy_relation.sql @@ -0,0 +1,44 @@ +-- 员工亲属关系表 +-- 创建时间: 2026-02-09 +-- 说明: 存储员工家庭成员关系信息,仅处理员工家庭关系(is_emp_family=1) + +CREATE TABLE IF NOT EXISTS `ccdi_staff_fmy_relation` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `person_id` VARCHAR(100) NOT NULL COMMENT '员工身份证号', + `relation_type` VARCHAR(50) NOT NULL COMMENT '关系类型(配偶、子女、父母、兄弟姐妹等)', + `relation_name` VARCHAR(100) NOT NULL COMMENT '关系人姓名', + `gender` CHAR(1) DEFAULT NULL COMMENT '性别:M-男,F-女,O-其他', + `birth_date` DATE DEFAULT NULL COMMENT '关系人出生日期', + `relation_cert_type` VARCHAR(50) NOT NULL COMMENT '关系人证件类型', + `relation_cert_no` VARCHAR(50) NOT NULL COMMENT '关系人证件号码', + `mobile_phone1` VARCHAR(20) DEFAULT NULL COMMENT '手机号码1', + `mobile_phone2` VARCHAR(20) DEFAULT NULL COMMENT '手机号码2', + `wechat_no1` VARCHAR(50) DEFAULT NULL COMMENT '微信名称1', + `wechat_no2` VARCHAR(50) DEFAULT NULL COMMENT '微信名称2', + `wechat_no3` VARCHAR(50) DEFAULT NULL COMMENT '微信名称3', + `contact_address` VARCHAR(500) DEFAULT NULL COMMENT '详细联系地址', + `relation_desc` VARCHAR(500) DEFAULT NULL COMMENT '关系详细描述', + `status` INT(11) NOT NULL DEFAULT 1 COMMENT '状态:0-无效,1-有效', + `effective_date` DATETIME DEFAULT NULL COMMENT '关系生效日期', + `invalid_date` DATETIME DEFAULT NULL COMMENT '关系失效日期', + `remark` TEXT DEFAULT NULL COMMENT '备注信息', + `data_source` VARCHAR(50) DEFAULT NULL COMMENT '数据来源:MANUAL-手工录入,SYSTEM-系统同步,IMPORT-批量导入,API-接口获取', + `is_emp_family` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否是员工的家庭关系:0-否 1-是', + `is_cust_family` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否是信贷客户的家庭关系:0-否 1-是', + `created_by` VARCHAR(100) NOT NULL COMMENT '记录创建人', + `updated_by` VARCHAR(100) DEFAULT NULL COMMENT '记录更新人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_person_cert` (`person_id`, `relation_cert_no`) COMMENT '员工身份证号+关系人证件号码唯一', + KEY `idx_person_id` (`person_id`) COMMENT '员工身份证号索引', + KEY `idx_relation_cert_no` (`relation_cert_no`) COMMENT '关系人证件号码索引', + KEY `idx_status` (`status`) COMMENT '状态索引', + KEY `idx_data_source` (`data_source`) COMMENT '数据来源索引' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工家庭关系表'; + +-- 测试数据 +-- INSERT INTO `ccdi_staff_fmy_relation` +-- (`person_id`, `relation_type`, `relation_name`, `gender`, `birth_date`, `relation_cert_type`, `relation_cert_no`, `mobile_phone1`, `contact_address`, `status`, `data_source`, `is_emp_family`, `is_cust_family`, `created_by`) +-- VALUES +-- ('110101199001011234', '配偶', '张三', 'M', '1990-05-15', '身份证', '110101199001015678', '13800138000', '北京市朝阳区', 1, 'MANUAL', 1, 0, 'admin'); diff --git a/sql/ccdi_staff_fmy_relation_menu.sql b/sql/ccdi_staff_fmy_relation_menu.sql new file mode 100644 index 0000000..5ad2f48 --- /dev/null +++ b/sql/ccdi_staff_fmy_relation_menu.sql @@ -0,0 +1,45 @@ +-- 添加员工亲属关系维护菜单 +-- 注意: 执行前请确认已存在"信息维护"父菜单 +-- 如果不存在,请先执行以下语句创建父菜单: +-- INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +-- VALUES (2000, '信息维护', 0, 4, 'dpc', NULL, '', '', 1, 0, 'M', '0', '0', '', 'example', 'admin', NOW(), '信息维护目录'); + +-- 查询信息维护父菜单ID +SET @parent_menu_id = (SELECT menu_id FROM sys_menu WHERE menu_name='信息维护' AND parent_id=0 LIMIT 1); + +-- 添加员工亲属关系维护菜单 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +VALUES +('员工亲属关系维护', @parent_menu_id, 3, 'staffFmyRelation', 'ccdiStaffFmyRelation/index', 1, 0, 'C', '0', '0', 'ccdi:staffFmyRelation:list', 'peoples', 'admin', NOW(), '', NULL, '员工亲属关系信息管理菜单'); + +-- 获取刚插入的菜单ID +SET @menu_id = LAST_INSERT_ID(); + +-- 添加按钮权限 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) VALUES +('亲属关系查询', @menu_id, 1, '', '', 1, 0, 'F', '0', '0', 'ccdi:staffFmyRelation:query', '#', 'admin', NOW(), ''), +('亲属关系新增', @menu_id, 2, '', '', 1, 0, 'F', '0', '0', 'ccdi:staffFmyRelation:add', '#', 'admin', NOW(), ''), +('亲属关系修改', @menu_id, 3, '', '', 1, 0, 'F', '0', '0', 'ccdi:staffFmyRelation:edit', '#', 'admin', NOW(), ''), +('亲属关系删除', @menu_id, 4, '', '', 1, 0, 'F', '0', '0', 'ccdi:staffFmyRelation:remove', '#', 'admin', NOW(), ''), +('亲属关系导出', @menu_id, 5, '', '', 1, 0, 'F', '0', '0', 'ccdi:staffFmyRelation:export', '#', 'admin', NOW(), ''), +('亲属关系导入', @menu_id, 6, '', '', 1, 0, 'F', '0', '0', 'ccdi:staffFmyRelation:import', '#', 'admin', NOW(), ''); + +-- 查询结果验证 +SELECT + m.menu_id AS '菜单ID', + m.menu_name AS '菜单名称', + m.parent_id AS '父菜单ID', + p.menu_name AS '父菜单名称', + m.order_num AS '显示顺序', + m.path AS '路由地址', + m.component AS '组件路径', + m.menu_type AS '菜单类型', + m.perms AS '权限标识', + m.icon AS '菜单图标', + m.visible AS '显示状态', + m.status AS '菜单状态', + m.create_time AS '创建时间' +FROM sys_menu m +LEFT JOIN sys_menu p ON m.parent_id = p.menu_id +WHERE m.menu_name = '员工亲属关系维护' OR m.parent_id = @menu_id +ORDER BY m.parent_id, m.order_num; diff --git a/test_result.json b/test_result.json new file mode 100644 index 0000000..fdbb101 --- /dev/null +++ b/test_result.json @@ -0,0 +1,4 @@ +{ + "msg": "\r\n### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'r.contact_phone' in 'field list'\r\n### The error may exist in file [D:\\ccdi\\ccdi\\ruoyi-ccdi\\target\\classes\\mapper\\ccdi\\CcdiStaffFmyRelationMapper.xml]\r\n### The error may involve defaultParameterMap\r\n### The error occurred while setting parameters\r\n### SQL: SELECT r.id, r.person_id, s.name as person_name, r.relation_type, r.relation_name, r.gender, r.relation_cert_type, r.relation_cert_no, r.contact_phone, r.address, r.occupation, r.work_unit, r.effective_date, r.expiry_date, r.status, r.remark, r.data_source, r.is_emp_family, r.is_cust_family, r.created_by, r.create_time, r.updated_by, r.update_time FROM ccdi_staff_fmy_relation r LEFT JOIN ccdi_base_staff s ON r.person_id = s.person_id WHERE r.is_emp_family = 1 ORDER BY r.create_time DESC LIMIT ?\r\n### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'r.contact_phone' in 'field list'\n; bad SQL grammar []", + "code": 500 +} \ No newline at end of file diff --git a/test_staff_fmy_relation_dropdown.py b/test_staff_fmy_relation_dropdown.py new file mode 100644 index 0000000..ffd72ca --- /dev/null +++ b/test_staff_fmy_relation_dropdown.py @@ -0,0 +1,264 @@ +""" +员工亲属关系功能测试脚本 +测试员工身份证号字段改造后的功能 +""" + +import requests +import json + +BASE_URL = "http://localhost:8080" + +# 登录获取 token +def login(): + url = f"{BASE_URL}/login/test" + data = { + "username": "admin", + "password": "admin123" + } + response = requests.post(url, json=data) + result = response.json() + if result.get("code") == 200: + token = result.get("token") + print("✓ 登录成功") + return token + else: + print(f"✗ 登录失败: {result.get('msg')}") + return None + +# 获取请求头 +def get_headers(token): + return { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + } + +# 测试1: 查询员工列表(用于下拉选择) +def test_list_staff(): + print("\n【测试1】查询员工列表") + url = f"{BASE_URL}/ccdi/baseStaff/list" + params = { + "pageNum": 1, + "pageSize": 10 + } + response = requests.get(url, params=params, headers=get_headers(token)) + result = response.json() + if result.get("code") == 200: + rows = result.get("rows", []) + print(f"✓ 查询成功,共 {len(rows)} 条员工记录") + if rows: + print(f" 示例: {rows[0].get('name')} - {rows[0].get('idCard')}") + return rows[0].get("idCard") # 返回第一个员工的身份证号 + else: + print(" 警告: 暂无员工数据") + return None + else: + print(f"✗ 查询失败: {result.get('msg')}") + return None + +# 测试2: 根据身份证号搜索员工 +def test_search_staff(keyword): + print(f"\n【测试2】根据身份证号搜索员工: {keyword}") + url = f"{BASE_URL}/ccdi/baseStaff/list" + params = { + "idCard": keyword, + "pageNum": 1, + "pageSize": 10 + } + response = requests.get(url, params=params, headers=get_headers(token)) + result = response.json() + if result.get("code") == 200: + rows = result.get("rows", []) + print(f"✓ 搜索成功,找到 {len(rows)} 条记录") + for row in rows[:3]: # 只显示前3条 + print(f" - {row.get('name')} - {row.get('idCard')}") + return rows + else: + print(f"✗ 搜索失败: {result.get('msg')}") + return [] + +# 测试3: 查询已有亲属关系(避免重复插入) +def test_get_existing_relation(person_id): + print(f"\n【测试3】查询已有员工亲属关系") + url = f"{BASE_URL}/ccdi/staffFmyRelation/list" + params = { + "personId": person_id, + "pageNum": 1, + "pageSize": 1 + } + response = requests.get(url, params=params, headers=get_headers(token)) + result = response.json() + if result.get("code") == 200: + rows = result.get("rows", []) + if rows: + relation_id = rows[0].get("id") + print(f"✓ 找到已有记录, ID: {relation_id}") + return relation_id + else: + print(" 未找到已有记录,准备新增测试数据") + return None + else: + print(f"✗ 查询失败: {result.get('msg')}") + return None + +# 测试4: 新增员工亲属关系(如果没有现有数据) +def test_add_relation(person_id): + print(f"\n【测试4】新增员工亲属关系") + url = f"{BASE_URL}/ccdi/staffFmyRelation" + import random + random_suffix = random.randint(10000, 99999) + data = { + "personId": person_id, + "relationType": "01", # 配偶 + "relationName": "测试亲属", + "gender": "M", + "relationCertType": "01", # 身份证 + "relationCertNo": f"11010119900101{random_suffix}", # 随机生成避免重复 + "status": 1 + } + response = requests.post(url, json=data, headers=get_headers(token)) + result = response.json() + if result.get("code") == 200: + # 若依框架新增接口返回的是 "操作成功" 消息,没有返回ID + # 需要通过查询列表来获取新增的记录 + print(f"✓ 新增成功") + # 查询列表获取最新记录的ID + list_url = f"{BASE_URL}/ccdi/staffFmyRelation/list" + list_params = { + "personId": person_id, + "pageNum": 1, + "pageSize": 1 + } + list_response = requests.get(list_url, params=list_params, headers=get_headers(token)) + list_result = list_response.json() + if list_result.get("rows"): + relation_id = list_result.get("rows")[0].get("id") + print(f" 获取到记录ID: {relation_id}") + return relation_id + return None + else: + print(f"✗ 新增失败: {result.get('msg')}") + return None + +# 测试5: 查询员工亲属关系列表 +def test_list_relations(person_id): + print(f"\n【测试5】查询员工亲属关系列表") + url = f"{BASE_URL}/ccdi/staffFmyRelation/list" + params = { + "personId": person_id, + "pageNum": 1, + "pageSize": 10 + } + response = requests.get(url, params=params, headers=get_headers(token)) + result = response.json() + if result.get("code") == 200: + rows = result.get("rows", []) + total = result.get("total", 0) + print(f"✓ 查询成功,共 {total} 条记录") + for row in rows: + print(f" - {row.get('personName')}({row.get('personId')}) 的{row.get('relationType')} - {row.get('relationName')}") + return rows + else: + print(f"✗ 查询失败: {result.get('msg')}") + return [] + +# 测试6: 查询亲属关系详情 +def test_get_relation_detail(relation_id): + print(f"\n【测试6】查询亲属关系详情: ID={relation_id}") + url = f"{BASE_URL}/ccdi/staffFmyRelation/{relation_id}" + response = requests.get(url, headers=get_headers(token)) + result = response.json() + if result.get("code") == 200: + data = result.get("data") + print(f"✓ 查询成功") + print(f" 员工: {data.get('personName')}({data.get('personId')})") + print(f" 关系: {data.get('relationType')} - {data.get('relationName')}") + print(f" 状态: {'有效' if data.get('status') == 1 else '无效'}") + return data + else: + print(f"✗ 查询失败: {result.get('msg')}") + return None + +# 测试7: 编辑员工亲属关系(只修改非身份证号字段) +def test_update_relation(relation_id): + print(f"\n【测试7】编辑员工亲属关系: ID={relation_id}") + url = f"{BASE_URL}/ccdi/staffFmyRelation" + data = { + "id": relation_id, + "relationName": "测试亲属(已修改)", + "mobilePhone1": "13800138000", + "status": 1 + } + response = requests.put(url, json=data, headers=get_headers(token)) + result = response.json() + if result.get("code") == 200: + print(f"✓ 修改成功") + return True + else: + print(f"✗ 修改失败: {result.get('msg')}") + return False + +# 测试8: 删除员工亲属关系 +def test_delete_relation(relation_id): + print(f"\n【测试8】删除员工亲属关系: ID={relation_id}") + url = f"{BASE_URL}/ccdi/staffFmyRelation/{relation_id}" + response = requests.delete(url, headers=get_headers(token)) + result = response.json() + if result.get("code") == 200: + print(f"✓ 删除成功") + return True + else: + print(f"✗ 删除失败: {result.get('msg')}") + return False + +# 主测试流程 +def main(): + global token + print("=" * 60) + print("员工亲属关系功能测试") + print("=" * 60) + + # 登录 + token = login() + if not token: + return + + # 测试1: 查询员工列表 + person_id = test_list_staff() + if not person_id: + print("\n提示: 请先在系统中添加员工数据") + return + + # 测试2: 搜索员工 + test_search_staff(person_id[:6]) # 使用身份证号前6位搜索 + + # 测试3: 查询已有亲属关系 + relation_id = test_get_existing_relation(person_id) + + # 如果没有已有记录,则新增 + if not relation_id: + relation_id = test_add_relation(person_id) + if not relation_id: + print("\n提示: 无法获取测试记录ID,跳过后续测试") + return + + # 测试5: 查询亲属关系列表 + test_list_relations(person_id) + + # 测试6: 查询详情 + test_get_relation_detail(relation_id) + + # 测试7: 编辑亲属关系 + test_update_relation(relation_id) + + # 测试8: 再次查看详情(验证修改) + test_get_relation_detail(relation_id) + + # 清理测试数据(仅限新增的数据) + print("\n【清理】测试结束(保留现有数据)") + + print("\n" + "=" * 60) + print("测试完成!") + print("=" * 60) + +if __name__ == "__main__": + main() diff --git a/test_staff_fmy_relation_fix.py b/test_staff_fmy_relation_fix.py new file mode 100644 index 0000000..a430c2a --- /dev/null +++ b/test_staff_fmy_relation_fix.py @@ -0,0 +1,105 @@ +import requests +import json + +# 配置 +BASE_URL = "http://localhost:8080" +LOGIN_URL = f"{BASE_URL}/login/test" +LIST_URL = f"{BASE_URL}/ccdi/staffFmyRelation/list" + +# 测试账号 +test_user = { + "username": "admin", + "password": "admin123" +} + +def test_fix(): + """测试修复是否成功""" + print("=" * 60) + print("开始测试员工家庭关系接口修复") + print("=" * 60) + + # Step 1: 获取token + print("\n[1/2] 正在登录获取token...") + + headers = { + "Content-Type": "application/json" + } + + login_response = requests.post(LOGIN_URL, json=test_user, headers=headers) + + if login_response.status_code != 200: + print(f"❌ 登录失败: {login_response.status_code}") + print(login_response.text) + return False + + login_data = login_response.json() + if login_data.get("code") != 200: + print(f"❌ 登录失败: {login_data.get('msg')}") + return False + + token = login_data.get("token") + print(f"✅ 登录成功,获取到token: {token[:20]}...") + + # Step 2: 调用分页查询接口 + print("\n[2/2] 正在测试分页查询接口...") + + headers = { + "Authorization": f"Bearer {token}" + } + + params = { + "pageNum": 1, + "pageSize": 10, + "isEmpFamily": 1 + } + + list_response = requests.get(LIST_URL, params=params, headers=headers) + + print(f"\n响应状态码: {list_response.status_code}") + + if list_response.status_code != 200: + print(f"❌ 接口调用失败") + print(list_response.text) + return False + + result = list_response.json() + + # 保存完整响应 + with open("test_result.json", "w", encoding="utf-8") as f: + json.dump(result, f, ensure_ascii=False, indent=2) + + print(f"\n完整响应已保存到 test_result.json") + + if result.get("code") == 200: + rows = result.get("rows", []) + total = result.get("total", 0) + + print(f"✅ 接口调用成功!") + print(f" - 数据总数: {total}") + print(f" - 当前页记录数: {len(rows)}") + + if rows: + print(f"\n示例数据 (第一条):") + first_row = rows[0] + for key, value in first_row.items(): + if key not in ["createTime", "updateTime"]: + print(f" {key}: {value}") + + print("\n" + "=" * 60) + print("✅ 修复验证成功!数据库字段映射问题已解决") + print("=" * 60) + return True + else: + print(f"❌ 接口返回错误: {result.get('msg')}") + print(f"错误详情: {result}") + return False + +if __name__ == "__main__": + try: + success = test_fix() + exit(0 if success else 1) + except Exception as e: + print(f"\n❌ 测试过程中发生异常: {str(e)}") + import traceback + traceback.print_exc() + exit(1)