# 员工资产导入与实体库自动补入修复设计

## 1. 背景

本设计用于修复以下后端问题：

1. 【员工信息维护】双 Sheet 导入时，`员工资产信息` Sheet 只能通过数据库已有员工反查归属，不能识别同一个导入文件中刚成功导入的员工。
2. 【员工亲属关系维护】双 Sheet 导入时，`亲属资产信息` Sheet 只能通过数据库已有亲属关系反查归属，不能识别同一个导入文件中刚成功导入的亲属关系。
3. 关联业务自动补入实体库能力在当前主工作区未完整接回，员工亲属、信贷客户、中介、供应商四类业务成功关联后，缺失企业需要统一补入 `ccdi_enterprise_base_info`。

本次设计只涉及后端。不调整前端页面结构、上传入口、模板样式和前端轮询字段。

## 2. 目标

- 双 Sheet 导入时，主 Sheet 与资产 Sheet 在后端按业务依赖顺序执行。
- 资产 Sheet 可关联数据库已有主数据，也可关联同一文件中本轮主 Sheet 成功导入的数据。
- 主 Sheet 失败行不能作为资产归属依据。
- 继续返回当前前端已支持的两个任务 ID：
  - 员工信息维护：`staffTaskId`、`assetTaskId`
  - 员工亲属关系维护：`relationTaskId`、`assetTaskId`
- 恢复统一 `EnterpriseAutoFillService`，并接入员工亲属、信贷客户、中介、供应商四类业务。
- 实体库自动补入只插入缺失企业，不更新已存在实体。

## 3. 不在本次范围

- 不改前端上传入口、轮询逻辑、失败记录展示和模板下载样式。
- 不合并双 Sheet 导入任务 ID。
- 不改变员工、亲属关系、资产、实体关联现有字段校验规则。
- 不改变实体库手工新增、编辑、导入的既有业务规则。
- 不增加兜底来源、降级来源或兼容性分支。

## 4. 总体架构

本次后端设计分为两条链路。

### 4.1 双 Sheet 导入编排

`/ccdi/baseStaff/importData` 和 `/ccdi/staffFmyRelation/importData` 保持原接口、原模板、原返回字段。

Controller 继续负责读取两个 Sheet，但不再分别提交两个彼此独立的异步任务。服务层新增提交编排方法：

- 员工信息维护提交方法接收 `List<CcdiBaseStaffExcel>` 和 `List<CcdiBaseStaffAssetInfoExcel>`。
- 员工亲属关系维护提交方法接收 `List<CcdiStaffFmyRelationExcel>` 和 `List<CcdiAssetInfoExcel>`。

提交方法按实际存在的 Sheet 初始化对应任务状态，并启动一个后台编排任务。编排任务内部按顺序执行：

1. 主 Sheet 校验与插入。
2. 收集本轮主 Sheet 成功上下文。
3. 资产 Sheet 校验与插入。

### 4.2 实体库自动补入

新增或恢复统一内部服务：

`ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/EnterpriseAutoFillService.java`

该服务是关联业务补入实体库的唯一后端入口。四类业务在业务校验通过、业务记录落库前调用：

- 员工亲属实体关联：`EMP_RELATION`
- 信贷客户实体关联：`CREDIT_CUSTOMER`
- 中介实体关联：`INTERMEDIARY`
- 招投标供应商：`SUPPLIER`

实体库缺失时最小插入；已存在时不更新。

供应商来源需要同步新增后端枚举 `EnterpriseSource.SUPPLIER("SUPPLIER", "供应商")`，并补充 `/ccdi/enum/enterpriseSource` 枚举接口测试。前端继续通过现有枚举接口展示来源，不需要调整页面结构。

## 5. 员工信息维护双 Sheet 导入设计

### 5.1 当前问题

当前 `/ccdi/baseStaff/importData` 分别读取 `员工信息` 和 `员工资产信息`，然后分别调用：

- `baseStaffService.importBaseStaff(staffList)`
- `baseStaffAssetImportService.importAssetInfo(assetList)`

两个任务互相独立。员工资产导入只通过数据库 `ccdi_base_staff.id_card` 查找归属，无法稳定看到同一文件中刚导入成功的员工。

### 5.2 设计方案

新增员工信息维护双 Sheet 后端编排能力。

提交阶段：

