56 KiB
Staff Asset Import And Enterprise Auto-Fill Fix Implementation Plan
For implementers: Follow this repository's
AGENTS.md. Do not enableusing-superpowersor subagents unless the user explicitly requests them. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Fix employee/family dual-Sheet asset imports so asset-only imports still work and same-file main rows can be used as owner context, then restore unified enterprise auto-fill for staff-family, credit-customer, intermediary, and supplier flows.
Architecture: Keep the existing API paths, Excel templates, and two-task-ID response shape. Add internal execution-result/context methods to existing import services, then add backend orchestration methods that initialize the same task IDs but run main Sheet before asset Sheet when both contain data. Add one EnterpriseAutoFillService as the only internal missing-entity insertion service and call it from the four successful business write paths before relation/supplier data is inserted.
Tech Stack: Java 21, Spring Boot 3, MyBatis Plus, RedisTemplate, EasyExcel, JUnit 5, Mockito, Maven.
Scope And Source Of Truth
- Approved design:
docs/design/2026-05-06-staff-asset-import-and-enterprise-autofill-fix-design.md - Existing draft solution is stale in several places:
docs/plans/backend/2026-05-06-staff-asset-import-and-enterprise-autofill-fix-backend-solution.md - Use this implementation plan and the approved design as the current source of truth.
- Do not change frontend files for this implementation.
- Do not stage or commit unrelated dirty workspace files.
- Files under
ccdi-info-collection/src/test/are local verification files in this repository because*/src/test/is ignored. Create or modify them while implementing TDD, but do not include them in commits unless the user explicitly asks to force-add tests withgit add -f. - After backend implementation, add a separate implementation record under
docs/reports/implementation/.
File Structure
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/EnterpriseSource.java- Add
SUPPLIER("SUPPLIER", "供应商").
- Add
- Create:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/EnterpriseAutoFillService.java- Internal missing-entity insert service.
- Create:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/support/EnterpriseAutoFillServiceTest.java- Unit tests for auto-fill insert, dedupe, risk rules, and duplicate-key behavior.
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffService.java- Add dual-Sheet submit method returning
BaseStaffImportSubmitResultVO.
- Add dual-Sheet submit method returning
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffServiceImpl.java- Initialize staff/asset task IDs and call orchestrated backend import.
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffImportServiceImpl.java- Extract reusable synchronous execution that returns success id cards and failure rows.
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java- Extract reusable synchronous execution that accepts extra owner mappings.
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffController.java- Replace two independent submissions with the service dual-Sheet submit method.
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffFmyRelationService.java- Add dual-Sheet submit method returning
StaffFmyRelationImportSubmitResultVO.
- Add dual-Sheet submit method returning
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java- Initialize relation/asset task IDs and call orchestrated backend import.
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationImportServiceImpl.java- Extract reusable synchronous execution that returns successful relation owner mappings.
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java- Extract reusable synchronous execution that accepts extra owner mappings and preserves existing owner query conditions.
- Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffFmyRelationController.java- Replace two independent submissions with the service dual-Sheet submit method.
- Modify tests:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiEnumControllerTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffDualImportServiceTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffFmyRelationImportServiceImplTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationServiceImplTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationImportServiceImplTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryServiceImplTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryEnterpriseRelationImportServiceImplTest.javaccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiPurchaseTransactionFeatureContractTest.java
- Modify relation/supplier services:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationServiceImpl.javaccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationImportServiceImpl.javaccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCustEnterpriseRelationServiceImpl.javaccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCustEnterpriseRelationImportServiceImpl.javaccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.javaccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.javaccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionServiceImpl.javaccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionImportServiceImpl.java
- Create:
docs/reports/implementation/2026-05-06-staff-asset-import-and-enterprise-autofill-fix-backend-implementation.md- Record final changed scope and verification.
Task 1: EnterpriseSource And Auto-Fill Service
Files:
-
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/EnterpriseSource.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiEnumControllerTest.java -
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: Add failing enum test
In CcdiEnumControllerTest#getEnterpriseSourceOptions_shouldReturnConfiguredOptions, add:
assertTrue(data.stream()
.map(EnumOptionVO.class::cast)
.anyMatch(option ->
"SUPPLIER".equals(option.getValue())
&& "供应商".equals(option.getLabel())));
Also add import static org.junit.jupiter.api.Assertions.assertTrue;.
- Step 2: Run enum test and verify it fails
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest test
Expected: FAIL because SUPPLIER/供应商 is not present.
- Step 3: Add SUPPLIER enum
Modify EnterpriseSource:
SUPPLIER("SUPPLIER", "供应商"),
Place it after INTERMEDIARY(...) and before BOTH(...).
- Step 4: Run enum test and verify it passes
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest test
Expected: PASS.
- Step 5: Write failing auto-fill service tests
Create EnterpriseAutoFillServiceTest with Mockito. Cover:
@Test
void ensureExistsBatch_shouldInsertMissingEnterpriseWithSourceAndNullRiskForSupplier() {
when(mapper.selectBatchIds(List.of("91330100MA27X12345"))).thenReturn(List.of());
service.ensureExistsBatch(List.of(new EnterpriseAutoFillService.EnterpriseFillItem(
"91330100MA27X12345",
"供应商A",
EnterpriseSource.SUPPLIER.getCode(),
DataSource.IMPORT.getCode(),
"tester"
)));
ArgumentCaptor<List<CcdiEnterpriseBaseInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(mapper).insertBatch(captor.capture());
CcdiEnterpriseBaseInfo entity = captor.getValue().get(0);
assertEquals("91330100MA27X12345", entity.getSocialCreditCode());
assertEquals("供应商A", entity.getEnterpriseName());
assertEquals("SUPPLIER", entity.getEntSource());
assertEquals("IMPORT", entity.getDataSource());
assertNull(entity.getRiskLevel());
assertEquals("tester", entity.getCreatedBy());
assertEquals("tester", entity.getUpdatedBy());
}
Add tests for:
-
Existing entity returned by
selectBatchIdsis not inserted. -
Duplicate items in same batch insert once.
-
INTERMEDIARYsetsriskLevelto"1"and still inserts whenenterpriseNameisnull. -
EMP_RELATIONandCREDIT_CUSTOMERkeepriskLevelnull. -
DuplicateKeyExceptionfrom batch insert falls back to per-rowselectByIdand treats existing row as success. -
Step 6: Run auto-fill tests and verify they fail
Run:
mvn -pl ccdi-info-collection -Dtest=EnterpriseAutoFillServiceTest test
Expected: FAIL because EnterpriseAutoFillService does not exist.
- Step 7: Implement EnterpriseAutoFillService
Create EnterpriseAutoFillService:
package com.ruoyi.info.collection.service.support;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
import com.ruoyi.info.collection.enums.EnterpriseSource;
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
import jakarta.annotation.Resource;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@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) {
Map<String, EnterpriseFillItem> deduped = dedupe(items);
if (deduped.isEmpty()) {
return;
}
List<CcdiEnterpriseBaseInfo> existing = enterpriseBaseInfoMapper.selectBatchIds(new ArrayList<>(deduped.keySet()));
Set<String> existingCodes = existing.stream()
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
.collect(Collectors.toSet());
List<CcdiEnterpriseBaseInfo> missing = deduped.entrySet().stream()
.filter(entry -> !existingCodes.contains(entry.getKey()))
.map(entry -> buildEntity(entry.getValue()))
.toList();
if (missing.isEmpty()) {
return;
}
try {
enterpriseBaseInfoMapper.insertBatch(missing);
} catch (DuplicateKeyException e) {
insertOneByOne(missing);
}
}
private Map<String, EnterpriseFillItem> dedupe(List<EnterpriseFillItem> items) {
Map<String, EnterpriseFillItem> result = new LinkedHashMap<>();
if (items == null) {
return result;
}
for (EnterpriseFillItem item : items) {
if (item == null) {
continue;
}
String code = StringUtils.trim(item.socialCreditCode());
if (StringUtils.isEmpty(code)) {
continue;
}
result.putIfAbsent(code, new EnterpriseFillItem(
code,
StringUtils.trim(item.enterpriseName()),
item.entSource(),
item.dataSource(),
item.userName()
));
}
return result;
}
private CcdiEnterpriseBaseInfo buildEntity(EnterpriseFillItem item) {
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
entity.setSocialCreditCode(item.socialCreditCode());
entity.setEnterpriseName(item.enterpriseName());
entity.setEntSource(item.entSource());
entity.setDataSource(item.dataSource());
entity.setRiskLevel(resolveRiskLevel(item.entSource()));
entity.setCreatedBy(item.userName());
entity.setUpdatedBy(item.userName());
return entity;
}
private String resolveRiskLevel(String entSource) {
return EnterpriseSource.INTERMEDIARY.getCode().equals(entSource) ? "1" : null;
}
private void insertOneByOne(List<CcdiEnterpriseBaseInfo> missing) {
for (CcdiEnterpriseBaseInfo entity : missing) {
if (enterpriseBaseInfoMapper.selectById(entity.getSocialCreditCode()) != null) {
continue;
}
try {
enterpriseBaseInfoMapper.insert(entity);
} catch (DuplicateKeyException ignored) {
// Concurrent insert won the race; treat as already existing.
}
}
}
}
- Step 8: Run focused tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest,EnterpriseAutoFillServiceTest test
Expected: PASS.
- Step 9: Commit
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/enums/EnterpriseSource.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/EnterpriseAutoFillService.java
git commit -m "新增实体库自动补入服务"
Task 2: Employee Dual-Sheet Import Execution Context
Files:
-
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffImportServiceImpl.java -
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffDualImportServiceTest.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java -
Step 1: Write failing staff execution-result test
In CcdiBaseStaffDualImportServiceTest, add a test for a new method named executeBaseStaffImport:
@Test
void executeBaseStaffImport_shouldReturnSuccessIdCardsAndIgnoreFailureRowsForAssetContext() {
CcdiBaseStaffExcel valid = buildExcel(1001L, 10L, "11010519491231002X");
CcdiBaseStaffExcel invalid = buildExcel(1002L, 99L, "320101199001010014");
when(baseStaffMapper.selectBatchIds(List.of(1001L, 1002L))).thenReturn(List.of());
when(baseStaffMapper.selectList(any())).thenReturn(List.of());
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
when(deptMapper.selectDeptById(99L)).thenReturn(null);
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
var result = service.executeBaseStaffImport(List.of(valid, invalid), "task-context", "tester");
assertEquals(Set.of("11010519491231002X"), result.successIdCards());
assertEquals(1, result.failures().size());
verify(baseStaffMapper).insertBatch(any());
}
- Step 2: Run test and verify it fails
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffDualImportServiceTest#executeBaseStaffImport_shouldReturnSuccessIdCardsAndIgnoreFailureRowsForAssetContext test
Expected: FAIL because executeBaseStaffImport does not exist.
- Step 3: Extract staff execution method
In CcdiBaseStaffImportServiceImpl:
- Add a public record:
public record BaseStaffImportExecutionResult(
List<CcdiBaseStaff> successRecords,
List<ImportFailureVO> failures,
Set<String> successIdCards
) {
}
- Add:
public BaseStaffImportExecutionResult executeBaseStaffImport(List<CcdiBaseStaffExcel> excelList, String taskId, String userName) {
// Move validation, failure collection, insertBatch, failure Redis write,
// status update, and logging from importBaseStaffAsync here.
// Build successIdCards only from records successfully added to newRecords.
}
- Make
importBaseStaffAsyncdelegate to:
executeBaseStaffImport(excelList, taskId, "系统");
Do not put failed rows, duplicate rows, or rows rejected by dept/id-card validation into successIdCards.
- Step 4: Run staff tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffDualImportServiceTest,CcdiBaseStaffImportServiceImplTest test
Expected: PASS.
- Step 5: Write failing employee asset extra-owner test
In CcdiBaseStaffAssetImportServiceImplTest, add:
@Test
void executeAssetImport_shouldImportWhenOwnerOnlyExistsInCurrentStaffContext() {
CcdiBaseStaffAssetInfoExcel excel = buildExcel("11010519491231002X", "房产");
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(assetInfoMapper.selectOwnerCandidatesByBaseStaffIdCards(List.of("11010519491231002X"))).thenReturn(List.of());
service.executeAssetImport(
List.of(excel),
"task-current-owner",
"tester",
Map.of("11010519491231002X", Set.of("11010519491231002X"))
);
ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(assetInfoMapper).insertBatch(captor.capture());
assertEquals("11010519491231002X", captor.getValue().get(0).getFamilyId());
}
- Step 6: Run test and verify it fails
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffAssetImportServiceImplTest#executeAssetImport_shouldImportWhenOwnerOnlyExistsInCurrentStaffContext test
Expected: FAIL because executeAssetImport does not exist.
- Step 7: Extract employee asset execution method
In CcdiBaseStaffAssetImportServiceImpl:
- Add:
public void executeAssetImport(
List<CcdiBaseStaffAssetInfoExcel> excelList,
String taskId,
String userName,
Map<String, Set<String>> extraOwnerMap
) {
// Move logic from importAssetInfoAsync here.
}
- Merge existing database owners with extra owner mappings:
Map<String, Set<String>> ownerMap = buildOwnerMap(personIds);
mergeOwnerMappings(ownerMap, extraOwnerMap);
- Keep
importAssetInfoAsyncdelegating to:
executeAssetImport(excelList, taskId, userName, Map.of());
-
Do not require staff Sheet data; when
extraOwnerMapis empty, behavior must match the current independent asset import. -
Step 8: Run employee import service tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffDualImportServiceTest,CcdiBaseStaffAssetImportServiceImplTest test
Expected: PASS.
- Step 9: Commit
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffImportServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java
git commit -m "重构员工导入执行上下文"
Task 3: Employee Dual-Sheet Orchestration
Files:
-
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffService.java -
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffServiceImpl.java -
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffController.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffControllerTest.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceImplTest.java -
Step 1: Write failing controller delegation test
In CcdiBaseStaffControllerTest, verify /importData delegates to baseStaffService.importBaseStaffWithAssets(staffList, assetList) and no longer calls asset service independently. Use mocked EasyExcelUtil only if current test pattern already does; otherwise add service-level tests first and keep controller assertion to response field shape.
Expected result data for asset-only file:
BaseStaffImportSubmitResultVO result = new BaseStaffImportSubmitResultVO();
result.setAssetTaskId("asset-task");
result.setMessage("已提交员工资产信息导入任务");
- Step 2: Write failing service tests for task IDs
In CcdiBaseStaffServiceImplTest, add tests:
importBaseStaffWithAssets_shouldReturnOnlyAssetTaskIdWhenStaffRowsEmptyimportBaseStaffWithAssets_shouldReturnBothTaskIdsWhenBothSheetsHaveRows
Use mocks for RedisTemplate.opsForHash() and verify no staffTaskId is created for an empty staff list.
- Step 3: Run tests and verify failure
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffServiceImplTest,CcdiBaseStaffControllerTest test
Expected: FAIL because the service method does not exist and controller still submits independently.
- Step 4: Add service method contract
In ICcdiBaseStaffService add:
BaseStaffImportSubmitResultVO importBaseStaffWithAssets(
List<CcdiBaseStaffExcel> staffList,
List<CcdiBaseStaffAssetInfoExcel> assetList
);
Add imports for CcdiBaseStaffAssetInfoExcel and BaseStaffImportSubmitResultVO.
- Step 5: Implement task initialization and orchestration
In CcdiBaseStaffServiceImpl:
- Inject:
@Resource
private CcdiBaseStaffImportServiceImpl baseStaffImportExecutionService;
@Resource
private CcdiBaseStaffAssetImportServiceImpl baseStaffAssetImportExecutionService;
@Async
public void importBaseStaffWithAssetsAsync(...) { ... }
If adding @Async on CcdiBaseStaffServiceImpl risks self-invocation, create a small new support service instead:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/BaseStaffDualSheetImportOrchestrator.java
Use that support service from CcdiBaseStaffServiceImpl. The support service should:
- Run
executeBaseStaffImportonly whenstaffListhas rows. - Convert
successIdCardstoMap<String, Set<String>>. - Run
executeAssetImportonly whenassetListhas rows. - Pass an empty extra owner map when staff rows are empty.
Task initialization rules:
-
Asset-only import creates only
assetTaskId. -
Staff-only import creates only
staffTaskId. -
Both create both IDs.
-
If both lists are empty, throw
RuntimeException("至少需要一条数据"). -
Step 6: Update controller
In CcdiBaseStaffController#importData, replace:
BaseStaffImportSubmitResultVO result = new BaseStaffImportSubmitResultVO();
if (hasStaffRows) {
result.setStaffTaskId(baseStaffService.importBaseStaff(staffList));
}
if (hasAssetRows) {
result.setAssetTaskId(baseStaffAssetImportService.importAssetInfo(assetList));
}
with:
BaseStaffImportSubmitResultVO result = baseStaffService.importBaseStaffWithAssets(staffList, assetList);
Keep existing response text:
return AjaxResult.success("导入任务已提交,正在后台处理", result);
- Step 7: Run employee dual-Sheet tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffServiceImplTest,CcdiBaseStaffControllerTest,CcdiBaseStaffDualImportServiceTest,CcdiBaseStaffAssetImportServiceImplTest test
Expected: PASS.
- Step 8: Commit
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffService.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffController.java
git commit -m "修复员工双Sheet导入编排"
If you created BaseStaffDualSheetImportOrchestrator.java, include it in the commit.
Task 4: Family Relation Dual-Sheet Import Execution Context
Files:
-
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationImportServiceImpl.java -
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffFmyRelationImportServiceImplTest.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java -
Step 1: Write failing family relation execution-result test
In CcdiStaffFmyRelationImportServiceImplTest, add a Mockito-based test or split into a new Mockito extension test. Assert a new method executeRelationImport returns only successful relation owner mappings:
var result = service.executeRelationImport(List.of(validRelation, invalidRelation), "relation-task", "tester");
assertEquals(Set.of("320101199001010011"), result.ownerMap().get("320101199201010022"));
assertEquals(1, result.failures().size());
The key is relationCertNo, and the owner value is the employee personId.
- Step 2: Run family relation test and verify failure
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiStaffFmyRelationImportServiceImplTest test
Expected: FAIL because executeRelationImport does not exist.
- Step 3: Extract relation execution method
In CcdiStaffFmyRelationImportServiceImpl:
- Add record:
public record StaffFmyRelationImportExecutionResult(
List<CcdiStaffFmyRelation> successRecords,
List<StaffFmyRelationImportFailureVO> failures,
Map<String, Set<String>> ownerMap
) {
}
- Add:
public StaffFmyRelationImportExecutionResult executeRelationImport(
List<CcdiStaffFmyRelationExcel> excelList,
String taskId,
String userName
) {
// Move current importRelationAsync body here.
// ownerMap key = relation.getRelationCertNo()
// ownerMap value includes relation.getPersonId()
}
-
Make
importRelationAsyncdelegate toexecuteRelationImport. -
Step 4: Write failing family asset extra-owner test
In CcdiAssetInfoImportServiceImplTest, add:
@Test
void executeAssetImport_shouldImportWhenOwnerOnlyExistsInCurrentRelationContext() {
CcdiAssetInfoExcel excel = buildExcel("320101199201010022", "房产");
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199201010022"))).thenReturn(List.of());
service.executeAssetImport(
List.of(excel),
"asset-task",
"tester",
Map.of("320101199201010022", Set.of("320101199001010011"))
);
ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(assetInfoMapper).insertBatch(captor.capture());
assertEquals("320101199001010011", captor.getValue().get(0).getFamilyId());
assertEquals("320101199201010022", captor.getValue().get(0).getPersonId());
}
- Step 5: Run family asset test and verify failure
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiAssetInfoImportServiceImplTest#executeAssetImport_shouldImportWhenOwnerOnlyExistsInCurrentRelationContext test
Expected: FAIL because executeAssetImport overload does not exist.
- Step 6: Extract family asset execution method
In CcdiAssetInfoImportServiceImpl:
- Add:
public void executeAssetImport(
List<CcdiAssetInfoExcel> excelList,
String taskId,
String userName,
Map<String, Set<String>> extraOwnerMap
) {
// Move current importAssetInfoAsync body here.
}
-
Merge
extraOwnerMapinto the existing owner map after callingselectOwnerCandidatesByRelationCertNos. -
Do not add
status = 1filtering to the database owner query. -
Keep owner ambiguity behavior: if merged owner set has more than one family id, fail with
亲属资产归属员工不唯一. -
Make
importAssetInfoAsyncdelegate withMap.of(). -
Step 7: Run family import tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiStaffFmyRelationImportServiceImplTest,CcdiAssetInfoImportServiceImplTest test
Expected: PASS.
- Step 8: Commit
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationImportServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java
git commit -m "重构亲属关系资产导入执行上下文"
Task 5: Family Relation Dual-Sheet Orchestration
Files:
-
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffFmyRelationService.java -
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java -
Modify:
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffFmyRelationController.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiStaffFmyRelationControllerTest.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffFmyRelationServiceImplTest.java -
Step 1: Write failing service tests
In CcdiStaffFmyRelationServiceImplTest, add:
importRelationWithAssets_shouldReturnOnlyAssetTaskIdWhenRelationRowsEmptyimportRelationWithAssets_shouldReturnBothTaskIdsWhenBothSheetsHaveRows
Assert relation-only, asset-only, and both-Sheet task ID behavior. In the asset-only test, assert no relation task is initialized.
- Step 2: Write failing controller delegation test
In CcdiStaffFmyRelationControllerTest, verify importData delegates to relationService.importRelationWithAssets(relationList, assetList) and returns StaffFmyRelationImportSubmitResultVO.
- Step 3: Run tests and verify failure
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiStaffFmyRelationServiceImplTest,CcdiStaffFmyRelationControllerTest test
Expected: FAIL because the service method does not exist and controller still submits independently.
- Step 4: Add service method contract
In ICcdiStaffFmyRelationService add:
StaffFmyRelationImportSubmitResultVO importRelationWithAssets(
List<CcdiStaffFmyRelationExcel> relationList,
List<CcdiAssetInfoExcel> assetList
);
- Step 5: Implement task initialization and orchestration
In CcdiStaffFmyRelationServiceImpl, either:
- Add a small support orchestrator
StaffFmyRelationDualSheetImportOrchestrator, or - Inject the execution services and use an async proxy-safe pattern.
The orchestrator must:
- Run relation import only when relation rows exist.
- Build extra owner map from successful relation rows.
- Run asset import only when asset rows exist.
- Pass empty extra owner map when relation rows are empty.
- Keep asset-only import as a normal path.
- Step 6: Update controller
In CcdiStaffFmyRelationController#importData, replace independent calls:
result.setRelationTaskId(relationService.importRelation(relationList));
result.setAssetTaskId(assetInfoImportService.importAssetInfo(assetList));
with:
StaffFmyRelationImportSubmitResultVO result = relationService.importRelationWithAssets(relationList, assetList);
- Step 7: Run family dual-Sheet tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiStaffFmyRelationServiceImplTest,CcdiStaffFmyRelationControllerTest,CcdiStaffFmyRelationImportServiceImplTest,CcdiAssetInfoImportServiceImplTest test
Expected: PASS.
- Step 8: Commit
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiStaffFmyRelationService.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiStaffFmyRelationController.java
git commit -m "修复员工亲属双Sheet导入编排"
If you created StaffFmyRelationDualSheetImportOrchestrator.java, include it in the commit.
Task 6: Staff And Credit Enterprise Relation Auto-Fill
Files:
-
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 -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationServiceImplTest.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationImportServiceImplTest.java -
Add or modify tests for customer relation import/service.
-
Step 1: Write failing staff manual auto-fill test
In CcdiStaffEnterpriseRelationServiceImplTest#insertRelation_shouldAllowValidFamily, mock EnterpriseAutoFillService and verify:
verify(enterpriseAutoFillService).ensureExists(argThat(item ->
"91310000123456789A".equals(item.socialCreditCode())
&& "测试企业".equals(item.enterpriseName())
&& "EMP_RELATION".equals(item.entSource())
&& "MANUAL".equals(item.dataSource())));
- Step 2: Write failing staff import auto-fill test
In CcdiStaffEnterpriseRelationImportServiceImplTest, add an async import test using mocks and assert ensureExistsBatch receives only the successful newRecords.
- Step 3: Write failing credit manual/import auto-fill tests
Add or extend customer relation service/import tests:
-
Manual insert calls
ensureExistswithCREDIT_CUSTOMER/MANUAL. -
Import calls
ensureExistsBatchwith only successful records andCREDIT_CUSTOMER/IMPORT. -
Duplicate DB or duplicate file rows do not enter auto-fill.
-
Step 4: Run tests and verify failure
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest,CcdiCustEnterpriseRelationServiceImplTest,CcdiCustEnterpriseRelationImportServiceImplTest test
Expected: FAIL because services do not call EnterpriseAutoFillService.
- Step 5: Implement staff manual auto-fill
In CcdiStaffEnterpriseRelationServiceImpl inject:
@Resource
private EnterpriseAutoFillService enterpriseAutoFillService;
After existsByPersonIdAndSocialCreditCode passes and before relationMapper.insert(relation), call:
enterpriseAutoFillService.ensureExists(new EnterpriseAutoFillService.EnterpriseFillItem(
addDTO.getSocialCreditCode(),
addDTO.getEnterpriseName(),
EnterpriseSource.EMP_RELATION.getCode(),
DataSource.MANUAL.getCode(),
SecurityUtils.getUsername()
));
- Step 6: Implement staff import auto-fill
In CcdiStaffEnterpriseRelationImportServiceImpl, after newRecords are collected and before saveBatch(newRecords, 500), call ensureExistsBatch with EMP_RELATION/IMPORT/userName.
- Step 7: Implement credit manual/import auto-fill
Use the same pattern:
CcdiCustEnterpriseRelationServiceImpl:CREDIT_CUSTOMER/MANUALCcdiCustEnterpriseRelationImportServiceImpl:CREDIT_CUSTOMER/IMPORT
Do not call auto-fill for failed import rows.
- Step 8: Run focused relation tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest,CcdiCustEnterpriseRelationServiceImplTest,CcdiCustEnterpriseRelationImportServiceImplTest test
Expected: PASS.
- Step 9: Commit
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffEnterpriseRelationImportServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCustEnterpriseRelationServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCustEnterpriseRelationImportServiceImpl.java
git commit -m "接入员工亲属和信贷客户实体自动补入"
If the customer test files do not exist yet, create them as local verification files only and do not stage them.
Task 7: Intermediary Enterprise Relation Auto-Fill
Files:
-
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 -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryServiceImplTest.java -
Test:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryEnterpriseRelationImportServiceImplTest.java -
Step 1: Write failing intermediary no-name auto-fill contract tests
Add local tests proving the backend contract:
-
Do not add
enterpriseNametoCcdiIntermediaryEnterpriseRelationAddDTO. -
Do not add
机构名称toCcdiIntermediaryEnterpriseRelationExcel. -
Missing entity + no enterprise name succeeds after auto-fill.
-
The auto-filled
EnterpriseFillItem.enterpriseName()isnull. -
Step 2: Write failing manual relation test
In CcdiIntermediaryServiceImplTest, add:
@Test
void insertIntermediaryEnterpriseRelation_shouldAutoFillMissingEnterpriseBeforeInsert() {
CcdiBizIntermediary owner = new CcdiBizIntermediary();
owner.setBizId("owner-biz");
owner.setPersonSubType("本人");
when(bizIntermediaryMapper.selectById("owner-biz")).thenReturn(owner);
when(enterpriseBaseInfoMapper.selectById("91330100MA27X12345")).thenReturn(null);
when(enterpriseRelationMapper.existsByIntermediaryBizIdAndSocialCreditCode("owner-biz", "91330100MA27X12345")).thenReturn(false);
when(enterpriseRelationMapper.insert(any())).thenReturn(1);
CcdiIntermediaryEnterpriseRelationAddDTO addDTO = buildEnterpriseRelationAddDto();
service.insertIntermediaryEnterpriseRelation("owner-biz", addDTO);
verify(enterpriseAutoFillService).ensureExists(argThat(item ->
"INTERMEDIARY".equals(item.entSource())
&& "MANUAL".equals(item.dataSource())
&& item.enterpriseName() == null));
}
- Step 3: Write failing import relation test
Change CcdiIntermediaryEnterpriseRelationImportServiceImplTest#importEnterpriseRelationAsync_shouldFailWhenEnterpriseDoesNotExist into a success test:
-
Owner exists.
-
Entity does not exist.
-
Excel row has no enterprise name column.
-
Relation combination does not exist.
-
EnterpriseAutoFillService.ensureExistsBatchis called withINTERMEDIARY/IMPORTandenterpriseName == null. -
Relation insert succeeds.
-
Step 4: Run tests and verify failure
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest test
Expected: FAIL because current code requires the enterprise to exist.
- Step 5: Keep intermediary API and Excel contract unchanged
Do not modify these files for the intermediary no-name rule:
CcdiIntermediaryEnterpriseRelationAddDTOCcdiIntermediaryEnterpriseRelationExcelIntermediaryEnterpriseRelationImportFailureVO
The intermediary relation only provides socialCreditCode; missing entity auto-fill must insert a minimal entity with enterprise_name = NULL. The current initialization SQL already defines ccdi_enterprise_base_info.enterprise_name as nullable.
- Step 6: Replace manual existing-enterprise check
In CcdiIntermediaryServiceImpl:
- Inject
EnterpriseAutoFillService. - Split
validateEnterpriseRelationso it only checks owner and duplicate relation. - Remove the
enterpriseBaseInfoMapper.selectById(socialCreditCode) == nullfailure for insert. - Add a helper that checks whether the entity exists. If it exists, do not call auto-fill and do not update it. If it does not exist, call auto-fill with
enterpriseName = nullbeforeenterpriseRelationMapper.insert(relation):
private void ensureIntermediaryEnterpriseExists(CcdiIntermediaryEnterpriseRelationAddDTO addDTO) {
if (enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode()) != null) {
return;
}
enterpriseAutoFillService.ensureExists(new EnterpriseAutoFillService.EnterpriseFillItem(
addDTO.getSocialCreditCode(),
null,
EnterpriseSource.INTERMEDIARY.getCode(),
DataSource.MANUAL.getCode(),
SecurityUtils.getUsername()
));
}
- Step 7: Replace import existing-enterprise check
In CcdiIntermediaryEnterpriseRelationImportServiceImpl:
-
Remove the existing enterprise code query from row failure validation;
EnterpriseAutoFillServicewill check existing entities in batch and insert only missing ones. -
If a row references a missing entity, do not fail just because the entity is missing.
-
If a row references an existing entity, do not update that entity.
-
Build
EnterpriseFillItemfrom successful Excel rows withenterpriseName = null. -
Call
ensureExistsBatchbeforesaveBatch(successRecords, 500). -
Keep owner missing, DB duplicate relation, and file duplicate relation failures unchanged.
-
Step 8: Run intermediary tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest,EnterpriseAutoFillServiceTest test
Expected: PASS.
- Step 9: Commit
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.java
git commit -m "接入中介实体自动补入"
Task 8: Purchase Supplier Auto-Fill
Files:
-
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:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiPurchaseTransactionFeatureContractTest.java -
Add if useful:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiPurchaseTransactionServiceImplTest.java -
Add if useful:
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiPurchaseTransactionImportServiceImplTest.java -
Step 1: Write failing manual supplier auto-fill test
Create focused service test if missing:
-
insertTransaction:supplierUsccnon-empty and valid -> callsensureExistsBatchwithSUPPLIER/MANUAL. -
updateTransaction:supplierUsccnon-empty and valid -> callsensureExistsBatchwithSUPPLIER/MANUAL. -
supplierUsccempty -> no auto-fill call for that supplier, but supplier is still saved if existing rules allow it. -
supplierUsccnon-empty but invalid -> no auto-fill call for that supplier, but do not add a new manual-save failure condition. -
Step 2: Write failing import supplier auto-fill test
In import test:
-
Successful purchase with supplier
supplierUsccnon-empty -> auto-fill. -
Successful purchase with supplier
supplierUsccempty -> no auto-fill, no failure because of empty code. -
Failed purchase -> supplier does not enter auto-fill.
-
Invalid supplier USCC -> existing supplier validation fails and does not auto-fill.
-
Step 3: Run supplier tests and verify failure
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiPurchaseTransactionFeatureContractTest,CcdiPurchaseTransactionServiceImplTest,CcdiPurchaseTransactionImportServiceImplTest test
Expected: FAIL because purchase services do not call auto-fill.
- Step 4: Implement manual supplier auto-fill
In CcdiPurchaseTransactionServiceImpl inject EnterpriseAutoFillService.
Add one helper in the service and reuse it for add and edit:
private static final String SUPPLIER_USCC_PATTERN = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$";
private boolean isValidSupplierUscc(String supplierUscc) {
return StringUtils.isNotEmpty(supplierUscc) && supplierUscc.matches(SUPPLIER_USCC_PATTERN);
}
private void autoFillSuppliers(List<CcdiPurchaseTransactionSupplier> supplierList, String dataSource, String userName) {
enterpriseAutoFillService.ensureExistsBatch(supplierList.stream()
.filter(item -> isValidSupplierUscc(item.getSupplierUscc()))
.map(item -> new EnterpriseAutoFillService.EnterpriseFillItem(
item.getSupplierUscc(),
item.getSupplierName(),
EnterpriseSource.SUPPLIER.getCode(),
dataSource,
userName
))
.toList());
}
Call it in both write paths:
insertTransaction: afterbuildSupplierEntities(...)and beforetransactionMapper.insert(transaction).updateTransaction: afterbuildSupplierEntities(...)and before replacing supplier rows.
Use DataSource.MANUAL.getCode() and SecurityUtils.getUsername() for both manual paths. Do not fail suppliers just because supplierUscc is empty or invalid; invalid codes are only excluded from entity auto-fill.
- Step 5: Implement import supplier auto-fill
In CcdiPurchaseTransactionImportServiceImpl, after newSuppliers is fully collected and before saveBatch(newTransactions, 500):
autoFillSuppliers(newSuppliers, DataSource.IMPORT.getCode(), userName);
Use the same valid-USCC filter as manual save. This naturally excludes failed purchase rows because they never enter newSuppliers; invalid supplier USCC rows already fail existing import validation before they can be collected.
- Step 6: Run supplier tests
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiPurchaseTransactionFeatureContractTest,CcdiPurchaseTransactionServiceImplTest,CcdiPurchaseTransactionImportServiceImplTest test
Expected: PASS.
- Step 7: Commit
git add \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionServiceImpl.java \
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiPurchaseTransactionImportServiceImpl.java
git commit -m "接入招投标供应商实体自动补入"
Keep supplier tests as local verification files unless the user explicitly asks to force-add tests.
Task 9: Backend Regression And Implementation Record
Files:
-
Create:
docs/reports/implementation/2026-05-06-staff-asset-import-and-enterprise-autofill-fix-backend-implementation.md -
Step 1: Run full backend focused regression
Run:
mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest,EnterpriseAutoFillServiceTest,CcdiBaseStaffDualImportServiceTest,CcdiBaseStaffAssetImportServiceImplTest,CcdiBaseStaffServiceImplTest,CcdiBaseStaffControllerTest,CcdiStaffFmyRelationImportServiceImplTest,CcdiAssetInfoImportServiceImplTest,CcdiStaffFmyRelationServiceImplTest,CcdiStaffFmyRelationControllerTest,CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest,CcdiCustEnterpriseRelationServiceImplTest,CcdiCustEnterpriseRelationImportServiceImplTest,CcdiIntermediaryServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest,CcdiPurchaseTransactionFeatureContractTest,CcdiPurchaseTransactionServiceImplTest,CcdiPurchaseTransactionImportServiceImplTest test
Expected: PASS.
If Mockito fails with inline Byte Buddy self-attach on JDK 21, use the existing project workaround:
-
Add or verify
ccdi-info-collection/src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontainsmock-maker-subclass. -
Re-run the same command.
-
Step 2: Run module compile
Run:
mvn -pl ccdi-info-collection -am test -DskipTests
Expected: PASS compilation.
- Step 3: Run API-level verification after backend restart
Restart backend with the project script:
sh bin/restart_java_backend.sh
Use /login/test to get token, download real templates, and verify:
/ccdi/baseStaff/importTemplate/ccdi/baseStaff/importData/ccdi/baseStaff/importStatus/{taskId}/ccdi/baseStaff/importFailures/{taskId}/ccdi/staffFmyRelation/importTemplate/ccdi/staffFmyRelation/importData/ccdi/staffFmyRelation/importStatus/{taskId}/ccdi/staffFmyRelation/importFailures/{taskId}/ccdi/staffEnterpriseRelation/ccdi/staffEnterpriseRelation/importTemplate/ccdi/staffEnterpriseRelation/importData/ccdi/staffEnterpriseRelation/importStatus/{taskId}/ccdi/staffEnterpriseRelation/importFailures/{taskId}/ccdi/custEnterpriseRelation/ccdi/custEnterpriseRelation/importTemplate/ccdi/custEnterpriseRelation/importData/ccdi/custEnterpriseRelation/importStatus/{taskId}/ccdi/custEnterpriseRelation/importFailures/{taskId}/ccdi/intermediary/{bizId}/enterprise-relation/ccdi/intermediary/importEnterpriseRelationTemplate/ccdi/intermediary/importEnterpriseRelationData/ccdi/intermediary/importEnterpriseRelationStatus/{taskId}/ccdi/intermediary/importEnterpriseRelationFailures/{taskId}/ccdi/purchaseTransaction/ccdi/purchaseTransaction/importTemplate/ccdi/purchaseTransaction/importData/ccdi/purchaseTransaction/importStatus/{taskId}/ccdi/purchaseTransaction/importFailures/{taskId}/ccdi/enterpriseBaseInfo/list
Scenarios:
-
Employee asset-only import referencing an existing employee succeeds.
-
Family asset-only import referencing an existing family relation succeeds.
-
Employee + employee asset in same file succeeds.
-
Family relation + family asset in same file succeeds.
-
Failed main row does not provide owner context to asset row.
-
Manual staff-family entity relation inserts missing entity with
ent_source=EMP_RELATION,data_source=MANUAL,risk_level IS NULL. -
Staff-family entity relation import inserts missing entity with
ent_source=EMP_RELATION,data_source=IMPORT,risk_level IS NULL; failed rows do not auto-fill. -
Manual credit-customer entity relation inserts missing entity with
ent_source=CREDIT_CUSTOMER,data_source=MANUAL,risk_level IS NULL. -
Credit-customer entity relation import inserts missing entity with
ent_source=CREDIT_CUSTOMER,data_source=IMPORT,risk_level IS NULL; failed rows do not auto-fill. -
Manual intermediary entity relation inserts missing entity without requiring enterprise name, with
enterprise_name IS NULL,ent_source=INTERMEDIARY,data_source=MANUAL,risk_level=1. -
Intermediary entity relation import inserts missing entity without requiring enterprise name, with
enterprise_name IS NULL,ent_source=INTERMEDIARY,data_source=IMPORT,risk_level=1. -
Manual purchase add and edit insert missing supplier entity with
ent_source=SUPPLIER,data_source=MANUAL,risk_level IS NULL. -
Purchase import inserts missing supplier entity with
ent_source=SUPPLIER,data_source=IMPORT,risk_level IS NULL; failed purchase or invalid supplier rows do not auto-fill. -
Existing entity rows for all four sources are not updated. Verify by seeding an existing
ccdi_enterprise_base_inforow with a sentinelenterprise_name,ent_source,data_source, andrisk_level, then re-querying the same row after each manual/import path. -
Supplier with empty
supplierUsccfollows existing save/import validation but does not auto-fill. -
Step 4: Run real page verification
Use the browser-use skill on real business pages, not prototype pages:
-
【员工信息维护】download template, upload employee + employee asset file, check task status, failure records, list/detail asset.
-
【员工亲属关系维护】download template, upload relation + family asset file, check task status, failure records, detail asset.
-
【实体库管理】query auto-filled entities by social credit code.
-
Step 5: Clean test data
Delete this run's test data:
ccdi_asset_infoccdi_base_staffccdi_staff_fmy_relationccdi_staff_enterprise_relationccdi_cust_enterprise_relationccdi_intermediary_enterprise_relationccdi_purchase_transactionccdi_purchase_transaction_supplierccdi_enterprise_base_info
For SQL containing Chinese comments or values, write a SQL file and execute:
bin/mysql_utf8_exec.sh <sql-file>
- Step 6: Stop test-started processes
Stop backend/frontend processes started during verification. Do not stop unrelated user processes.
- Step 7: Write implementation record
Create docs/reports/implementation/2026-05-06-staff-asset-import-and-enterprise-autofill-fix-backend-implementation.md:
# 员工资产导入与实体库自动补入修复实施记录
## 修改内容
- 员工信息维护双 Sheet 导入改为后端顺序编排,保留两个任务 ID。
- 员工亲属关系维护双 Sheet 导入改为后端顺序编排,保留两个任务 ID。
- 只导资产 Sheet 时按数据库已有归属正常导入。
- 新增统一实体库自动补入服务,接入员工亲属、信贷客户、中介、供应商四类业务。
## 影响范围
- 后端导入服务
- 实体库来源枚举
- 关联业务新增和导入链路
## 验证情况
- 单元测试:
- 编译:
- 接口验证:
- 页面验证:
- 数据清理:
- Step 8: Commit implementation record and final changes
Check staged scope:
git status --short
git diff --cached --name-status
Then commit only related files:
git add <related files>
git commit -m "修复员工资产导入与实体库自动补入"
Do not use git add -f for local src/test verification files unless the user explicitly asks to commit tests.
Final Verification Checklist
- Asset-only employee import succeeds when database employee exists.
- Asset-only family import succeeds when database family owner exists.
- Same-file employee + employee asset import succeeds.
- Same-file family relation + family asset import succeeds.
- Main Sheet failed row never becomes asset owner context.
- Two-task-ID response shape remains unchanged.
EnterpriseSource.SUPPLIERappears in enum endpoint as供应商.- Auto-fill inserts missing entities only.
- Auto-fill does not update existing entities.
- Staff-family manual add and import insert
ent_source=EMP_RELATION,data_source=MANUAL/IMPORT,risk_level=NULL. - Credit-customer manual add and import insert
ent_source=CREDIT_CUSTOMER,data_source=MANUAL/IMPORT,risk_level=NULL. - Intermediary manual add and import insert
enterprise_name=NULL,ent_source=INTERMEDIARY,data_source=MANUAL/IMPORT,risk_level=1. - Supplier manual add, manual edit, and import insert
ent_source=SUPPLIER,data_source=MANUAL/IMPORT,risk_level=NULL. - Failed staff-family, credit-customer, intermediary, and supplier import rows do not auto-fill entities.
- Intermediary missing entity does not require
enterpriseName/机构名称. - Intermediary auto-fill inserts
risk_level=1. - Staff-family, credit-customer, and supplier auto-fill insert
risk_level=NULL. - Supplier empty or invalid
supplierUsccdoes not auto-fill and does not become a new manual-save failure condition. - Real business pages verified.
- Test data cleaned.
- Test-started processes stopped.