新增员工资产信息后端实施计划

This commit is contained in:
wkc
2026-03-12 16:33:07 +08:00
parent 606aab6bb4
commit bac3cf094e
22 changed files with 1825 additions and 2 deletions

View File

@@ -0,0 +1,175 @@
package com.ruoyi.info.collection.mapper;
import com.ruoyi.info.collection.domain.CcdiAssetInfo;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.ibatis.type.TypeAliasRegistry;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CcdiAssetInfoMapperTest {
private static final String RESOURCE = "mapper/info/collection/CcdiAssetInfoMapper.xml";
@Test
void selectByFamilyId_shouldFilterByFamilyId() throws Exception {
MappedStatement mappedStatement = loadMappedStatement(
"com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.selectByFamilyId");
String sql = renderSql(mappedStatement, Map.of("familyId", "320101199001010011"));
assertTrue(sql.contains("FROM ccdi_asset_info"), sql);
assertTrue(sql.contains("WHERE family_id = ?"), sql);
}
@Test
void selectByPersonId_shouldFilterByPersonId() throws Exception {
MappedStatement mappedStatement = loadMappedStatement(
"com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.selectByPersonId");
String sql = renderSql(mappedStatement, Map.of("personId", "320101199201010022"));
assertTrue(sql.contains("FROM ccdi_asset_info"), sql);
assertTrue(sql.contains("WHERE person_id = ?"), sql);
}
@Test
void deleteByFamilyIds_shouldRenderInClause() throws Exception {
MappedStatement mappedStatement = loadMappedStatement(
"com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.deleteByFamilyIds");
String sql = renderSql(mappedStatement, Map.of("familyIds", List.of("A", "B")));
assertTrue(sql.contains("DELETE FROM ccdi_asset_info"), sql);
assertTrue(sql.contains("family_id IN"), sql);
assertFalse(sql.contains("IN ()"), sql);
}
@Test
void insertBatch_shouldIncludeAllBusinessColumns() throws Exception {
MappedStatement mappedStatement = loadMappedStatement(
"com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.insertBatch");
CcdiAssetInfo assetInfo = new CcdiAssetInfo();
assetInfo.setFamilyId("320101199001010011");
assetInfo.setPersonId("320101199201010022");
assetInfo.setAssetMainType("车辆");
assetInfo.setAssetSubType("小汽车");
assetInfo.setAssetName("家庭车辆");
assetInfo.setCurrentValue(new BigDecimal("100000.00"));
assetInfo.setAssetStatus("正常");
String sql = renderSql(mappedStatement, Map.of("list", List.of(assetInfo)));
assertTrue(sql.contains("INSERT INTO ccdi_asset_info"), sql);
assertTrue(sql.contains("family_id"), sql);
assertTrue(sql.contains("person_id"), sql);
assertTrue(sql.contains("asset_main_type"), sql);
assertTrue(sql.contains("current_value"), sql);
assertTrue(sql.contains("asset_status"), sql);
}
@Test
void ownerLookupQueries_shouldResolveFromEmployeeAndFamilyRelation() throws Exception {
MappedStatement employeeStatement = loadMappedStatement(
"com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.selectOwnerByEmployeeIdCards");
MappedStatement familyStatement = loadMappedStatement(
"com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper.selectOwnerByFamilyRelationIdCards");
String employeeSql = renderSql(employeeStatement, Map.of("personIds", List.of("A")));
String familySql = renderSql(familyStatement, Map.of("personIds", List.of("B")));
assertTrue(employeeSql.contains("FROM ccdi_base_staff"), employeeSql);
assertTrue(employeeSql.contains("id_card AS personId"), employeeSql);
assertTrue(employeeSql.contains("id_card AS familyId"), employeeSql);
assertTrue(familySql.contains("FROM ccdi_staff_fmy_relation"), familySql);
assertTrue(familySql.contains("relation_cert_no AS personId"), familySql);
assertTrue(familySql.contains("person_id AS familyId"), familySql);
assertTrue(familySql.contains("is_emp_family = 1"), familySql);
}
private MappedStatement loadMappedStatement(String statementId) throws Exception {
Configuration configuration = new Configuration();
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));
registerTypeAliases(configuration.getTypeAliasRegistry());
configuration.getLanguageRegistry().register(XMLLanguageDriver.class);
configuration.addMapper(CcdiAssetInfoMapper.class);
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
XMLMapperBuilder xmlMapperBuilder =
new XMLMapperBuilder(inputStream, configuration, RESOURCE, configuration.getSqlFragments());
xmlMapperBuilder.parse();
}
return configuration.getMappedStatement(statementId);
}
private String renderSql(MappedStatement mappedStatement, Map<String, Object> params) {
BoundSql boundSql = mappedStatement.getBoundSql(new HashMap<>(params));
return boundSql.getSql().replaceAll("\\s+", " ").trim();
}
private void registerTypeAliases(TypeAliasRegistry typeAliasRegistry) {
typeAliasRegistry.registerAlias("map", Map.class);
}
private static class NoOpDataSource implements DataSource {
@Override
public java.sql.Connection getConnection() {
throw new UnsupportedOperationException("Not required for SQL rendering tests");
}
@Override
public java.sql.Connection getConnection(String username, String password) {
throw new UnsupportedOperationException("Not required for SQL rendering tests");
}
@Override
public java.io.PrintWriter getLogWriter() {
return null;
}
@Override
public void setLogWriter(java.io.PrintWriter out) {
}
@Override
public void setLoginTimeout(int seconds) {
}
@Override
public int getLoginTimeout() {
return 0;
}
@Override
public java.util.logging.Logger getParentLogger() {
return java.util.logging.Logger.getGlobal();
}
@Override
public <T> T unwrap(Class<T> iface) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return false;
}
}
}