1. Controller 读取两个 Sheet。
2. 若两个 Sheet 都无数据，仍返回“至少需要一条数据”。
3. 若存在员工 Sheet，生成并初始化 `staffTaskId`。
4. 若存在员工资产 Sheet，生成并初始化 `assetTaskId`。
5. 返回当前前端兼容的 `BaseStaffImportSubmitResultVO`。
6. 后台启动一个编排任务。

编排阶段：

1. 先执行员工主数据导入。
2. 沿用现有员工必填、身份证号、部门、员工 ID、身份证号重复校验。
3. 批量插入员工成功记录。
4. 收集本轮成功员工的 `idCard`。
5. 更新员工任务状态和失败记录。
6. 再执行员工资产导入。
7. 员工资产归属候选来源为：
   - 数据库已有 `ccdi_base_staff.id_card`
   - 本轮员工 Sheet 成功导入的 `idCard`
8. 员工资产落库继续保持：
   - `family_id = 员工身份证号`
   - `person_id = 员工身份证号`
9. 员工资产继续保持重复校验：`personId + assetMainType + assetSubType + assetName`。

### 5.3 任务状态

- 只填员工 Sheet：只返回 `staffTaskId`。
- 只填员工资产 Sheet：只返回 `assetTaskId`，只按数据库已有员工校验。
- 只填员工资产 Sheet 是正常导入场景，不因 `员工信息` Sheet 为空或未填写而拦截。
- 两个 Sheet 都填：返回 `staffTaskId` 和 `assetTaskId`，后台保证员工先处理、资产后处理。
- 员工主 Sheet 部分成功时，员工资产只能使用成功员工上下文。
- 员工主 Sheet 全部失败时，员工资产仍执行，但只能命中数据库已有员工。

## 6. 员工亲属关系维护双 Sheet 导入设计

### 6.1 当前问题

当前 `/ccdi/staffFmyRelation/importData` 分别读取 `员工亲属关系信息` 和 `亲属资产信息`，然后分别调用：

- `relationService.importRelation(relationList)`
- `assetInfoImportService.importAssetInfo(assetList)`

两个任务互相独立。亲属资产导入只通过数据库 `ccdi_staff_fmy_relation.relation_cert_no` 查找归属，无法稳定看到同一文件中刚导入成功的亲属关系。

### 6.2 设计方案

新增员工亲属关系维护双 Sheet 后端编排能力。

提交阶段：

1. Controller 读取两个 Sheet。
2. 若两个 Sheet 都无数据，仍返回“至少需要一条数据”。
3. 若存在亲属关系 Sheet，生成并初始化 `relationTaskId`。
4. 若存在亲属资产 Sheet，生成并初始化 `assetTaskId`。
5. 返回当前前端兼容的 `StaffFmyRelationImportSubmitResultVO`。
6. 后台启动一个编排任务。

编排阶段：

1. 先执行亲属关系导入。
2. 沿用现有亲属关系必填、员工身份证号存在性、关系人证件号、重复组合校验。
3. 批量插入亲属关系成功记录。
4. 收集本轮成功亲属关系映射：
   - `relationCertNo` 作为资产 Sheet 的 `personId`
   - `personId` 作为资产落库的 `familyId`
5. 更新亲属关系任务状态和失败记录。
6. 再执行亲属资产导入。
7. 亲属资产归属候选来源为：
   - 数据库已有员工亲属关系，沿用现有 owner 查询条件
   - 本轮亲属关系 Sheet 成功导入的员工亲属关系
8. 亲属资产落库继续保持：
   - `family_id = 员工身份证号`
   - `person_id = 亲属身份证号`

### 6.3 任务状态

- 只填亲属关系 Sheet：只返回 `relationTaskId`。
- 只填亲属资产 Sheet：只返回 `assetTaskId`，只按数据库已有员工亲属关系校验，并沿用现有 owner 查询条件。
- 只填亲属资产 Sheet 是正常导入场景，不因 `员工亲属关系信息` Sheet 为空或未填写而拦截。
- 两个 Sheet 都填：返回 `relationTaskId` 和 `assetTaskId`，后台保证亲属关系先处理、资产后处理。
- 亲属关系主 Sheet 部分成功时，亲属资产只能使用成功亲属关系上下文。
- 亲属关系主 Sheet 全部失败时，亲属资产仍执行，但只能命中数据库已有员工亲属关系。

## 7. 实体库自动补入设计

### 7.1 服务职责

`EnterpriseAutoFillService` 只负责一件事：对关联业务成功记录中的企业，确保实体库存在对应统一社会信用代码。

服务接口：

