提交资产导入拆分当前进展
This commit is contained in:
@@ -28,12 +28,12 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 亲属资产信息Controller
|
||||
* 资产信息导入Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-03-12
|
||||
*/
|
||||
@Tag(name = "亲属资产信息管理")
|
||||
@Tag(name = "资产信息导入管理")
|
||||
@RestController
|
||||
@RequestMapping("/ccdi/assetInfo")
|
||||
public class CcdiAssetInfoController extends BaseController {
|
||||
@@ -44,18 +44,18 @@ public class CcdiAssetInfoController extends BaseController {
|
||||
/**
|
||||
* 下载导入模板
|
||||
*/
|
||||
@Operation(summary = "下载亲属资产导入模板")
|
||||
@Operation(summary = "下载资产导入模板")
|
||||
@PostMapping("/importTemplate")
|
||||
public void importTemplate(HttpServletResponse response) {
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiAssetInfoExcel.class, "亲属资产信息");
|
||||
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiAssetInfoExcel.class, "资产信息");
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入亲属资产信息
|
||||
* 导入资产信息
|
||||
*/
|
||||
@Operation(summary = "导入亲属资产信息")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')")
|
||||
@Log(title = "亲属资产信息", businessType = BusinessType.IMPORT)
|
||||
@Operation(summary = "导入资产信息")
|
||||
@PreAuthorize("@ss.hasAnyPermi('ccdi:employee:import,ccdi:staffFmyRelation:import')")
|
||||
@Log(title = "资产信息", businessType = BusinessType.IMPORT)
|
||||
@PostMapping("/importData")
|
||||
public AjaxResult importData(MultipartFile file) throws Exception {
|
||||
List<CcdiAssetInfoExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiAssetInfoExcel.class);
|
||||
@@ -74,8 +74,8 @@ public class CcdiAssetInfoController extends BaseController {
|
||||
/**
|
||||
* 查询导入状态
|
||||
*/
|
||||
@Operation(summary = "查询亲属资产导入状态")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')")
|
||||
@Operation(summary = "查询资产导入状态")
|
||||
@PreAuthorize("@ss.hasAnyPermi('ccdi:employee:import,ccdi:staffFmyRelation:import')")
|
||||
@GetMapping("/importStatus/{taskId}")
|
||||
public AjaxResult getImportStatus(@PathVariable String taskId) {
|
||||
return success(assetInfoImportService.getImportStatus(taskId));
|
||||
@@ -84,8 +84,8 @@ public class CcdiAssetInfoController extends BaseController {
|
||||
/**
|
||||
* 查询导入失败记录
|
||||
*/
|
||||
@Operation(summary = "查询亲属资产导入失败记录")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:staffFmyRelation:import')")
|
||||
@Operation(summary = "查询资产导入失败记录")
|
||||
@PreAuthorize("@ss.hasAnyPermi('ccdi:employee:import,ccdi:staffFmyRelation:import')")
|
||||
@GetMapping("/importFailures/{taskId}")
|
||||
public TableDataInfo getImportFailures(
|
||||
@PathVariable String taskId,
|
||||
|
||||
@@ -24,8 +24,8 @@ public class CcdiAssetInfoExcel implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 关系人证件号 */
|
||||
@ExcelProperty(value = "关系人证件号*", index = 0)
|
||||
/** 资产实际持有人证件号 */
|
||||
@ExcelProperty(value = "资产实际持有人证件号*", index = 0)
|
||||
@ColumnWidth(22)
|
||||
@Required
|
||||
@TextFormat
|
||||
|
||||
@@ -75,6 +75,14 @@ public interface CcdiAssetInfoMapper extends BaseMapper<CcdiAssetInfo> {
|
||||
*/
|
||||
int insertBatch(@Param("list") List<CcdiAssetInfo> list);
|
||||
|
||||
/**
|
||||
* 按资产实际持有人证件号查询员工本人归属候选
|
||||
*
|
||||
* @param personIds 资产实际持有人证件号列表
|
||||
* @return 归属映射
|
||||
*/
|
||||
List<Map<String, String>> selectOwnerCandidatesByPersonIds(@Param("personIds") List<String> personIds);
|
||||
|
||||
/**
|
||||
* 按关系人证件号查询归属员工候选
|
||||
*
|
||||
|
||||
@@ -97,10 +97,10 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
|
||||
validateExcel(excel);
|
||||
Set<String> familyIds = ownerMap.get(excel.getPersonId());
|
||||
if (familyIds == null || familyIds.isEmpty()) {
|
||||
throw new RuntimeException("未找到亲属资产归属员工");
|
||||
throw new RuntimeException("未找到资产归属员工");
|
||||
}
|
||||
if (familyIds.size() > 1) {
|
||||
throw new RuntimeException("亲属资产归属员工不唯一");
|
||||
throw new RuntimeException("资产归属员工不唯一");
|
||||
}
|
||||
|
||||
CcdiAssetInfo assetInfo = new CcdiAssetInfo();
|
||||
@@ -164,7 +164,19 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
|
||||
|
||||
private Map<String, Set<String>> buildOwnerMap(List<String> personIds) {
|
||||
Map<String, Set<String>> result = new LinkedHashMap<>();
|
||||
mergeOwnerMappings(result, assetInfoMapper.selectOwnerCandidatesByRelationCertNos(personIds));
|
||||
if (personIds == null || personIds.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
List<Map<String, String>> selfMappings = assetInfoMapper.selectOwnerCandidatesByPersonIds(personIds);
|
||||
mergeOwnerMappings(result, selfMappings);
|
||||
|
||||
Set<String> selfMatchedIds = result.keySet();
|
||||
List<String> relationPersonIds = personIds.stream()
|
||||
.filter(personId -> !selfMatchedIds.contains(personId))
|
||||
.toList();
|
||||
if (!relationPersonIds.isEmpty()) {
|
||||
mergeOwnerMappings(result, assetInfoMapper.selectOwnerCandidatesByRelationCertNos(relationPersonIds));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -184,7 +196,7 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
|
||||
|
||||
private void validateExcel(CcdiAssetInfoExcel excel) {
|
||||
if (StringUtils.isEmpty(excel.getPersonId())) {
|
||||
throw new RuntimeException("关系人证件号不能为空");
|
||||
throw new RuntimeException("资产实际持有人证件号不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(excel.getAssetMainType())) {
|
||||
throw new RuntimeException("资产大类不能为空");
|
||||
|
||||
@@ -77,6 +77,17 @@
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="selectOwnerCandidatesByPersonIds" resultType="map">
|
||||
SELECT
|
||||
id_card AS personId,
|
||||
id_card AS familyId
|
||||
FROM ccdi_base_staff
|
||||
WHERE id_card IN
|
||||
<foreach collection="personIds" item="personId" open="(" separator="," close=")">
|
||||
#{personId}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<select id="selectOwnerCandidatesByRelationCertNos" resultType="map">
|
||||
SELECT
|
||||
relation_cert_no AS personId,
|
||||
|
||||
@@ -109,11 +109,11 @@ class CcdiAssetInfoControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void importTemplate_shouldUseRelativeAssetTemplateName() {
|
||||
void importTemplate_shouldUseGenericAssetTemplateName() {
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
controller.importTemplate(null);
|
||||
|
||||
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(null, CcdiAssetInfoExcel.class, "亲属资产信息"));
|
||||
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(null, CcdiAssetInfoExcel.class, "资产信息"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,22 @@ class CcdiAssetInfoImportServiceImplTest {
|
||||
assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importAssetInfoAsync_shouldResolveFamilyIdFromEmployeeIdCardBeforeFamilyRelation() {
|
||||
CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产");
|
||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||
when(assetInfoMapper.selectOwnerCandidatesByPersonIds(List.of("320101199001010011")))
|
||||
.thenReturn(List.of(owner("320101199001010011", "320101199001010011")));
|
||||
|
||||
service.importAssetInfoAsync(List.of(excel), "task-self", "tester");
|
||||
|
||||
ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(assetInfoMapper).insertBatch(captor.capture());
|
||||
assertEquals("320101199001010011", captor.getValue().get(0).getFamilyId());
|
||||
assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
|
||||
verify(assetInfoMapper, never()).selectOwnerCandidatesByRelationCertNos(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importAssetInfoAsync_shouldResolveFamilyIdFromFamilyRelationIdCard() {
|
||||
CcdiAssetInfoExcel excel = buildExcel("320101199201010022", "车辆");
|
||||
@@ -134,7 +150,7 @@ class CcdiAssetInfoImportServiceImplTest {
|
||||
assertEquals(1, failures.size());
|
||||
AssetImportFailureVO failure = (AssetImportFailureVO) failures.get(0);
|
||||
assertEquals("320101199001010099", failure.getPersonId());
|
||||
assertTrue(failure.getErrorMessage().contains("未找到亲属资产归属员工"));
|
||||
assertTrue(failure.getErrorMessage().contains("未找到资产归属员工"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -154,7 +170,7 @@ class CcdiAssetInfoImportServiceImplTest {
|
||||
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(valueOperations).set(eq("import:assetInfo:task-4:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
|
||||
AssetImportFailureVO failure = (AssetImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
|
||||
assertTrue(failure.getErrorMessage().contains("亲属资产归属员工不唯一"));
|
||||
assertTrue(failure.getErrorMessage().contains("资产归属员工不唯一"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -175,7 +191,7 @@ class CcdiAssetInfoImportServiceImplTest {
|
||||
));
|
||||
AssetImportFailureVO failureVO = new AssetImportFailureVO();
|
||||
failureVO.setPersonId("320101199001010099");
|
||||
failureVO.setErrorMessage("未找到亲属资产归属员工");
|
||||
failureVO.setErrorMessage("未找到资产归属员工");
|
||||
when(valueOperations.get("import:assetInfo:task-5:failures")).thenReturn(List.of(failureVO));
|
||||
|
||||
ImportStatusVO statusVO = service.getImportStatus("task-5");
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
# 员工资产导入与亲属资产导入拆分后端实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 将员工资产导入与亲属资产导入拆成两套独立后端接口与服务,确保员工页仅导入本人资产、亲属页仅导入亲属资产。
|
||||
|
||||
**Architecture:** 保留现有亲属资产导入控制器与服务作为“亲属专用导入链路”,新增一套员工资产专用导入控制器、Excel 模型、失败记录 VO 与异步导入服务。两套链路分别使用不同权限、模板和归属匹配规则,不再共享“本人/亲属兜底识别”逻辑。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, Redis, EasyExcel, JUnit 5, Mockito
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 固化拆分后的导入规则测试
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java`
|
||||
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java`
|
||||
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiAssetInfoControllerTest.java`
|
||||
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportControllerTest.java`
|
||||
|
||||
**Step 1: 写亲属资产导入失败测试**
|
||||
|
||||
- 为 `CcdiAssetInfoImportServiceImplTest` 增加用例:
|
||||
- 员工本人身份证号导入时失败
|
||||
- 失败文案为亲属资产专用文案
|
||||
- 为 `CcdiAssetInfoControllerTest` 校验模板标题为“亲属资产信息”
|
||||
|
||||
**Step 2: 运行测试确认当前失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiAssetInfoImportServiceImplTest,CcdiAssetInfoControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 新增断言失败
|
||||
- 失败原因是当前实现仍允许员工本人资产命中
|
||||
|
||||
**Step 3: 写员工资产导入测试**
|
||||
|
||||
- 新增 `CcdiBaseStaffAssetImportServiceImplTest`
|
||||
- 覆盖以下场景:
|
||||
- 员工本人身份证号导入成功
|
||||
- 亲属证件号导入失败
|
||||
- Redis key 使用员工资产独立前缀
|
||||
- 新增 `CcdiBaseStaffAssetImportControllerTest`
|
||||
- 覆盖模板标题、任务创建、状态与失败记录查询
|
||||
|
||||
**Step 4: 再次运行测试确认失败点准确**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiBaseStaffAssetImportServiceImplTest,CcdiBaseStaffAssetImportControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 因类与实现尚不存在而失败
|
||||
|
||||
**Step 5: 提交测试脚手架**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiAssetInfoControllerTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportControllerTest.java
|
||||
git commit -m "补充资产导入拆分后端失败测试"
|
||||
```
|
||||
|
||||
### Task 2: 收敛亲属资产导入为亲属专用链路
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java`
|
||||
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml`
|
||||
|
||||
**Step 1: 最小实现修改**
|
||||
|
||||
- 将 `CcdiAssetInfoImportServiceImpl` 的归属匹配改为只调用 `selectOwnerCandidatesByRelationCertNos`
|
||||
- 删除员工本人兜底逻辑
|
||||
- 恢复亲属资产专用错误文案
|
||||
- 将 Excel 首列表头改为“亲属证件号*”
|
||||
- 将 controller 标题、swagger 文案、日志标题改回亲属资产专用表述
|
||||
- 权限仅保留 `ccdi:staffFmyRelation:import`
|
||||
|
||||
**Step 2: 运行亲属资产相关测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiAssetInfoImportServiceImplTest,CcdiAssetInfoControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 亲属资产测试全部通过
|
||||
|
||||
**Step 3: 检查无多余员工导入逻辑残留**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git grep -n "selectOwnerCandidatesByPersonIds|hasAnyPermi('ccdi:employee:import" -- "ccdi-info-collection/src/main/java/**/*.java" "ccdi-info-collection/src/main/resources/**/*.xml"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `CcdiAssetInfo*` 相关文件不再包含员工资产导入特有逻辑
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml
|
||||
git commit -m "收敛亲属资产导入为亲属专用逻辑"
|
||||
```
|
||||
|
||||
### Task 3: 新增员工资产导入后端接口与模型
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportController.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffAssetImportService.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiBaseStaffAssetInfoExcel.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/BaseStaffAssetImportFailureVO.java`
|
||||
|
||||
**Step 1: 写最小接口定义**
|
||||
|
||||
- 定义员工资产导入 service 接口:
|
||||
- `importAssetInfo`
|
||||
- `importAssetInfoAsync`
|
||||
- `getImportStatus`
|
||||
- `getImportFailures`
|
||||
|
||||
**Step 2: 写 controller 最小实现**
|
||||
|
||||
- 提供以下接口:
|
||||
- `POST /ccdi/baseStaff/asset/importTemplate`
|
||||
- `POST /ccdi/baseStaff/asset/importData`
|
||||
- `GET /ccdi/baseStaff/asset/importStatus/{taskId}`
|
||||
- `GET /ccdi/baseStaff/asset/importFailures/{taskId}`
|
||||
- 权限使用 `ccdi:employee:import`
|
||||
|
||||
**Step 3: 写 Excel 与失败记录模型**
|
||||
|
||||
- `CcdiBaseStaffAssetInfoExcel` 首列使用“员工身份证号*”
|
||||
- `BaseStaffAssetImportFailureVO` 字段与员工资产模板保持一致
|
||||
|
||||
**Step 4: 运行员工资产 controller 测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiBaseStaffAssetImportControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- controller 测试通过
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportController.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffAssetImportService.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiBaseStaffAssetInfoExcel.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/BaseStaffAssetImportFailureVO.java
|
||||
git commit -m "新增员工资产导入后端接口"
|
||||
```
|
||||
|
||||
### Task 4: 实现员工资产归属匹配与异步导入
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java`
|
||||
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml`
|
||||
|
||||
**Step 1: 写最小匹配实现**
|
||||
|
||||
- 在 mapper 中新增 `selectOwnerCandidatesByBaseStaffIdCards`
|
||||
- SQL 只查询 `ccdi_base_staff.id_card`
|
||||
- service 只按员工身份证号匹配
|
||||
- 不查亲属关系表
|
||||
|
||||
**Step 2: 写导入成功逻辑**
|
||||
|
||||
- 复制 Excel 到 `CcdiAssetInfo`
|
||||
- 强制 `familyId = personId = 员工身份证号`
|
||||
- 使用独立 Redis 前缀,例如 `import:baseStaffAsset:`
|
||||
|
||||
**Step 3: 写失败逻辑**
|
||||
|
||||
- 未命中员工表时报错
|
||||
- 失败文案使用员工资产专用文案
|
||||
|
||||
**Step 4: 运行员工资产 service 测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiBaseStaffAssetImportServiceImplTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 员工资产 service 测试通过
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml
|
||||
git commit -m "实现员工资产导入归属匹配"
|
||||
```
|
||||
|
||||
### Task 5: 执行回归验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/plans/2026-03-13-employee-family-asset-import-split-design.md`
|
||||
|
||||
**Step 1: 运行后端定向测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiAssetInfoImportServiceImplTest,CcdiAssetInfoControllerTest,CcdiBaseStaffAssetImportServiceImplTest,CcdiBaseStaffAssetImportControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 相关测试全部通过
|
||||
|
||||
**Step 2: 做源码断言检查**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git grep -n "/ccdi/baseStaff/asset|/ccdi/assetInfo" -- "ccdi-info-collection/src/main/java/**/*.java"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 员工与亲属两套接口都存在
|
||||
- 路由职责清晰
|
||||
|
||||
**Step 3: 更新设计文档的实现状态说明**
|
||||
|
||||
- 在设计文档末尾补充“已完成实现验证”的简短说明
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/plans/2026-03-13-employee-family-asset-import-split-design.md
|
||||
git commit -m "完成资产导入拆分后端验证"
|
||||
```
|
||||
@@ -0,0 +1,241 @@
|
||||
# 员工资产导入与亲属资产导入拆分设计
|
||||
|
||||
## 背景
|
||||
|
||||
当前员工信息维护页 [ccdiBaseStaff/index.vue](/D:/ccdi/ccdi/ruoyi-ui/src/views/ccdiBaseStaff/index.vue) 与员工亲属关系维护页 [ccdiStaffFmyRelation/index.vue](/D:/ccdi/ccdi/ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue) 共用了 `/ccdi/assetInfo/*` 资产导入接口,导致两类业务边界混淆:
|
||||
|
||||
- 员工页的“导入资产信息”应只导入员工本人资产
|
||||
- 亲属页的“导入亲属资产信息”应只导入员工亲属资产
|
||||
- 当前共享接口无法同时满足这两条规则,模板、权限、失败文案也容易串用
|
||||
|
||||
用户已确认:
|
||||
|
||||
- 保留员工页“导入资产信息”按钮
|
||||
- 员工资产导入与亲属资产导入必须彻底拆分
|
||||
- 员工亲属资产导入功能只能导入员工亲属的资产信息,不能更新员工的
|
||||
|
||||
## 目标
|
||||
|
||||
- 将员工资产导入与亲属资产导入拆成两条独立链路
|
||||
- 员工页只支持员工本人资产导入
|
||||
- 亲属页只支持亲属资产导入
|
||||
- 模板、接口、权限、失败提示、任务状态缓存全部分离
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不改造员工资产手工维护功能
|
||||
- 不改造亲属资产手工维护功能
|
||||
- 不新增独立资产菜单页面
|
||||
- 不调整 `ccdi_asset_info` 表结构
|
||||
|
||||
## 现状
|
||||
|
||||
当前共用资产导入能力集中在:
|
||||
|
||||
- 前端 API:[ccdiAssetInfo.js](/D:/ccdi/ccdi/ruoyi-ui/src/api/ccdiAssetInfo.js)
|
||||
- 员工页调用点:[ccdiBaseStaff/index.vue](/D:/ccdi/ccdi/ruoyi-ui/src/views/ccdiBaseStaff/index.vue)
|
||||
- 亲属页调用点:[ccdiStaffFmyRelation/index.vue](/D:/ccdi/ccdi/ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue)
|
||||
- 后端控制器:[CcdiAssetInfoController.java](/D:/ccdi/ccdi/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java)
|
||||
- 后端导入服务:[CcdiAssetInfoImportServiceImpl.java](/D:/ccdi/ccdi/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java)
|
||||
|
||||
问题点:
|
||||
|
||||
- 员工页与亲属页共用同一上传地址 `/ccdi/assetInfo/importData`
|
||||
- 共用同一模板下载地址 `/ccdi/assetInfo/importTemplate`
|
||||
- 共用同一任务状态与失败记录查询入口
|
||||
- 共用同一导入匹配规则,无法表达“员工页仅员工本人、亲属页仅亲属”
|
||||
|
||||
## 方案对比
|
||||
|
||||
### 方案一:员工资产导入、亲属资产导入完全拆分
|
||||
|
||||
- 员工页新增一套独立导入接口
|
||||
- 亲属页保留现有 `/ccdi/assetInfo/*`
|
||||
- 两边各自使用独立模板、权限、校验和失败文案
|
||||
|
||||
优点:
|
||||
|
||||
- 业务边界最清晰
|
||||
- 后续维护风险最低
|
||||
- 模板与前端交互不再串用
|
||||
|
||||
缺点:
|
||||
|
||||
- 需要新增一套员工资产导入 controller/service/api
|
||||
|
||||
### 方案二:继续共用接口,通过 `mode` 区分
|
||||
|
||||
- 前端调用同一接口
|
||||
- 后端通过 `mode=employee/family` 分支处理
|
||||
|
||||
优点:
|
||||
|
||||
- 代码新增较少
|
||||
|
||||
缺点:
|
||||
|
||||
- 控制器和 service 内分支复杂
|
||||
- 模板、权限、提示文案仍容易混淆
|
||||
- 后续扩展时回归风险高
|
||||
|
||||
### 方案三:仅修前端入口文案
|
||||
|
||||
优点:
|
||||
|
||||
- 改动最小
|
||||
|
||||
缺点:
|
||||
|
||||
- 业务问题未解决
|
||||
- 实际导入规则仍然混乱
|
||||
|
||||
## 最终方案
|
||||
|
||||
采用方案一:员工资产导入与亲属资产导入完全拆分。
|
||||
|
||||
### 员工资产导入
|
||||
|
||||
- 页面入口:员工信息维护页
|
||||
- 独立接口:
|
||||
- `POST /ccdi/baseStaff/asset/importTemplate`
|
||||
- `POST /ccdi/baseStaff/asset/importData`
|
||||
- `GET /ccdi/baseStaff/asset/importStatus/{taskId}`
|
||||
- `GET /ccdi/baseStaff/asset/importFailures/{taskId}`
|
||||
- 独立前端 API 文件:`ruoyi-ui/src/api/ccdiBaseStaffAsset.js`
|
||||
- 仅允许导入员工本人资产
|
||||
- 模板第一列要求填写员工身份证号
|
||||
- 导入成功后强制写入:
|
||||
- `family_id = 员工身份证号`
|
||||
- `person_id = 员工身份证号`
|
||||
|
||||
### 亲属资产导入
|
||||
|
||||
- 页面入口:员工亲属关系维护页
|
||||
- 保留现有接口:
|
||||
- `POST /ccdi/assetInfo/importTemplate`
|
||||
- `POST /ccdi/assetInfo/importData`
|
||||
- `GET /ccdi/assetInfo/importStatus/{taskId}`
|
||||
- `GET /ccdi/assetInfo/importFailures/{taskId}`
|
||||
- 仅允许导入员工亲属资产
|
||||
- 模板第一列要求填写亲属证件号
|
||||
- 导入成功后强制写入:
|
||||
- `family_id = 关联员工证件号`
|
||||
- `person_id = 亲属证件号`
|
||||
|
||||
## 后端设计
|
||||
|
||||
### 新增员工资产导入控制面
|
||||
|
||||
新增:
|
||||
|
||||
- `controller/CcdiBaseStaffAssetImportController.java`
|
||||
- `service/ICcdiBaseStaffAssetImportService.java`
|
||||
- `service/impl/CcdiBaseStaffAssetImportServiceImpl.java`
|
||||
- `domain/excel/CcdiBaseStaffAssetInfoExcel.java`
|
||||
- `domain/vo/BaseStaffAssetImportFailureVO.java`
|
||||
|
||||
员工资产导入匹配规则:
|
||||
|
||||
- 仅根据 `ccdi_base_staff.id_card` 匹配
|
||||
- 若未匹配到员工,导入失败
|
||||
- 不再兜底匹配亲属关系表
|
||||
- 若命中员工,则写入 `family_id = person_id = id_card`
|
||||
|
||||
亲属资产导入规则调整:
|
||||
|
||||
- [CcdiAssetInfoImportServiceImpl.java](/D:/ccdi/ccdi/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java) 只保留亲属资产逻辑
|
||||
- 仅根据 `ccdi_staff_fmy_relation.relation_cert_no` 匹配
|
||||
- 不再匹配员工本人身份证号
|
||||
|
||||
### 权限设计
|
||||
|
||||
- 员工资产导入接口:`ccdi:employee:import`
|
||||
- 亲属资产导入接口:`ccdi:staffFmyRelation:import`
|
||||
|
||||
禁止再使用同时兼容两个权限的写法,以免接口语义再次混淆。
|
||||
|
||||
## 前端设计
|
||||
|
||||
### 员工页
|
||||
|
||||
[ccdiBaseStaff/index.vue](/D:/ccdi/ccdi/ruoyi-ui/src/views/ccdiBaseStaff/index.vue) 改为:
|
||||
|
||||
- 上传地址改为员工资产专用接口
|
||||
- 模板下载改为员工资产模板
|
||||
- 任务状态和失败记录查询改为员工资产专用接口
|
||||
- 提示文案明确为“员工资产数据导入”
|
||||
|
||||
### 亲属页
|
||||
|
||||
[ccdiStaffFmyRelation/index.vue](/D:/ccdi/ccdi/ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue) 保持:
|
||||
|
||||
- 上传地址仍为 `/ccdi/assetInfo/importData`
|
||||
- 模板下载仍为亲属资产模板
|
||||
- 文案继续强调“亲属资产”
|
||||
|
||||
## 模板设计
|
||||
|
||||
员工资产模板:
|
||||
|
||||
- 文件名:`员工资产信息模板_xxx.xlsx`
|
||||
- 首列表头:`员工身份证号*`
|
||||
|
||||
亲属资产模板:
|
||||
|
||||
- 文件名:`亲属资产信息模板_xxx.xlsx`
|
||||
- 首列表头:`亲属证件号*`
|
||||
|
||||
## 校验规则
|
||||
|
||||
### 员工资产导入
|
||||
|
||||
- 员工身份证号不能为空
|
||||
- 资产必填字段不能为空
|
||||
- 员工身份证号必须存在于 `ccdi_base_staff.id_card`
|
||||
- 若填写的是亲属证件号或其他未命中的证件号,直接失败
|
||||
|
||||
建议失败文案:
|
||||
|
||||
- `未找到员工资产归属员工`
|
||||
- 或更明确的 `员工资产导入仅支持员工本人证件号`
|
||||
|
||||
### 亲属资产导入
|
||||
|
||||
- 亲属证件号不能为空
|
||||
- 资产必填字段不能为空
|
||||
- 亲属证件号必须存在于 `ccdi_staff_fmy_relation.relation_cert_no`
|
||||
- 若填写员工本人身份证号且不存在亲属关系映射,直接失败
|
||||
- 若同一亲属证件号匹配多个员工关系,失败并提示归属不唯一
|
||||
|
||||
## 测试要求
|
||||
|
||||
后端:
|
||||
|
||||
- 员工资产导入:员工本人证件号成功
|
||||
- 员工资产导入:亲属证件号失败
|
||||
- 亲属资产导入:亲属证件号成功
|
||||
- 亲属资产导入:员工本人证件号失败
|
||||
- 两套模板标题和首列表头不同
|
||||
- 两套接口权限分别正确
|
||||
|
||||
前端:
|
||||
|
||||
- 员工页使用员工资产专用上传地址
|
||||
- 亲属页继续使用 `/ccdi/assetInfo/*`
|
||||
- 员工页下载员工资产模板
|
||||
- 亲属页下载亲属资产模板
|
||||
- 员工页和亲属页的失败记录弹窗文案不混淆
|
||||
|
||||
## 风险与回滚
|
||||
|
||||
风险:
|
||||
|
||||
- 当前仓库内已有共享资产导入代码,拆分时容易遗漏某一处调用
|
||||
- 若只拆后端不拆前端,页面会继续指向旧接口
|
||||
- 若模板文案未同步,用户仍可能误用模板
|
||||
|
||||
回滚策略:
|
||||
|
||||
- 独立提交员工资产导入新增改动
|
||||
- 独立提交亲属资产导入收敛改动
|
||||
- 任一阶段出现回归,可按提交粒度回退
|
||||
@@ -0,0 +1,230 @@
|
||||
# 员工资产导入与亲属资产导入拆分前端实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 将员工页与亲属页的资产导入前端交互彻底拆开,确保员工页只走员工资产导入接口,亲属页只走亲属资产导入接口。
|
||||
|
||||
**Architecture:** 保留亲属页现有资产导入状态管理,新增员工资产专用 API 封装并改造员工页上传地址、模板下载、状态轮询与失败记录查询。两边继续复用现有异步导入交互样式,但数据源和文案完全隔离。
|
||||
|
||||
**Tech Stack:** Vue 2, Element UI, Axios request wrapper, Node 静态契约测试
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 固化前端拆分契约测试
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/tests/unit/employee-asset-api-contract.test.js`
|
||||
- Modify: `ruoyi-ui/tests/unit/employee-asset-import-ui.test.js`
|
||||
- Modify: `ruoyi-ui/tests/unit/staff-family-asset-api-contract.test.js`
|
||||
- Modify: `ruoyi-ui/tests/unit/staff-family-asset-detail-import-ui.test.js`
|
||||
|
||||
**Step 1: 写员工页失败测试**
|
||||
|
||||
- 断言员工页不再引用 `/ccdi/assetInfo/importData`
|
||||
- 断言员工页引用新的员工资产 API 文件或路由
|
||||
- 断言模板文案为“员工资产模板”
|
||||
|
||||
**Step 2: 运行员工页静态测试确认失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/unit/employee-asset-api-contract.test.js
|
||||
node tests/unit/employee-asset-import-ui.test.js
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 至少一个断言失败
|
||||
- 失败原因是员工页仍指向旧接口
|
||||
|
||||
**Step 3: 写亲属页保护性测试**
|
||||
|
||||
- 断言亲属页继续使用 `/ccdi/assetInfo/*`
|
||||
- 断言亲属页模板文案仍为“亲属资产”
|
||||
|
||||
**Step 4: 运行亲属页测试确认当前仍通过**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/unit/staff-family-asset-api-contract.test.js
|
||||
node tests/unit/staff-family-asset-detail-import-ui.test.js
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 现有亲属页测试通过
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/tests/unit/employee-asset-api-contract.test.js ruoyi-ui/tests/unit/employee-asset-import-ui.test.js ruoyi-ui/tests/unit/staff-family-asset-api-contract.test.js ruoyi-ui/tests/unit/staff-family-asset-detail-import-ui.test.js
|
||||
git commit -m "补充资产导入拆分前端失败测试"
|
||||
```
|
||||
|
||||
### Task 2: 新增员工资产导入 API 封装
|
||||
|
||||
**Files:**
|
||||
- Create: `ruoyi-ui/src/api/ccdiBaseStaffAsset.js`
|
||||
- Modify: `ruoyi-ui/tests/unit/employee-asset-api-contract.test.js`
|
||||
|
||||
**Step 1: 写最小 API 文件**
|
||||
|
||||
导出以下方法:
|
||||
|
||||
```javascript
|
||||
export function importBaseStaffAssetTemplate() {}
|
||||
export function importBaseStaffAssetData(data) {}
|
||||
export function getBaseStaffAssetImportStatus(taskId) {}
|
||||
export function getBaseStaffAssetImportFailures(taskId, pageNum, pageSize) {}
|
||||
```
|
||||
|
||||
**Step 2: 接入员工专用路由**
|
||||
|
||||
- `/ccdi/baseStaff/asset/importTemplate`
|
||||
- `/ccdi/baseStaff/asset/importData`
|
||||
- `/ccdi/baseStaff/asset/importStatus/`
|
||||
- `/ccdi/baseStaff/asset/importFailures/`
|
||||
|
||||
**Step 3: 运行员工资产 API 静态测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/unit/employee-asset-api-contract.test.js
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- API 契约测试通过
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/api/ccdiBaseStaffAsset.js ruoyi-ui/tests/unit/employee-asset-api-contract.test.js
|
||||
git commit -m "新增员工资产导入前端接口"
|
||||
```
|
||||
|
||||
### Task 3: 改造员工页导入交互
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiBaseStaff/index.vue`
|
||||
- Modify: `ruoyi-ui/tests/unit/employee-asset-import-ui.test.js`
|
||||
|
||||
**Step 1: 替换员工页上传地址**
|
||||
|
||||
- `assetUpload.url` 改为 `/ccdi/baseStaff/asset/importData`
|
||||
- 模板下载改为 `/ccdi/baseStaff/asset/importTemplate`
|
||||
- 状态查询与失败记录改为员工资产专用 API
|
||||
|
||||
**Step 2: 保持状态隔离**
|
||||
|
||||
- 保留 `assetUpload`、`assetPollingTimer`、`assetCurrentTaskId`
|
||||
- 仅替换其数据来源
|
||||
- 不改动普通员工导入状态
|
||||
|
||||
**Step 3: 调整员工页提示文案**
|
||||
|
||||
- 提示用户仅支持员工本人资产导入
|
||||
- 下载链接文字明确为“下载员工资产模板”
|
||||
|
||||
**Step 4: 运行员工页静态测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/unit/employee-asset-import-ui.test.js
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 测试通过
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiBaseStaff/index.vue ruoyi-ui/tests/unit/employee-asset-import-ui.test.js
|
||||
git commit -m "切换员工页资产导入到专用接口"
|
||||
```
|
||||
|
||||
### Task 4: 保护亲属页导入交互不回归
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue`
|
||||
- Modify: `ruoyi-ui/tests/unit/staff-family-asset-api-contract.test.js`
|
||||
- Modify: `ruoyi-ui/tests/unit/staff-family-asset-detail-import-ui.test.js`
|
||||
|
||||
**Step 1: 检查亲属页调用**
|
||||
|
||||
- 确认亲属页仍使用 `/ccdi/assetInfo/importData`
|
||||
- 确认模板下载仍使用 `/ccdi/assetInfo/importTemplate`
|
||||
- 确认提示文案仍强调“亲属资产”
|
||||
|
||||
**Step 2: 如有必要补充只读修正**
|
||||
|
||||
- 若之前误改了亲属页文案或下载文件名,恢复为亲属专用表达
|
||||
|
||||
**Step 3: 运行亲属页静态测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/unit/staff-family-asset-api-contract.test.js
|
||||
node tests/unit/staff-family-asset-detail-import-ui.test.js
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 测试通过
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue ruoyi-ui/tests/unit/staff-family-asset-api-contract.test.js ruoyi-ui/tests/unit/staff-family-asset-detail-import-ui.test.js
|
||||
git commit -m "保护亲属页资产导入交互不回归"
|
||||
```
|
||||
|
||||
### Task 5: 执行前端回归验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/plans/2026-03-13-employee-family-asset-import-split-design.md`
|
||||
|
||||
**Step 1: 运行全部相关静态测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/unit/employee-asset-api-contract.test.js
|
||||
node tests/unit/employee-asset-import-ui.test.js
|
||||
node tests/unit/staff-family-asset-api-contract.test.js
|
||||
node tests/unit/staff-family-asset-detail-import-ui.test.js
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 四个测试全部通过
|
||||
|
||||
**Step 2: 做源码检查**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git grep -n "/ccdi/baseStaff/asset|/ccdi/assetInfo" -- "ruoyi-ui/src/views/**/*.vue" "ruoyi-ui/src/api/*.js"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 员工页仅指向 `/ccdi/baseStaff/asset/*`
|
||||
- 亲属页仅指向 `/ccdi/assetInfo/*`
|
||||
|
||||
**Step 3: 更新设计文档实现状态**
|
||||
|
||||
- 在设计文档末尾补充前端已完成拆分验证说明
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/plans/2026-03-13-employee-family-asset-import-split-design.md
|
||||
git commit -m "完成资产导入拆分前端验证"
|
||||
```
|
||||
Reference in New Issue
Block a user