View File

@@ -0,0 +1,213 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiAssetInfo;
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
import com.ruoyi.info.collection.domain.vo.AssetImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper;
import com.ruoyi.info.collection.service.impl.CcdiAssetInfoImportServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiAssetInfoImportServiceImplTest {
@InjectMocks
private CcdiAssetInfoImportServiceImpl service;
@Mock
private CcdiAssetInfoMapper assetInfoMapper;
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Mock
private ICcdiAssetInfoImportService assetInfoImportService;
@Mock
private HashOperations<String, Object, Object> hashOperations;
@Mock
private ValueOperations<String, Object> valueOperations;
@Test
void assetInfoExcel_shouldExcludeAssetIdAndFamilyId() {
Set<String> fieldNames = Arrays.stream(CcdiAssetInfoExcel.class.getDeclaredFields())
.map(field -> field.getName())
.collect(Collectors.toSet());
assertFalse(fieldNames.contains("assetId"));
assertFalse(fieldNames.contains("familyId"));
assertTrue(fieldNames.contains("personId"));
}
@Test
void importAssetInfo_shouldUseDedicatedAssetTaskKeys() {
List<CcdiAssetInfoExcel> excelList = List.of(buildExcel("320101199001010011", "房产"));
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
String taskId = service.importAssetInfo(excelList);
verify(hashOperations).putAll(eq("import:assetInfo:" + taskId), anyMap());
verify(redisTemplate).expire("import:assetInfo:" + taskId, 7, TimeUnit.DAYS);
verify(assetInfoImportService).importAssetInfoAsync(eq(excelList), eq(taskId), any());
}
@Test
void importAssetInfoAsync_shouldResolveFamilyIdFromEmployeeIdCard() {
CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产");
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(assetInfoMapper.selectOwnerByEmployeeIdCards(List.of("320101199001010011")))
.thenReturn(List.of(owner("320101199001010011", "320101199001010011")));
when(assetInfoMapper.selectOwnerByFamilyRelationIdCards(List.of("320101199001010011")))
.thenReturn(List.of());
service.importAssetInfoAsync(List.of(excel), "task-1", "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());
}
@Test
void importAssetInfoAsync_shouldResolveFamilyIdFromFamilyRelationIdCard() {
CcdiAssetInfoExcel excel = buildExcel("320101199201010022", "车辆");
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(assetInfoMapper.selectOwnerByEmployeeIdCards(List.of("320101199201010022")))
.thenReturn(List.of());
when(assetInfoMapper.selectOwnerByFamilyRelationIdCards(List.of("320101199201010022")))
.thenReturn(List.of(owner("320101199201010022", "320101199001010011")));
service.importAssetInfoAsync(List.of(excel), "task-2", "tester");
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());
}
@Test
void importAssetInfoAsync_shouldStoreFailureRowsOnlyForBadRecords() {
CcdiAssetInfoExcel good = buildExcel("320101199001010011", "房产");
CcdiAssetInfoExcel bad = buildExcel("320101199001010099", "车辆");
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(assetInfoMapper.selectOwnerByEmployeeIdCards(List.of("320101199001010011", "320101199001010099")))
.thenReturn(List.of(owner("320101199001010011", "320101199001010011")));
when(assetInfoMapper.selectOwnerByFamilyRelationIdCards(List.of("320101199001010011", "320101199001010099")))
.thenReturn(List.of());
service.importAssetInfoAsync(List.of(good, bad), "task-3", "tester");
ArgumentCaptor<List<CcdiAssetInfo>> insertCaptor = ArgumentCaptor.forClass(List.class);
verify(assetInfoMapper).insertBatch(insertCaptor.capture());
assertEquals(1, insertCaptor.getValue().size());
assertEquals("320101199001010011", insertCaptor.getValue().get(0).getFamilyId());
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
verify(valueOperations).set(eq("import:assetInfo:task-3:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
List<?> failures = (List<?>) failureCaptor.getValue();
assertEquals(1, failures.size());
AssetImportFailureVO failure = (AssetImportFailureVO) failures.get(0);
assertEquals("320101199001010099", failure.getPersonId());
assertTrue(failure.getErrorMessage().contains("未找到资产归属员工"));
}
@Test
void importAssetInfoAsync_shouldFailWhenOwnerIsAmbiguous() {
CcdiAssetInfoExcel excel = buildExcel("320101199201010022", "车辆");
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(assetInfoMapper.selectOwnerByEmployeeIdCards(List.of("320101199201010022")))
.thenReturn(List.of(
owner("320101199201010022", "320101199001010011"),
owner("320101199201010022", "320101199001010033")
));
when(assetInfoMapper.selectOwnerByFamilyRelationIdCards(List.of("320101199201010022")))
.thenReturn(List.of());
service.importAssetInfoAsync(List.of(excel), "task-4", "tester");
verify(assetInfoMapper, never()).insertBatch(any());
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("资产归属员工不唯一"));
}
@Test
void getImportStatusAndFailures_shouldUseAssetPrefixes() {
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(redisTemplate.hasKey("import:assetInfo:task-5")).thenReturn(true);
when(hashOperations.entries("import:assetInfo:task-5")).thenReturn(Map.of(
"taskId", "task-5",
"status", "SUCCESS",
"totalCount", 1,
"successCount", 1,
"failureCount", 0,
"progress", 100,
"startTime", 1L,
"endTime", 2L,
"message", "全部成功"
));
AssetImportFailureVO failureVO = new AssetImportFailureVO();
failureVO.setPersonId("320101199001010099");
failureVO.setErrorMessage("未找到资产归属员工");
when(valueOperations.get("import:assetInfo:task-5:failures")).thenReturn(List.of(failureVO));
ImportStatusVO statusVO = service.getImportStatus("task-5");
List<AssetImportFailureVO> failures = service.getImportFailures("task-5");
assertEquals("task-5", statusVO.getTaskId());
assertEquals("SUCCESS", statusVO.getStatus());
assertNotNull(failures);
assertEquals(1, failures.size());
assertEquals("320101199001010099", failures.get(0).getPersonId());
}
private CcdiAssetInfoExcel buildExcel(String personId, String assetMainType) {
CcdiAssetInfoExcel excel = new CcdiAssetInfoExcel();
excel.setPersonId(personId);
excel.setAssetMainType(assetMainType);
excel.setAssetSubType(assetMainType + "小类");
excel.setAssetName(assetMainType + "名称");
excel.setCurrentValue(new BigDecimal("100.00"));
excel.setAssetStatus("正常");
return excel;
}
private Map<String, String> owner(String personId, String familyId) {
return Map.of("personId", personId, "familyId", familyId);
}
}

View File

@@ -0,0 +1,105 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiAssetInfo;
import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO;
import com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper;
import com.ruoyi.info.collection.service.impl.CcdiAssetInfoServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiAssetInfoServiceImplTest {
@InjectMocks
private CcdiAssetInfoServiceImpl service;
@Mock
private CcdiAssetInfoMapper assetInfoMapper;
@Test
void selectByFamilyId_shouldReturnMapperResult() {
List<CcdiAssetInfo> expected = List.of(new CcdiAssetInfo());
when(assetInfoMapper.selectByFamilyId("320101199001010011")).thenReturn(expected);
List<CcdiAssetInfo> result = service.selectByFamilyId("320101199001010011");
assertSame(expected, result);
}
@Test
void replaceByFamilyId_shouldDeleteThenInsertNormalizedRows() {
CcdiAssetInfoDTO selfOwnedAsset = buildDto("320101199001010011", "房产");
CcdiAssetInfoDTO familyOwnedAsset = buildDto("320101199201010022", "车辆");
service.replaceByFamilyId("320101199001010011", List.of(selfOwnedAsset, familyOwnedAsset));
verify(assetInfoMapper).deleteByFamilyId("320101199001010011");
ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(assetInfoMapper).insertBatch(captor.capture());
List<CcdiAssetInfo> savedList = captor.getValue();
assertEquals(2, savedList.size());
assertEquals("320101199001010011", savedList.get(0).getFamilyId());
assertEquals("320101199001010011", savedList.get(0).getPersonId());
assertEquals("320101199001010011", savedList.get(1).getFamilyId());
assertEquals("320101199201010022", savedList.get(1).getPersonId());
assertEquals("房产", savedList.get(0).getAssetMainType());
assertEquals("车辆", savedList.get(1).getAssetMainType());
}
@Test
void replaceByFamilyId_shouldIgnoreEmptyRows() {
CcdiAssetInfoDTO emptyRow = new CcdiAssetInfoDTO();
service.replaceByFamilyId("320101199001010011", List.of(emptyRow));
verify(assetInfoMapper).deleteByFamilyId("320101199001010011");
verify(assetInfoMapper, never()).insertBatch(anyList());
}
@Test
void deleteByFamilyId_shouldDelegateToMapper() {
when(assetInfoMapper.deleteByFamilyId("320101199001010011")).thenReturn(1);
int result = service.deleteByFamilyId("320101199001010011");
assertEquals(1, result);
}
@Test
void deleteByFamilyIds_shouldDelegateToMapper() {
List<String> familyIds = List.of("320101199001010011", "320101199001010033");
when(assetInfoMapper.deleteByFamilyIds(familyIds)).thenReturn(2);
int result = service.deleteByFamilyIds(familyIds);
assertEquals(2, result);
verify(assetInfoMapper).deleteByFamilyIds(eq(familyIds));
}
private CcdiAssetInfoDTO buildDto(String personId, String assetMainType) {
CcdiAssetInfoDTO dto = new CcdiAssetInfoDTO();
dto.setPersonId(personId);
dto.setAssetMainType(assetMainType);
dto.setAssetSubType(assetMainType + "小类");
dto.setAssetName(assetMainType + "名称");
dto.setCurrentValue(new BigDecimal("100.00"));
dto.setAssetStatus("正常");
return dto;
}
}

View File

@@ -0,0 +1,45 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertSame;
class CcdiBaseStaffServiceAssetAggregationTest {
@Test
void addDto_shouldExposeAssetInfoList() {
CcdiBaseStaffAddDTO addDTO = new CcdiBaseStaffAddDTO();
List<CcdiAssetInfoDTO> assetInfoList = List.of(new CcdiAssetInfoDTO());
addDTO.setAssetInfoList(assetInfoList);
assertSame(assetInfoList, addDTO.getAssetInfoList());
}
@Test
void editDto_shouldExposeAssetInfoList() {
CcdiBaseStaffEditDTO editDTO = new CcdiBaseStaffEditDTO();
List<CcdiAssetInfoDTO> assetInfoList = List.of(new CcdiAssetInfoDTO());
editDTO.setAssetInfoList(assetInfoList);
assertSame(assetInfoList, editDTO.getAssetInfoList());
}
@Test
void staffVo_shouldExposeAssetInfoList() {
CcdiBaseStaffVO staffVO = new CcdiBaseStaffVO();
List<CcdiAssetInfoVO> assetInfoList = List.of(new CcdiAssetInfoVO());
staffVO.setAssetInfoList(assetInfoList);
assertSame(assetInfoList, staffVO.getAssetInfoList());
}
}

View File

@@ -0,0 +1,185 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.CcdiAssetInfo;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.dto.CcdiAssetInfoDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.service.impl.CcdiBaseStaffServiceImpl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.RedisTemplate;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class CcdiBaseStaffServiceImplTest {
@InjectMocks
private CcdiBaseStaffServiceImpl service;
@Mock
private CcdiBaseStaffMapper baseStaffMapper;
@Mock
private ICcdiBaseStaffImportService importAsyncService;
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Mock
private ICcdiAssetInfoService assetInfoService;
@Test
void insertBaseStaff_shouldPersistEmployeeThenReplaceAssetsUsingEmployeeIdCard() {
CcdiBaseStaffAddDTO addDTO = new CcdiBaseStaffAddDTO();
addDTO.setStaffId(1001L);
addDTO.setName("张三");
addDTO.setDeptId(10L);
addDTO.setIdCard("320101199001010011");
addDTO.setPhone("13812345678");
addDTO.setStatus("0");
addDTO.setAssetInfoList(List.of(
buildAssetDto("320101199001010011", "房产"),
buildAssetDto("320101199201010022", "车辆")
));
when(baseStaffMapper.selectById(1001L)).thenReturn(null);
when(baseStaffMapper.selectCount(any())).thenReturn(0L);
when(baseStaffMapper.insert(any(CcdiBaseStaff.class))).thenReturn(1);
int result = service.insertBaseStaff(addDTO);
assertEquals(1, result);
ArgumentCaptor<List<CcdiAssetInfoDTO>> captor = ArgumentCaptor.forClass(List.class);
verify(assetInfoService).replaceByFamilyId(eq("320101199001010011"), captor.capture());
List<CcdiAssetInfoDTO> savedAssets = captor.getValue();
assertEquals(2, savedAssets.size());
assertEquals("320101199001010011", savedAssets.get(0).getPersonId());
assertEquals("320101199201010022", savedAssets.get(1).getPersonId());
}
@Test
void updateBaseStaff_shouldReplaceAssetsForCurrentIdCard() {
CcdiBaseStaff existing = new CcdiBaseStaff();
existing.setStaffId(1001L);
existing.setIdCard("320101199001010011");
CcdiBaseStaffEditDTO editDTO = new CcdiBaseStaffEditDTO();
editDTO.setStaffId(1001L);
editDTO.setDeptId(10L);
editDTO.setName("张三");
editDTO.setIdCard("320101199001010011");
editDTO.setPhone("13812345678");
editDTO.setStatus("0");
editDTO.setAssetInfoList(List.of(buildAssetDto("320101199201010022", "车辆")));
when(baseStaffMapper.selectById(1001L)).thenReturn(existing);
when(baseStaffMapper.selectCount(any())).thenReturn(0L);
when(baseStaffMapper.updateById(any(CcdiBaseStaff.class))).thenReturn(1);
int result = service.updateBaseStaff(editDTO);
assertEquals(1, result);
verify(assetInfoService, never()).deleteByFamilyId("320101199001010011");
verify(assetInfoService).replaceByFamilyId("320101199001010011", editDTO.getAssetInfoList());
}
@Test
void updateBaseStaff_shouldDeleteOldAssetsWhenIdCardChanges() {
CcdiBaseStaff existing = new CcdiBaseStaff();
existing.setStaffId(1001L);
existing.setIdCard("320101199001010099");
CcdiBaseStaffEditDTO editDTO = new CcdiBaseStaffEditDTO();
editDTO.setStaffId(1001L);
editDTO.setDeptId(10L);
editDTO.setName("张三");
editDTO.setIdCard("320101199001010011");
editDTO.setPhone("13812345678");
editDTO.setStatus("0");
editDTO.setAssetInfoList(List.of(buildAssetDto("320101199201010022", "车辆")));
when(baseStaffMapper.selectById(1001L)).thenReturn(existing);
when(baseStaffMapper.selectCount(any())).thenReturn(0L);
when(baseStaffMapper.updateById(any(CcdiBaseStaff.class))).thenReturn(1);
service.updateBaseStaff(editDTO);
verify(assetInfoService).deleteByFamilyId("320101199001010099");
verify(assetInfoService).replaceByFamilyId("320101199001010011", editDTO.getAssetInfoList());
}
@Test
void selectBaseStaffById_shouldReturnAssetInfoList() {
CcdiBaseStaff staff = new CcdiBaseStaff();
staff.setStaffId(1001L);
staff.setName("张三");
staff.setIdCard("320101199001010011");
staff.setStatus("0");
CcdiAssetInfo assetInfo = new CcdiAssetInfo();
assetInfo.setFamilyId("320101199001010011");
assetInfo.setPersonId("320101199201010022");
assetInfo.setAssetMainType("车辆");
assetInfo.setAssetSubType("小汽车");
assetInfo.setAssetName("家庭车辆");
assetInfo.setCurrentValue(new BigDecimal("100000.00"));
assetInfo.setAssetStatus("正常");
when(baseStaffMapper.selectById(1001L)).thenReturn(staff);
when(assetInfoService.selectByFamilyId("320101199001010011")).thenReturn(List.of(assetInfo));
CcdiBaseStaffVO result = service.selectBaseStaffById(1001L);
assertNotNull(result.getAssetInfoList());
assertEquals(1, result.getAssetInfoList().size());
assertEquals("320101199201010022", result.getAssetInfoList().get(0).getPersonId());
assertEquals("车辆", result.getAssetInfoList().get(0).getAssetMainType());
}
@Test
void deleteBaseStaffByIds_shouldCascadeDeleteAssets() {
CcdiBaseStaff staff1 = new CcdiBaseStaff();
staff1.setStaffId(1001L);
staff1.setIdCard("320101199001010011");
CcdiBaseStaff staff2 = new CcdiBaseStaff();
staff2.setStaffId(1002L);
staff2.setIdCard("320101199001010022");
when(baseStaffMapper.selectBatchIds(List.of(1001L, 1002L))).thenReturn(List.of(staff1, staff2));
when(baseStaffMapper.deleteBatchIds(List.of(1001L, 1002L))).thenReturn(2);
int result = service.deleteBaseStaffByIds(new Long[]{1001L, 1002L});
assertEquals(2, result);
verify(assetInfoService).deleteByFamilyIds(List.of("320101199001010011", "320101199001010022"));
}
private CcdiAssetInfoDTO buildAssetDto(String personId, String assetMainType) {
CcdiAssetInfoDTO dto = new CcdiAssetInfoDTO();
dto.setPersonId(personId);
dto.setAssetMainType(assetMainType);
dto.setAssetSubType(assetMainType + "小类");
dto.setAssetName(assetMainType + "名称");
dto.setCurrentValue(new BigDecimal("100.00"));
dto.setAssetStatus("正常");
return dto;
}
}