- `ensureExists(EnterpriseFillItem item)`
- `ensureExistsBatch(List<EnterpriseFillItem> items)`

`EnterpriseFillItem` 字段：

- `socialCreditCode`
- `enterpriseName`
- `entSource`
- `dataSource`
- `userName`

### 7.2 插入规则

1. 按 `socialCreditCode` 去重，同批次同一统一社会信用代码只处理一次。
2. 批量查询 `ccdi_enterprise_base_info` 已存在记录。
3. 已存在实体不更新。
4. 缺失实体最小插入：
   - `social_credit_code = 统一社会信用代码`
   - `enterprise_name = 企业名称；中介实体关联缺失实体时允许为 NULL`
   - `ent_source = 来源`
   - `data_source = MANUAL 或 IMPORT`
   - `risk_level = 来源规则值`
   - `created_by/updated_by = 当前用户`
5. 中介实体关联缺失实体时不要求提供机构名称，补入实体的 `enterprise_name` 可以为 `NULL`。
6. 中介来源 `INTERMEDIARY` 写 `risk_level = 1`。
7. 员工亲属、信贷客户、供应商来源写 `risk_level = NULL`。
8. 并发导致主键重复时，按“已存在实体”处理。
9. 其他数据库异常抛出，让当前业务事务失败。

### 7.3 四类业务接入点

员工亲属实体关联：

- 手工新增：校验有效亲属和组合不重复后、插入关联表前补入。
- 导入：只对校验成功并即将插入的记录批量补入。
- 来源：`EMP_RELATION`
- 数据来源：手工新增为 `MANUAL`，导入为 `IMPORT`
- 风险等级：`NULL`

信贷客户实体关联：

- 手工新增：校验通过后、插入关联表前补入。
- 导入：只对校验成功并即将插入的记录批量补入。
- 来源：`CREDIT_CUSTOMER`
- 数据来源：手工新增为 `MANUAL`，导入为 `IMPORT`
- 风险等级：`NULL`

中介实体关联：

- 手工新增：字段校验和重复关系校验通过后，取消或替换“实体库必须已存在”校验，先补入实体库，再插入关联关系。
- 导入：字段校验和重复关系校验通过后，取消或替换“实体库必须已存在”失败条件，只对即将插入的成功记录批量补入。
- 来源：`INTERMEDIARY`
- 数据来源：手工新增为 `MANUAL`，导入为 `IMPORT`
- 风险等级：`1`

招投标供应商：

- 手工新增或保存招投标主信息时，只对 `supplierUscc` 非空且通过现有格式校验的供应商补入。
- 导入：只对成功采购事项中 `supplierUscc` 非空且通过现有格式校验的供应商批量补入，失败采购事项不补入。
- 导入中 `supplierUscc` 为空的供应商保持现有保存规则，但不补入实体库。
- 来源：`SUPPLIER`
- 数据来源：手工新增为 `MANUAL`，导入为 `IMPORT`
- 风险等级：`NULL`

## 8. 错误处理与边界

### 8.1 双 Sheet 导入边界

- 主 Sheet 失败行不能进入资产归属候选。
- 主 Sheet 文件内重复行不能进入资产归属候选。
- 主 Sheet 数据库重复行不能进入资产归属候选。
- 主 Sheet 为空但资产 Sheet 有数据时，资产 Sheet 必须按现有独立资产导入规则正常执行。
- 资产 Sheet 仍按自身失败记录任务记录 `sheetName`、`rowNum`、`errorMessage`。
- 员工资产找不到员工时继续报“员工资产导入仅支持员工本人证件号”。
- 亲属资产找不到归属时继续报“未找到亲属资产归属员工”。
- 亲属资产命中多个归属时继续报“亲属资产归属员工不唯一”。
- 亲属资产数据库归属查询条件沿用当前实现，不因本次编排新增额外状态过滤。

### 8.2 自动补入边界

- 只处理业务校验成功、即将落库的记录。
- 失败业务行不能产生实体库记录。
- 已存在实体不更新，避免覆盖人工维护数据。
- 不增加默认企业名称、默认来源、默认风险等级等兜底逻辑。
- 不改变实体库导入的严格新增规则。
- 中介实体关联的“实体库必须已存在”校验需要被自动补入替换，否则缺失实体无法进入补入链路。
- 中介实体关联不新增机构名称入参；缺失实体补入时允许 `enterprise_name = NULL`。
- 供应商自动补入只处理非空统一社会信用代码，空统一社会信用代码不改变原导入保存规则。

