Files
ccdi/docs/plans/backend/2026-04-26-enterprise-auto-fill-backend-implementation.md

494 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 关联业务自动补入实体库 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<EnterpriseFillItem> 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 "实现关联业务自动补入实体库"
```