# 员工亲属实体关联维护设计

## 1. 背景

当前信息维护中的员工实体关联维护模块，底层使用 `ccdi_staff_enterprise_relation` 表，页面、接口、导入能力均已完整存在，但现有语义以“员工本人”为主对象。

本次需求要求将该模块整体切换为“员工亲属与实体的关联关系维护”：

- 主对象从员工本人切换为员工亲属
- 员工亲属必须来源于员工亲属关系维护中已维护的亲属
- 新增和导入时仅允许使用员工亲属关系中状态为有效的亲属
- 列表需要展示亲属身份证、亲属名称、亲属关联的员工
- 现有导入能力同步切换为员工亲属实体关联导入
- 若亲属关系后续被改为无效，则该亲属名下已有实体关联自动更新为无效

本次设计按最短路径实施，不新增平行模块，不做兼容性方案，不额外扩展用户未提出的兜底逻辑。

## 2. 目标与范围

### 2.1 目标

在保留现有员工实体关联模块入口、表结构和基本交互骨架的前提下，将其业务语义改造为员工亲属实体关联维护，并保证新增、编辑、查询、详情、导入、失效联动链路闭环。

### 2.2 本次范围

- 改造员工实体关联模块的查询、详情、新增、编辑、导入语义
- 接入员工亲属关系表作为唯一合法亲属来源
- 页面支持按亲属身份证模糊搜索有效亲属并自动带出信息
- 列表和详情展示亲属名称及关联员工
- 员工亲属关系失效时，自动将对应实体关联更新为无效

### 2.3 非本次范围

- 不新增独立“员工亲属实体关联”新表
- 不迁移旧的员工本人实体关联历史数据
- 不设计旧数据兼容展示逻辑
- 不增加亲属关系失效后自动恢复实体关联为有效的反向联动
- 不扩展用户未提出的新查询条件、新统计能力或新菜单入口

## 3. 现状分析

### 3.1 现有实体关联模块

当前模块主要位置如下：

- 后端控制器：`ccdi-info-collection/.../controller/CcdiStaffEnterpriseRelationController.java`
- 后端服务：`ccdi-info-collection/.../service/impl/CcdiStaffEnterpriseRelationServiceImpl.java`
- 查询 Mapper：`ccdi-info-collection/.../mapper/info/collection/CcdiStaffEnterpriseRelationMapper.xml`
- 前端页面：`ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue`
- 前端 API：`ruoyi-ui/src/api/ccdiStaffEnterpriseRelation.js`

当前主表为 `ccdi_staff_enterprise_relation`，核心字段包括：

- `person_id`：当前语义为员工身份证号
- `social_credit_code`
- `enterprise_name`
- `relation_person_post`
- `status`
- `remark`
- `data_source`

当前列表通过 `ccdi_base_staff.id_card = person_id` 查询员工姓名。

### 3.2 现有员工亲属关系模块

员工亲属关系维护模块已完整存在，主要使用：

- 表：`ccdi_staff_fmy_relation`
- 员工身份证：`person_id`
- 亲属名称：`relation_name`
- 亲属身份证：`relation_cert_no`
- 员工亲属标记：`is_emp_family`
- 状态：`status`

该表已经能够稳定表达“某亲属属于哪个员工”，可以直接作为本次改造的唯一合法来源。

## 4. 方案对比

### 4.1 方案 A：沿用现有表并切换业务语义

做法：

- 继续使用 `ccdi_staff_enterprise_relation`
- 将 `person_id` 改为表示“亲属身份证号”
- 查询时通过亲属关系表回补亲属名称和关联员工

优点：

- 改动最小
- 现有增删改查和导入框架可复用
- 不需要新增表、菜单、权限和页面

缺点：

- `person_id` 字段命名不再直观表达“亲属”

### 4.2 方案 B：沿用现有表并增加冗余字段

做法：

- 在原表上补充亲属名称、关联员工身份证、关联员工姓名等冗余字段

优点：

- 查询 SQL 简单

缺点：

- 引入冗余数据
- 与员工亲属关系、员工基础信息容易产生不一致
- 超出最短路径原则

### 4.3 方案 C：新建员工亲属实体关联表

做法：

- 新建全新表结构与全新模块

优点：

- 语义最干净

缺点：

- 改造范围明显扩大
- 需要增加整套表、接口、页面和权限
- 不符合本次“最短路径实现”要求

### 4.4 选型结论

采用方案 A。

原因：

- 可以在现有模块上直接改造，成本最低
- 满足业务闭环，不需要额外兜底
- 与当前仓库已有的员工亲属关系维护能力天然衔接

## 5. 数据模型设计

## 5.1 主表沿用

继续使用 `ccdi_staff_enterprise_relation`，但字段语义调整如下：

| 字段 | 新语义 |
|------|--------|
| `person_id` | 亲属身份证号 |
| `social_credit_code` | 统一社会信用代码 |
| `enterprise_name` | 企业名称 |
| `relation_person_post` | 亲属在企业中的职务 |
| `status` | 关联关系状态 |
| `remark` | 补充说明 |
| `data_source` | 数据来源 |

