# 关联业务自动补入实体库 Backend Implementation Plan > **执行约束:** 按当前项目 `AGENTS.md` 执行;未获得用户明确要求时不启用 subagent。Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 新建和导入员工亲属实体关联、中介实体关联、信贷客户实体关联、招投标供应商时,实体库缺失的企业自动写入 `ccdi_enterprise_base_info`。 **Architecture:** 新增一个后端内部实体库自动补全服务,统一处理“已存在不覆盖、缺失则最小插入、同批去重、来源和风险等级映射”。各业务 Service 在业务校验通过、业务数据落库前调用该能力;`EnterpriseSource` 枚举新增 `SUPPLIER` 并继续由现有 `/ccdi/enum/enterpriseSource` 接口驱动前端。 **Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, JUnit 5, Mockito, Maven. --- ## File Structure - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/EnterpriseSource.java` - 新增 `SUPPLIER("SUPPLIER", "供应商")`。 - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/EnterpriseAutoFillService.java` - 内部补全服务,封装单条和批量实体补入。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationServiceImpl.java` - 新建员工亲属实体关联前补实体库。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationImportServiceImpl.java` - 导入成功行批量补实体库。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCustEnterpriseRelationServiceImpl.java` - 新建信贷客户实体关联前补实体库。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCustEnterpriseRelationImportServiceImpl.java` - 导入成功行批量补实体库。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java` - 中介实体关联新建时取消实体库必须已存在校验,改为补实体库。 - 中介库管理新增实体时风险等级默认高风险。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.java` - 取消“机构表不存在”失败条件,改为成功行批量补实体库。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoImportServiceImpl.java` - 中介库管理导入实体风险等级默认高风险。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionServiceImpl.java` - 招投标新建时供应商补实体库。 - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionImportServiceImpl.java` - 招投标导入成功采购事项的供应商批量补实体库。 - Test: existing unit tests under `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/` - 扩展或新增对应 Service/Import/Controller 测试。 - Create: `docs/reports/implementation/2026-04-26-enterprise-auto-fill-implementation.md` - 记录修改内容、影响范围、验证情况。 ## Task 1: EnterpriseSource 枚举与接口契约 **Files:** - Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/EnterpriseSource.java` - Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiEnumControllerTest.java` - [ ] **Step 1: 写失败测试** 在 `CcdiEnumControllerTest#getEnterpriseSourceOptions_shouldReturnConfiguredOptions` 中断言返回值包含 `SUPPLIER/供应商`。 ```java assertTrue(data.stream() .map(EnumOptionVO.class::cast) .anyMatch(option -> "SUPPLIER".equals(option.getValue()) && "供应商".equals(option.getLabel()))); ``` - [ ] **Step 2: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest test ``` Expected: FAIL,提示未找到 `SUPPLIER`。 - [ ] **Step 3: 实现枚举** 在 `EnterpriseSource` 中新增: ```java SUPPLIER("SUPPLIER", "供应商"), ``` 保持 `contains`、`resolveCode`、`getDescByCode` 通过 `values()` 自动生效。 - [ ] **Step 4: 运行测试确认通过** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest test ``` Expected: PASS。 ## Task 2: 实体库自动补全服务 **Files:** - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/EnterpriseAutoFillService.java` - Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/support/EnterpriseAutoFillServiceTest.java` - [ ] **Step 1: 写服务测试** 覆盖以下行为: - 已存在实体不插入、不覆盖。 - 缺失实体插入最小记录。 - 中介来源写 `riskLevel=1`。 - 员工亲属、信贷客户、供应商来源写 `riskLevel=null`。 - 批量同一信用代码只插一次,并使用首次有效名称。 - 插入时遇到主键重复按已存在处理。 核心断言示例: ```java assertEquals("SUPPLIER", captured.getEntSource()); assertNull(captured.getRiskLevel()); assertEquals("IMPORT", captured.getDataSource()); ``` - [ ] **Step 2: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=EnterpriseAutoFillServiceTest test ``` Expected: FAIL,类不存在。 - [ ] **Step 3: 实现服务接口** 创建内部记录类型和方法: ```java @Service public class EnterpriseAutoFillService { @Resource private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper; public record EnterpriseFillItem( String socialCreditCode, String enterpriseName, String entSource, String dataSource, String userName ) {} @Transactional public void ensureExists(EnterpriseFillItem item) { ensureExistsBatch(List.of(item)); } @Transactional public void ensureExistsBatch(List items) { // trim、过滤空信用代码、按 socialCreditCode 首次出现去重 // selectBatchIds 查询已存在记录 // 组装 CcdiEnterpriseBaseInfo 最小实体 // riskLevel: INTERMEDIARY -> "1",其他 -> null // dataSource: MANUAL 或 IMPORT // 分批调用 enterpriseBaseInfoMapper.insertBatch // 捕获 DuplicateKeyException 后继续逐条 selectById/insert,重复则忽略 } } ``` 实现注意: - 不调用 `CcdiEnterpriseBaseInfoServiceImpl#insertEnterpriseBaseInfo`,避免复用手工新增风险等级校验。 - 对非中介来源显式 `setRiskLevel(null)`。 - 不更新已存在实体。 - `enterpriseName` 使用来源业务已通过校验的名称,不增加额外兜底。 - [ ] **Step 4: 运行服务测试** Run: ```bash mvn -pl ccdi-info-collection -Dtest=EnterpriseAutoFillServiceTest test ``` Expected: PASS。 ## Task 3: 员工亲属实体关联接入 **Files:** - Modify: `CcdiStaffEnterpriseRelationServiceImpl.java` - Modify: `CcdiStaffEnterpriseRelationImportServiceImpl.java` - Modify: `CcdiStaffEnterpriseRelationServiceImplTest.java` - Modify: `CcdiStaffEnterpriseRelationImportServiceImplTest.java` - [ ] **Step 1: 写新建测试** 在 `insertRelation_shouldAllowValidFamily` 中注入 `EnterpriseAutoFillService` mock,并验证: ```java verify(enterpriseAutoFillService).ensureExists(argThat(item -> "91310000123456789A".equals(item.socialCreditCode()) && "测试企业".equals(item.enterpriseName()) && "EMP_RELATION".equals(item.entSource()) && "MANUAL".equals(item.dataSource()))); ``` - [ ] **Step 2: 写导入测试** 扩展导入测试,验证成功行调用批量补入,失败行不进入补入集合。 - [ ] **Step 3: 运行员工亲属测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest test ``` Expected: FAIL,尚未调用自动补全服务。 - [ ] **Step 4: 实现新建接入** 在 `insertRelation` 中,`existsByPersonIdAndSocialCreditCode` 通过后、`relationMapper.insert` 前调用: ```java enterpriseAutoFillService.ensureExists(new EnterpriseAutoFillService.EnterpriseFillItem( addDTO.getSocialCreditCode(), addDTO.getEnterpriseName(), EnterpriseSource.EMP_RELATION.getCode(), DataSource.MANUAL.getCode(), SecurityUtils.getUsername() )); ``` - [ ] **Step 5: 实现导入接入** 在 `importRelationAsync` 成功构建 `newRecords` 后、`saveBatch(newRecords, 500)` 前,按成功记录组装补入集合: ```java enterpriseAutoFillService.ensureExistsBatch(newRecords.stream() .map(item -> new EnterpriseFillItem(item.getSocialCreditCode(), item.getEnterpriseName(), EnterpriseSource.EMP_RELATION.getCode(), DataSource.IMPORT.getCode(), userName)) .toList()); ``` - [ ] **Step 6: 运行员工亲属测试** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest test ``` Expected: PASS。 ## Task 4: 信贷客户实体关联接入 **Files:** - Modify: `CcdiCustEnterpriseRelationServiceImpl.java` - Modify: `CcdiCustEnterpriseRelationImportServiceImpl.java` - Test: add `CcdiCustEnterpriseRelationServiceImplTest.java` if missing - Test: add or extend `CcdiCustEnterpriseRelationImportServiceImplTest.java` - [ ] **Step 1: 写新建测试** 验证 `insertRelation` 成功时调用自动补全: ```java assertEquals("CREDIT_CUSTOMER", item.entSource()); assertEquals("MANUAL", item.dataSource()); ``` - [ ] **Step 2: 写导入测试** 准备一条成功、一条重复组合失败,验证只有成功行传入 `ensureExistsBatch`。 - [ ] **Step 3: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiCustEnterpriseRelationServiceImplTest,CcdiCustEnterpriseRelationImportServiceImplTest test ``` Expected: FAIL,尚未调用自动补全服务。 - [ ] **Step 4: 实现新建接入** 在 `insertRelation` 唯一性校验后、插入前调用自动补全,来源 `CREDIT_CUSTOMER`,数据来源 `MANUAL`。 - [ ] **Step 5: 实现导入接入** 在 `importRelationAsync` 成功记录批量插入前调用自动补全,来源 `CREDIT_CUSTOMER`,数据来源 `IMPORT`。 - [ ] **Step 6: 运行测试** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiCustEnterpriseRelationServiceImplTest,CcdiCustEnterpriseRelationImportServiceImplTest test ``` Expected: PASS。 ## Task 5: 中介实体关联和中介实体管理规则 **Files:** - Modify: `CcdiIntermediaryServiceImpl.java` - Modify: `CcdiIntermediaryEnterpriseRelationImportServiceImpl.java` - Modify: `CcdiEnterpriseBaseInfoImportServiceImpl.java` - Modify: `CcdiIntermediaryServiceImplTest.java` - Modify: `CcdiIntermediaryEnterpriseRelationImportServiceImplTest.java` - Modify: `CcdiEnterpriseBaseInfoImportServiceImplTest.java` - [ ] **Step 1: 写中介实体关联新建测试** 验证实体库缺失不再抛“关联机构不存在”,而是调用自动补全并插入关联: ```java when(enterpriseRelationMapper.existsByIntermediaryBizIdAndSocialCreditCode("owner-biz", uscc)).thenReturn(false); verify(enterpriseAutoFillService).ensureExists(argThat(item -> "INTERMEDIARY".equals(item.entSource()) && "MANUAL".equals(item.dataSource()))); verify(enterpriseRelationMapper).insert(any(CcdiIntermediaryEnterpriseRelation.class)); ``` - [ ] **Step 2: 写中介实体关联导入测试** 将现有 `importEnterpriseRelationAsync_shouldFailWhenEnterpriseDoesNotExist` 改成成功场景,断言: - 不再产生失败记录。 - 调用 `ensureExistsBatch`。 - 插入关联记录。 - [ ] **Step 3: 写中介库管理默认高风险测试** 在 `CcdiEnterpriseBaseInfoImportServiceImplTest` 增加: ```java excel.setRiskLevel(null); excel.setEntSource("中介"); CcdiEnterpriseBaseInfo entity = service.validateAndBuildEntity(excel, Set.of(), new HashSet<>(), "admin"); assertEquals("1", entity.getRiskLevel()); assertEquals("INTERMEDIARY", entity.getEntSource()); ``` 在 `CcdiIntermediaryServiceImplTest` 验证 `insertIntermediaryEntity` 未传风险等级时写入 `1`。 - [ ] **Step 4: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest,CcdiEnterpriseBaseInfoImportServiceImplTest test ``` Expected: FAIL。 - [ ] **Step 5: 实现新建接入** 修改 `validateEnterpriseRelation`:保留中介本人和重复组合校验,删除 `enterpriseBaseInfoMapper.selectById(socialCreditCode) == null` 抛错。 在 `insertIntermediaryEnterpriseRelation` 插入前调用自动补全,来源 `INTERMEDIARY`,数据来源 `MANUAL`。 - [ ] **Step 6: 实现导入接入** 在导入服务中删除 `getExistingEnterpriseCodes` 的失败判断。成功记录插入前按 Excel 行组装实体补入,来源 `INTERMEDIARY`,数据来源 `IMPORT`。 - [ ] **Step 7: 实现中介实体默认高风险** 在 `insertIntermediaryEntity` 中,如果 `riskLevel` 为空,设置为 `"1"`。 在 `CcdiEnterpriseBaseInfoImportServiceImpl#validateAndBuildEntity` 中,当解析出的 `entSource` 为 `INTERMEDIARY` 且 `riskLevel` 为空时,设置 `"1"`。 - [ ] **Step 8: 运行测试** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest,CcdiEnterpriseBaseInfoImportServiceImplTest test ``` Expected: PASS。 ## Task 6: 招投标供应商接入 **Files:** - Modify: `CcdiPurchaseTransactionServiceImpl.java` - Modify: `CcdiPurchaseTransactionImportServiceImpl.java` - Test: add `CcdiPurchaseTransactionServiceImplTest.java` if missing - Modify: `CcdiPurchaseTransactionFeatureContractTest.java` or add import service unit test - [ ] **Step 1: 写新建测试** 验证 `insertTransaction` 成功时,仅对 `supplierUscc` 不为空的供应商调用自动补全: ```java assertEquals("SUPPLIER", item.entSource()); assertEquals("MANUAL", item.dataSource()); assertEquals("供应商A", item.enterpriseName()); ``` - [ ] **Step 2: 写导入测试** 准备一个成功采购事项和一个失败采购事项,断言只有成功事项的供应商进入 `ensureExistsBatch`,且来源为 `SUPPLIER`、数据来源为 `IMPORT`。 - [ ] **Step 3: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiPurchaseTransactionServiceImplTest,CcdiPurchaseTransactionImportServiceImplTest,CcdiPurchaseTransactionFeatureContractTest test ``` Expected: FAIL。 - [ ] **Step 4: 实现新建接入** 在 `insertTransaction` 中,`buildSupplierEntities` 和校验完成后、写主从表前,收集供应商: ```java enterpriseAutoFillService.ensureExistsBatch(supplierList.stream() .filter(item -> StringUtils.isNotEmpty(item.getSupplierUscc())) .map(item -> new EnterpriseFillItem(item.getSupplierUscc(), item.getSupplierName(), EnterpriseSource.SUPPLIER.getCode(), DataSource.MANUAL.getCode(), SecurityUtils.getUsername())) .toList()); ``` - [ ] **Step 5: 实现导入接入** 在 `importTransactionAsync` 中,按成功构建的 `newSuppliers` 收集供应商实体,在 `saveBatch(newTransactions, 500)` 之前调用自动补全。失败事项的供应商不进入 `newSuppliers`,天然不补。 - [ ] **Step 6: 运行测试** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiPurchaseTransactionServiceImplTest,CcdiPurchaseTransactionImportServiceImplTest,CcdiPurchaseTransactionFeatureContractTest test ``` Expected: PASS。 ## Task 7: 集成验证与实施记录 **Files:** - Create: `docs/reports/implementation/2026-04-26-enterprise-auto-fill-implementation.md` - [ ] **Step 1: 运行后端相关测试集合** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest,EnterpriseAutoFillServiceTest,CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest,CcdiCustEnterpriseRelationServiceImplTest,CcdiCustEnterpriseRelationImportServiceImplTest,CcdiIntermediaryServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest,CcdiEnterpriseBaseInfoImportServiceImplTest,CcdiPurchaseTransactionServiceImplTest,CcdiPurchaseTransactionImportServiceImplTest,CcdiPurchaseTransactionFeatureContractTest test ``` Expected: BUILD SUCCESS。 - [ ] **Step 2: 如涉及数据库实测,确认 `risk_level` 落库值** 验证样本: - 员工亲属自动补入:`risk_level IS NULL` - 信贷客户自动补入:`risk_level IS NULL` - 招投标供应商自动补入:`risk_level IS NULL` - 中介自动补入:`risk_level = '1'` - [ ] **Step 3: 写实施记录** 实施记录至少包含: ```markdown # 关联业务自动补入实体库实施记录 ## 修改内容 - 新增实体库自动补全服务 - 接入员工亲属、中介、信贷客户、招投标链路 - 新增 SUPPLIER 企业来源 ## 影响范围 - ccdi-info-collection 后端服务 - 实体库管理企业来源枚举接口 ## 验证情况 - 列出 Maven 测试命令与结果 - 列出页面或数据库验证结果 ``` - [ ] **Step 4: 检查工作区** Run: ```bash git status --short ``` Expected: 仅包含本次功能相关源码、测试和实施记录,不包含 `.DS_Store` 或生成测试文件。 - [ ] **Step 5: 提交后端改动** ```bash git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection ccdi-info-collection/src/test/java/com/ruoyi/info/collection docs/reports/implementation/2026-04-26-enterprise-auto-fill-implementation.md git commit -m "实现关联业务自动补入实体库" ```