1379 lines
56 KiB
Markdown
1379 lines
56 KiB
Markdown
# Staff Asset Import And Enterprise Auto-Fill Fix Implementation Plan
|
|
|
|
> **For implementers:** Follow this repository's `AGENTS.md`. Do not enable `using-superpowers` or 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 with `git 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", "供应商")`.
|
|
- 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`.
|
|
- 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`.
|
|
- 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.java`
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffDualImportServiceTest.java`
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java`
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java`
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffFmyRelationImportServiceImplTest.java`
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationServiceImplTest.java`
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiStaffEnterpriseRelationImportServiceImplTest.java`
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryServiceImplTest.java`
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryEnterpriseRelationImportServiceImplTest.java`
|
|
- `ccdi-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.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`
|
|
- `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`
|
|
- `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`
|
|
- 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:
|
|
|
|
```java
|
|
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:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest test
|
|
```
|
|
|
|
Expected: FAIL because `SUPPLIER/供应商` is not present.
|
|
|
|
- [ ] **Step 3: Add SUPPLIER enum**
|
|
|
|
Modify `EnterpriseSource`:
|
|
|
|
```java
|
|
SUPPLIER("SUPPLIER", "供应商"),
|
|
```
|
|
|
|
Place it after `INTERMEDIARY(...)` and before `BOTH(...)`.
|
|
|
|
- [ ] **Step 4: Run enum test and verify it passes**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 5: Write failing auto-fill service tests**
|
|
|
|
Create `EnterpriseAutoFillServiceTest` with Mockito. Cover:
|
|
|
|
```java
|
|
@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 `selectBatchIds` is not inserted.
|
|
- Duplicate items in same batch insert once.
|
|
- `INTERMEDIARY` sets `riskLevel` to `"1"` and still inserts when `enterpriseName` is `null`.
|
|
- `EMP_RELATION` and `CREDIT_CUSTOMER` keep `riskLevel` null.
|
|
- `DuplicateKeyException` from batch insert falls back to per-row `selectById` and treats existing row as success.
|
|
|
|
- [ ] **Step 6: Run auto-fill tests and verify they fail**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=EnterpriseAutoFillServiceTest test
|
|
```
|
|
|
|
Expected: FAIL because `EnterpriseAutoFillService` does not exist.
|
|
|
|
- [ ] **Step 7: Implement EnterpriseAutoFillService**
|
|
|
|
Create `EnterpriseAutoFillService`:
|
|
|
|
```java
|
|
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:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiEnumControllerTest,EnterpriseAutoFillServiceTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 9: Commit**
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```java
|
|
@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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
public record BaseStaffImportExecutionResult(
|
|
List<CcdiBaseStaff> successRecords,
|
|
List<ImportFailureVO> failures,
|
|
Set<String> successIdCards
|
|
) {
|
|
}
|
|
```
|
|
|
|
- Add:
|
|
|
|
```java
|
|
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 `importBaseStaffAsync` delegate to:
|
|
|
|
```java
|
|
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:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffDualImportServiceTest,CcdiBaseStaffImportServiceImplTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 5: Write failing employee asset extra-owner test**
|
|
|
|
In `CcdiBaseStaffAssetImportServiceImplTest`, add:
|
|
|
|
```java
|
|
@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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
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:
|
|
|
|
```java
|
|
Map<String, Set<String>> ownerMap = buildOwnerMap(personIds);
|
|
mergeOwnerMappings(ownerMap, extraOwnerMap);
|
|
```
|
|
|
|
- Keep `importAssetInfoAsync` delegating to:
|
|
|
|
```java
|
|
executeAssetImport(excelList, taskId, userName, Map.of());
|
|
```
|
|
|
|
- Do not require staff Sheet data; when `extraOwnerMap` is empty, behavior must match the current independent asset import.
|
|
|
|
- [ ] **Step 8: Run employee import service tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffDualImportServiceTest,CcdiBaseStaffAssetImportServiceImplTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 9: Commit**
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
BaseStaffImportSubmitResultVO result = new BaseStaffImportSubmitResultVO();
|
|
result.setAssetTaskId("asset-task");
|
|
result.setMessage("已提交员工资产信息导入任务");
|
|
```
|
|
|
|
- [ ] **Step 2: Write failing service tests for task IDs**
|
|
|
|
In `CcdiBaseStaffServiceImplTest`, add tests:
|
|
|
|
- `importBaseStaffWithAssets_shouldReturnOnlyAssetTaskIdWhenStaffRowsEmpty`
|
|
- `importBaseStaffWithAssets_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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
BaseStaffImportSubmitResultVO importBaseStaffWithAssets(
|
|
List<CcdiBaseStaffExcel> staffList,
|
|
List<CcdiBaseStaffAssetInfoExcel> assetList
|
|
);
|
|
```
|
|
|
|
Add imports for `CcdiBaseStaffAssetInfoExcel` and `BaseStaffImportSubmitResultVO`.
|
|
|
|
- [ ] **Step 5: Implement task initialization and orchestration**
|
|
|
|
In `CcdiBaseStaffServiceImpl`:
|
|
|
|
- Inject:
|
|
|
|
```java
|
|
@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:
|
|
|
|
1. Run `executeBaseStaffImport` only when `staffList` has rows.
|
|
2. Convert `successIdCards` to `Map<String, Set<String>>`.
|
|
3. Run `executeAssetImport` only when `assetList` has rows.
|
|
4. 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:
|
|
|
|
```java
|
|
BaseStaffImportSubmitResultVO result = new BaseStaffImportSubmitResultVO();
|
|
if (hasStaffRows) {
|
|
result.setStaffTaskId(baseStaffService.importBaseStaff(staffList));
|
|
}
|
|
if (hasAssetRows) {
|
|
result.setAssetTaskId(baseStaffAssetImportService.importAssetInfo(assetList));
|
|
}
|
|
```
|
|
|
|
with:
|
|
|
|
```java
|
|
BaseStaffImportSubmitResultVO result = baseStaffService.importBaseStaffWithAssets(staffList, assetList);
|
|
```
|
|
|
|
Keep existing response text:
|
|
|
|
```java
|
|
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
|
```
|
|
|
|
- [ ] **Step 7: Run employee dual-Sheet tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiBaseStaffServiceImplTest,CcdiBaseStaffControllerTest,CcdiBaseStaffDualImportServiceTest,CcdiBaseStaffAssetImportServiceImplTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
public record StaffFmyRelationImportExecutionResult(
|
|
List<CcdiStaffFmyRelation> successRecords,
|
|
List<StaffFmyRelationImportFailureVO> failures,
|
|
Map<String, Set<String>> ownerMap
|
|
) {
|
|
}
|
|
```
|
|
|
|
- Add:
|
|
|
|
```java
|
|
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 `importRelationAsync` delegate to `executeRelationImport`.
|
|
|
|
- [ ] **Step 4: Write failing family asset extra-owner test**
|
|
|
|
In `CcdiAssetInfoImportServiceImplTest`, add:
|
|
|
|
```java
|
|
@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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
public void executeAssetImport(
|
|
List<CcdiAssetInfoExcel> excelList,
|
|
String taskId,
|
|
String userName,
|
|
Map<String, Set<String>> extraOwnerMap
|
|
) {
|
|
// Move current importAssetInfoAsync body here.
|
|
}
|
|
```
|
|
|
|
- Merge `extraOwnerMap` into the existing owner map after calling `selectOwnerCandidatesByRelationCertNos`.
|
|
- Do not add `status = 1` filtering to the database owner query.
|
|
- Keep owner ambiguity behavior: if merged owner set has more than one family id, fail with `亲属资产归属员工不唯一`.
|
|
- Make `importAssetInfoAsync` delegate with `Map.of()`.
|
|
|
|
- [ ] **Step 7: Run family import tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiStaffFmyRelationImportServiceImplTest,CcdiAssetInfoImportServiceImplTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
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_shouldReturnOnlyAssetTaskIdWhenRelationRowsEmpty`
|
|
- `importRelationWithAssets_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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
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:
|
|
|
|
1. Run relation import only when relation rows exist.
|
|
2. Build extra owner map from successful relation rows.
|
|
3. Run asset import only when asset rows exist.
|
|
4. Pass empty extra owner map when relation rows are empty.
|
|
5. Keep asset-only import as a normal path.
|
|
|
|
- [ ] **Step 6: Update controller**
|
|
|
|
In `CcdiStaffFmyRelationController#importData`, replace independent calls:
|
|
|
|
```java
|
|
result.setRelationTaskId(relationService.importRelation(relationList));
|
|
result.setAssetTaskId(assetInfoImportService.importAssetInfo(assetList));
|
|
```
|
|
|
|
with:
|
|
|
|
```java
|
|
StaffFmyRelationImportSubmitResultVO result = relationService.importRelationWithAssets(relationList, assetList);
|
|
```
|
|
|
|
- [ ] **Step 7: Run family dual-Sheet tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiStaffFmyRelationServiceImplTest,CcdiStaffFmyRelationControllerTest,CcdiStaffFmyRelationImportServiceImplTest,CcdiAssetInfoImportServiceImplTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 8: Commit**
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
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 `ensureExists` with `CREDIT_CUSTOMER/MANUAL`.
|
|
- Import calls `ensureExistsBatch` with only successful records and `CREDIT_CUSTOMER/IMPORT`.
|
|
- Duplicate DB or duplicate file rows do not enter auto-fill.
|
|
|
|
- [ ] **Step 4: Run tests and verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
@Resource
|
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
|
```
|
|
|
|
After `existsByPersonIdAndSocialCreditCode` passes and before `relationMapper.insert(relation)`, call:
|
|
|
|
```java
|
|
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/MANUAL`
|
|
- `CcdiCustEnterpriseRelationImportServiceImpl`: `CREDIT_CUSTOMER/IMPORT`
|
|
|
|
Do not call auto-fill for failed import rows.
|
|
|
|
- [ ] **Step 8: Run focused relation tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest,CcdiCustEnterpriseRelationServiceImplTest,CcdiCustEnterpriseRelationImportServiceImplTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 9: Commit**
|
|
|
|
```bash
|
|
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 `enterpriseName` to `CcdiIntermediaryEnterpriseRelationAddDTO`.
|
|
- Do not add `机构名称` to `CcdiIntermediaryEnterpriseRelationExcel`.
|
|
- Missing entity + no enterprise name succeeds after auto-fill.
|
|
- The auto-filled `EnterpriseFillItem.enterpriseName()` is `null`.
|
|
|
|
- [ ] **Step 2: Write failing manual relation test**
|
|
|
|
In `CcdiIntermediaryServiceImplTest`, add:
|
|
|
|
```java
|
|
@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.ensureExistsBatch` is called with `INTERMEDIARY/IMPORT` and `enterpriseName == null`.
|
|
- Relation insert succeeds.
|
|
|
|
- [ ] **Step 4: Run tests and verify failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
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:
|
|
|
|
- `CcdiIntermediaryEnterpriseRelationAddDTO`
|
|
- `CcdiIntermediaryEnterpriseRelationExcel`
|
|
- `IntermediaryEnterpriseRelationImportFailureVO`
|
|
|
|
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 `validateEnterpriseRelation` so it only checks owner and duplicate relation.
|
|
- Remove the `enterpriseBaseInfoMapper.selectById(socialCreditCode) == null` failure 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 = null` before `enterpriseRelationMapper.insert(relation)`:
|
|
|
|
```java
|
|
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; `EnterpriseAutoFillService` will 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 `EnterpriseFillItem` from successful Excel rows with `enterpriseName = null`.
|
|
- Call `ensureExistsBatch` before `saveBatch(successRecords, 500)`.
|
|
- Keep owner missing, DB duplicate relation, and file duplicate relation failures unchanged.
|
|
|
|
- [ ] **Step 8: Run intermediary tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest,EnterpriseAutoFillServiceTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 9: Commit**
|
|
|
|
```bash
|
|
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`: `supplierUscc` non-empty and valid -> calls `ensureExistsBatch` with `SUPPLIER/MANUAL`.
|
|
- `updateTransaction`: `supplierUscc` non-empty and valid -> calls `ensureExistsBatch` with `SUPPLIER/MANUAL`.
|
|
- `supplierUscc` empty -> no auto-fill call for that supplier, but supplier is still saved if existing rules allow it.
|
|
- `supplierUscc` non-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 `supplierUscc` non-empty -> auto-fill.
|
|
- Successful purchase with supplier `supplierUscc` empty -> 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```java
|
|
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`: after `buildSupplierEntities(...)` and before `transactionMapper.insert(transaction)`.
|
|
- `updateTransaction`: after `buildSupplierEntities(...)` 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)`:
|
|
|
|
```java
|
|
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:
|
|
|
|
```bash
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiPurchaseTransactionFeatureContractTest,CcdiPurchaseTransactionServiceImplTest,CcdiPurchaseTransactionImportServiceImplTest test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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.MockMaker` contains `mock-maker-subclass`.
|
|
- Re-run the same command.
|
|
|
|
- [ ] **Step 2: Run module compile**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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_info` row with a sentinel `enterprise_name`, `ent_source`, `data_source`, and `risk_level`, then re-querying the same row after each manual/import path.
|
|
- Supplier with empty `supplierUscc` follows 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_info`
|
|
- `ccdi_base_staff`
|
|
- `ccdi_staff_fmy_relation`
|
|
- `ccdi_staff_enterprise_relation`
|
|
- `ccdi_cust_enterprise_relation`
|
|
- `ccdi_intermediary_enterprise_relation`
|
|
- `ccdi_purchase_transaction`
|
|
- `ccdi_purchase_transaction_supplier`
|
|
- `ccdi_enterprise_base_info`
|
|
|
|
For SQL containing Chinese comments or values, write a SQL file and execute:
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```markdown
|
|
# 员工资产导入与实体库自动补入修复实施记录
|
|
|
|
## 修改内容
|
|
|
|
- 员工信息维护双 Sheet 导入改为后端顺序编排,保留两个任务 ID。
|
|
- 员工亲属关系维护双 Sheet 导入改为后端顺序编排,保留两个任务 ID。
|
|
- 只导资产 Sheet 时按数据库已有归属正常导入。
|
|
- 新增统一实体库自动补入服务,接入员工亲属、信贷客户、中介、供应商四类业务。
|
|
|
|
## 影响范围
|
|
|
|
- 后端导入服务
|
|
- 实体库来源枚举
|
|
- 关联业务新增和导入链路
|
|
|
|
## 验证情况
|
|
|
|
- 单元测试:
|
|
- 编译:
|
|
- 接口验证:
|
|
- 页面验证:
|
|
- 数据清理:
|
|
```
|
|
|
|
- [ ] **Step 8: Commit implementation record and final changes**
|
|
|
|
Check staged scope:
|
|
|
|
```bash
|
|
git status --short
|
|
git diff --cached --name-status
|
|
```
|
|
|
|
Then commit only related files:
|
|
|
|
```bash
|
|
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.SUPPLIER` appears 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 `supplierUscc` does not auto-fill and does not become a new manual-save failure condition.
|
|
- [ ] Real business pages verified.
|
|
- [ ] Test data cleaned.
|
|
- [ ] Test-started processes stopped.
|