本次不修改表结构，不新增字段。

## 5.2 查询回填链路

列表和详情展示使用以下关联链路：

1. 以 `ccdi_staff_enterprise_relation` 为主表
2. 通过 `ccdi_staff_fmy_relation.relation_cert_no = ccdi_staff_enterprise_relation.person_id`
3. 并限定 `ccdi_staff_fmy_relation.is_emp_family = 1`
4. 从 `ccdi_staff_fmy_relation` 获取：
   - `relation_name` 作为亲属名称
   - `person_id` 作为关联员工身份证
5. 再通过 `ccdi_base_staff.id_card = ccdi_staff_fmy_relation.person_id` 获取员工姓名

最终返回给前端的核心展示字段为：

- `personId`：亲属身份证
- `relationName`：亲属名称
- `staffPersonId`：关联员工身份证
- `staffPersonName`：关联员工姓名

现有 `personName` 字段不再承载员工姓名语义，避免前后端继续误用，改造后统一使用新的返回字段。

## 6. 业务规则

### 6.1 合法亲属来源

新增和导入时，亲属身份证号必须匹配到一条满足以下条件的员工亲属关系：

- `is_emp_family = 1`
- `status = 1`
- `relation_cert_no = person_id`

只要任一条件不满足，即判定该亲属不允许用于实体关联维护。

### 6.2 唯一性规则

业务唯一性继续沿用现有模式，但语义变更为：

- `亲属身份证号 + 统一社会信用代码` 唯一

新增与导入都必须校验该组合唯一，禁止重复录入。

### 6.3 编辑规则

编辑时延续现有“业务主键不可修改”的方式：

- 亲属身份证号不可修改
- 统一社会信用代码不可修改

只允许修改：

- 企业名称
- 关联人在企业的职务
- 状态
- 补充说明

### 6.4 亲属失效联动规则

当员工亲属关系模块中某条亲属关系状态从有效改为无效时：

- 自动将该亲属名下所有实体关联记录的 `status` 更新为 `0`

联动范围限定为：

- `ccdi_staff_enterprise_relation.person_id = ccdi_staff_fmy_relation.relation_cert_no`

本次仅实现“改为无效时自动同步为无效”的单向联动，不设计反向自动恢复为有效。

## 7. 后端设计

### 7.1 接口延用

继续沿用现有接口路径：

- `GET /ccdi/staffEnterpriseRelation/list`
- `GET /ccdi/staffEnterpriseRelation/{id}`
- `POST /ccdi/staffEnterpriseRelation`
- `PUT /ccdi/staffEnterpriseRelation`
- `DELETE /ccdi/staffEnterpriseRelation/{ids}`
- `POST /ccdi/staffEnterpriseRelation/importTemplate`
- `POST /ccdi/staffEnterpriseRelation/importData`
- `GET /ccdi/staffEnterpriseRelation/importStatus/{taskId}`
- `GET /ccdi/staffEnterpriseRelation/importFailures/{taskId}`

不新增平行接口组。

### 7.2 查询 DTO 与 VO

查询条件建议调整为围绕亲属语义：

- 亲属身份证号
- 亲属名称
- 关联员工
- 统一社会信用代码
- 企业名称
- 状态

VO 增加或切换为以下字段：

- `personId`
- `relationName`
- `staffPersonId`
- `staffPersonName`
- `relationPersonPost`
- `socialCreditCode`
- `enterpriseName`
- `status`
- `remark`
- `dataSource`
- `createTime`
- `updateTime`
- `createdBy`
- `updatedBy`

### 7.3 查询 SQL

列表和详情 SQL 改为从亲属关系表回补字段，示意逻辑如下：

```sql
SELECT
    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.created_by,
    ser.create_time,
    ser.updated_by,
    ser.update_time
FROM ccdi_staff_enterprise_relation ser
LEFT JOIN ccdi_staff_fmy_relation sfr
    ON sfr.relation_cert_no = ser.person_id
   AND sfr.is_emp_family = 1
LEFT JOIN ccdi_base_staff bs
    ON bs.id_card = sfr.person_id
```

其中：

- 列表查询不额外过滤 `sfr.status = 1`
- 原因是历史记录需要正常展示，即使亲属后续失效，记录仍可查到，但实体关联状态会被联动改为无效

### 7.4 新增与导入校验

新增和导入都统一改为校验有效亲属关系：

1. 根据亲属身份证号查询 `ccdi_staff_fmy_relation`
2. 要求命中 `is_emp_family = 1 and status = 1`
3. 未命中则报错
4. 命中后继续校验 `亲属身份证号 + 统一社会信用代码` 唯一

### 7.5 亲属下拉搜索接口

前端新增/编辑弹窗需要按身份证号模糊搜索有效亲属。

实现方式：

- 在员工实体关联模块后端新增一个用于下拉搜索的查询接口
- 返回有效亲属列表

返回项建议包含：

- `relationCertNo`
- `relationName`
- `personId`
- `personName`

查询条件：

- `relation_cert_no` 模糊匹配输入值
- `is_emp_family = 1`
- `status = 1`

### 7.6 亲属失效联动落点