## 9. 测试设计

### 9.1 单元测试

员工信息维护双 Sheet：

- 同一模板中新员工和员工资产同时导入，资产引用新员工身份证号，资产成功。
- 员工 Sheet 行校验失败，资产引用该身份证号且数据库不存在，资产失败。
- 员工信息 Sheet 为空或未填写，员工资产 Sheet 引用数据库已有员工时，资产成功导入。
- 只导员工资产，数据库已有员工时成功。
- 只导员工资产，数据库无员工时失败。
- 资产重复命中数据库或当前文件重复时失败。

员工亲属关系维护双 Sheet：

- 同一模板中新亲属关系和亲属资产同时导入，资产引用新亲属证件号，资产成功。
- 亲属关系 Sheet 行校验失败，资产引用该亲属证件号且数据库不存在，资产失败。
- 员工亲属关系信息 Sheet 为空或未填写，亲属资产 Sheet 引用数据库已有员工亲属关系时，资产成功导入。
- 只导亲属资产，数据库已有唯一员工亲属关系时成功。
- 只导亲属资产，数据库不存在亲属关系时失败。
- 同一亲属证件号命中多个员工归属时失败。

实体库自动补入：

- 已存在实体不插入、不更新。
- 缺失实体插入最小记录。
- 同批多个成功行引用同一统一社会信用代码，只补入一次。
- 中介来源写 `riskLevel=1`。
- 员工亲属、信贷客户、供应商来源写 `riskLevel=null`。
- 后端枚举接口返回 `SUPPLIER/供应商`。
- 并发重复主键按已存在处理。

四类业务接入：

- 手工新增成功时调用自动补入。
- 导入成功行进入自动补入集合。
- 导入失败行不进入自动补入集合。

### 9.2 接口验证

1. 下载真实 `/ccdi/baseStaff/importTemplate` 模板，构造 `员工信息` + `员工资产信息` 同文件导入，上传 `/ccdi/baseStaff/importData`，轮询两个任务并查询员工、资产落库。
2. 下载真实 `/ccdi/staffFmyRelation/importTemplate` 模板，构造 `员工亲属关系信息` + `亲属资产信息` 同文件导入，上传 `/ccdi/staffFmyRelation/importData`，轮询两个任务并查询亲属关系、资产落库。
3. 分别调用员工亲属、信贷客户、中介、供应商新增或导入接口，验证实体库自动补入来源、数据来源和风险等级。
4. 涉及中文清理 SQL 或验证 SQL 时使用 `bin/mysql_utf8_exec.sh`。

### 9.3 页面验证

完成后必须进入真实业务页面验证，不打开 prototype：

1. 【员工信息维护】下载模板，填写员工和员工资产，上传后检查任务状态、失败记录、列表和详情资产。
2. 【员工亲属关系维护】下载模板，填写亲属关系和亲属资产，上传后检查任务状态、失败记录、详情资产。
3. 四类实体自动补入完成后，进入【实体库管理】按统一社会信用代码查询。
4. 测试结束后清理本轮新增员工、亲属关系、资产、实体关联和自动补入实体库数据。
5. 测试结束后关闭测试过程中启动的前后端进程。

## 10. 实施顺序

1. 提炼员工主数据导入执行方法，返回成功员工上下文和失败记录。
2. 提炼员工资产导入执行方法，支持额外员工归属上下文。
3. 新增员工信息维护双 Sheet 编排方法并接入 Controller。
4. 提炼亲属关系导入执行方法，返回成功亲属关系上下文和失败记录。
5. 提炼亲属资产导入执行方法，支持额外亲属关系归属上下文。
6. 新增员工亲属关系维护双 Sheet 编排方法并接入 Controller。
7. 恢复或新增 `EnterpriseAutoFillService`。
8. 接入员工亲属、信贷客户、中介、供应商四类自动补入。
9. 补充单元测试。
10. 执行接口验证和真实页面验证。
11. 新增实施记录到 `docs/reports/implementation/`。

## 11. 风险与控制

- 不能继续由 Controller 分别提交两个独立异步任务，否则资产任务执行顺序仍不确定。
- 不能把主 Sheet 失败行放入资产归属上下文，否则会产生脏资产。
- 不能更新已存在实体库记录，否则会覆盖人工维护的企业名称、风险等级和来源。
- 自动补入必须在业务记录落库前执行，并与业务事务保持一致。
- 四类自动补入必须只处理成功业务行，失败行不能污染实体库。