联动逻辑放在员工亲属关系保存链路中处理，即：

- `CcdiStaffFmyRelationServiceImpl.updateRelation`

处理规则：

1. 查询更新前旧记录
2. 判断旧状态是否为有效、更新后状态是否为无效
3. 若是，则按当前亲属身份证号批量更新实体关联表状态为无效

该联动需和亲属关系更新处于同一事务内，保证状态一致。

## 8. 前端设计

### 8.1 页面延用

继续使用：

- `ruoyi-ui/src/views/ccdiStaffEnterpriseRelation/index.vue`

不新建新页面。

### 8.2 查询区

查询区改为亲属语义，保留或新增：

- 亲属身份证号
- 亲属名称
- 关联员工
- 统一社会信用代码
- 企业名称
- 状态

### 8.3 列表区

列表核心列调整为：

- 亲属身份证
- 亲属名称
- 关联员工
- 企业名称
- 关联人在企业的职务
- 状态
- 数据来源
- 创建时间
- 操作

其中“关联员工”展示建议为：

- `员工姓名（员工身份证号）`

这样可以避免用户只看到姓名无法定位员工。

### 8.4 新增弹窗

新增弹窗保留现有骨架，调整主录入方式：

- 身份证号输入改为“按亲属身份证模糊搜索”
- 下拉数据来源改为有效员工亲属

下拉项显示建议：

- 左侧：亲属身份证号
- 右侧：亲属名称 / 关联员工姓名

用户选中后，自动回填并只读展示：

- 亲属名称
- 关联员工

其余字段继续录入：

- 统一社会信用代码
- 企业名称
- 关联人在企业的职务
- 补充说明

新增时状态仍默认有效。

### 8.5 编辑弹窗

编辑弹窗延续现有模式：

- 亲属身份证号不可编辑
- 统一社会信用代码不可编辑
- 亲属名称和关联员工仅展示，不可改

### 8.6 详情弹窗

详情弹窗基础信息展示：

- 亲属身份证
- 亲属名称
- 关联员工
- 统一社会信用代码
- 企业名称
- 关联人在企业的职务
- 状态
- 数据来源
- 补充说明
- 审计信息

## 9. 导入设计

### 9.1 导入模板

继续沿用现有异步导入机制，但模板标题、文案和字段语义改为亲属口径。

模板标题改为：

- 员工亲属实体关联信息

模板中的“身份证号”字段实际表示：

- 亲属身份证号

### 9.2 导入校验规则

每行导入数据按以下顺序校验：

1. 基础字段非空与格式合法
2. 亲属身份证号必须存在于员工亲属关系中
3. 且该亲属必须为员工亲属、状态有效
4. `亲属身份证号 + 统一社会信用代码` 在库中不能重复
5. 导入文件内同组合不能重复

### 9.3 失败记录展示

失败记录弹窗字段调整为：

- 亲属身份证号
- 亲属名称
- 企业名称
- 统一社会信用代码
- 失败原因

失败原因示例：

- 亲属身份证号不存在于员工亲属关系中
- 亲属已失效，无法导入实体关联
- 亲属身份证号和统一社会信用代码组合已存在

### 9.4 导入结果提示

所有导入提示文案统一改为：

- 员工亲属实体关联导入

避免继续出现“员工实体关系”旧语义。

## 10. 错误处理

后端错误提示统一切换为亲属语义，示例：

- 所选身份证号不是有效的员工亲属，无法新增实体关联
- 亲属身份证号和统一社会信用代码组合已存在
- 亲属身份证号不存在于员工亲属关系中，或该亲属已无效
- 亲属身份证号不可修改

前端不新增特殊兜底逻辑，直接按现有错误提示机制回显后端返回信息。

## 11. 测试要点

### 11.1 列表与详情

- 列表正确展示亲属身份证、亲属名称、关联员工
- 详情页展示字段与列表语义一致

### 11.2 新增

- 输入亲属身份证可模糊搜索有效亲属
- 选中亲属后自动带出亲属名称和关联员工
- 无效亲属、非员工亲属、不存在的亲属身份证不能新增
- 重复的亲属身份证号 + 统一社会信用代码不能新增

### 11.3 编辑

- 亲属身份证号不可修改
- 统一社会信用代码不可修改
- 企业名称、职务、状态、备注可正常编辑

### 11.4 导入

- 有效亲属导入成功
- 亲属不存在导入失败
- 亲属无效导入失败
- 导入文件内重复失败
- 数据库内重复失败
- 失败记录文案与字段正确

### 11.5 失效联动

- 将员工亲属关系从有效改为无效后
- 该亲属名下已有实体关联自动更新为无效
- 列表仍能查到该记录，但状态为无效

## 12. 实施边界总结

本次改造严格限定为“在原员工实体关联模块上切换主对象为员工亲属”，核心边界如下：

- 不新建表
- 不新建页面
- 不迁移旧数据
- 不做兼容性兜底
- 不做反向恢复联动
- 所有合法性判断均以员工亲属关系表为准

在此边界内，前后端和导入链路均可形成完整闭环，并满足本次需求。
