Update import templates and relation query fields

This commit is contained in:
wkc
2026-05-06 23:37:32 +08:00
parent 75cb8967da
commit 5980ed0790
21 changed files with 350 additions and 80 deletions

View File

@@ -33,6 +33,10 @@ public class CcdiCustFmyRelationQueryDTO implements Serializable {
@Schema(description = "关系人姓名")
private String relationName;
/** 关系人身份证号 */
@Schema(description = "关系人身份证号")
private String relationCertNo;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;

View File

@@ -37,6 +37,10 @@ public class CcdiStaffFmyRelationQueryDTO implements Serializable {
@Schema(description = "关系人姓名")
private String relationName;
/** 关系人身份证号 */
@Schema(description = "关系人身份证号")
private String relationCertNo;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;

View File

@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.Required;
import lombok.Data;
import java.io.Serial;
@@ -17,17 +18,19 @@ public class CcdiIntermediaryEnterpriseRelationExcel implements Serializable {
private static final long serialVersionUID = 1L;
/** 中介本人证件号码 */
@ExcelProperty(value = "中介本人证件号码*", index = 0)
@ExcelProperty(value = "中介本人证件号码", index = 0)
@ColumnWidth(24)
@Required
private String ownerPersonId;
/** 统一社会信用代码 */
@ExcelProperty(value = "统一社会信用代码*", index = 1)
@ExcelProperty(value = "统一社会信用代码", index = 1)
@ColumnWidth(24)
@Required
private String socialCreditCode;
/** 关联职务 */
@ExcelProperty(value = "关联职务", index = 2)
/** 关联职务 */
@ExcelProperty(value = "关联职务", index = 2)
@ColumnWidth(20)
private String relationPersonPost;

View File

@@ -21,8 +21,8 @@ public class CcdiStaffRecruitmentExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 招聘项目编号 */
@ExcelProperty(value = "招聘项目编号", index = 0)
/** 招聘记录编号 */
@ExcelProperty(value = "招聘记录编号", index = 0)
@ColumnWidth(20)
@Required
private String recruitId;
@@ -51,66 +51,72 @@ public class CcdiStaffRecruitmentExcel implements Serializable {
@Required
private String posDesc;
/** 应聘人员姓名 */
@ExcelProperty(value = "应聘人员姓名", index = 5)
@ColumnWidth(15)
@Required
private String candName;
/** 应聘人员学历 */
@ExcelProperty(value = "应聘人员学历", index = 6)
@ColumnWidth(15)
@Required
private String candEdu;
/** 应聘人员证件号码 */
@ExcelProperty(value = "应聘人员证件号码", index = 7)
@ColumnWidth(20)
@Required
private String candId;
/** 应聘人员毕业院校 */
@ExcelProperty(value = "应聘人员毕业院校", index = 8)
@ColumnWidth(20)
@Required
private String candSchool;
/** 应聘人员专业 */
@ExcelProperty(value = "应聘人员专业", index = 9)
@ColumnWidth(15)
@Required
private String candMajor;
/** 应聘人员毕业年月 */
@ExcelProperty(value = "应聘人员毕业年月", index = 10)
@ColumnWidth(15)
@Required
private String candGrad;
/** 录用情况 */
@ExcelProperty(value = "录用情况", index = 11)
@ExcelProperty(value = "录用情况", index = 5)
@ColumnWidth(10)
@DictDropdown(dictType = "ccdi_admit_status")
@Required
private String admitStatus;
/** 候选人姓名 */
@ExcelProperty(value = "候选人姓名", index = 6)
@ColumnWidth(15)
@Required
private String candName;
/** 招聘类型 */
@ExcelProperty(value = "招聘类型", index = 7)
@ColumnWidth(12)
@Required
private String recruitType;
/** 应聘人员学历 */
@ExcelProperty(value = "学历", index = 8)
@ColumnWidth(15)
@Required
private String candEdu;
/** 应聘人员证件号码 */
@ExcelProperty(value = "证件号码", index = 9)
@ColumnWidth(20)
@Required
private String candId;
/** 应聘人员毕业年月 */
@ExcelProperty(value = "毕业年月", index = 10)
@ColumnWidth(15)
@Required
private String candGrad;
/** 应聘人员毕业院校 */
@ExcelProperty(value = "毕业院校", index = 11)
@ColumnWidth(20)
@Required
private String candSchool;
/** 应聘人员专业 */
@ExcelProperty(value = "专业", index = 12)
@ColumnWidth(15)
@Required
private String candMajor;
/** 面试官1姓名 */
@ExcelProperty(value = "面试官1姓名", index = 12)
@ExcelProperty(value = "面试官1姓名", index = 13)
@ColumnWidth(15)
private String interviewerName1;
/** 面试官1工号 */
@ExcelProperty(value = "面试官1工号", index = 13)
@ExcelProperty(value = "面试官1工号", index = 14)
@ColumnWidth(15)
private String interviewerId1;
/** 面试官2姓名 */
@ExcelProperty(value = "面试官2姓名", index = 14)
@ExcelProperty(value = "面试官2姓名", index = 15)
@ColumnWidth(15)
private String interviewerName2;
/** 面试官2工号 */
@ExcelProperty(value = "面试官2工号", index = 15)
@ExcelProperty(value = "面试官2工号", index = 16)
@ColumnWidth(15)
private String interviewerId2;
}

View File

@@ -61,20 +61,20 @@ public class CcdiStaffRecruitmentWorkExcel implements Serializable {
@ColumnWidth(18)
private String departmentName;
/** 岗位 */
@ExcelProperty(value = "岗位", index = 7)
/** 岗位名称 */
@ExcelProperty(value = "岗位名称", index = 7)
@ColumnWidth(20)
@Required
private String positionName;
/** 入职年月 */
@ExcelProperty(value = "入职年月", index = 8)
@ExcelProperty(value = "入职时间", index = 8)
@ColumnWidth(12)
@Required
private String jobStartMonth;
/** 离职年月 */
@ExcelProperty(value = "离职年月", index = 9)
@ExcelProperty(value = "离职时间", index = 9)
@ColumnWidth(12)
private String jobEndMonth;
@@ -83,8 +83,8 @@ public class CcdiStaffRecruitmentWorkExcel implements Serializable {
@ColumnWidth(30)
private String departureReason;
/** 工作内容 */
@ExcelProperty(value = "工作内容", index = 11)
/** 主要工作内容 */
@ExcelProperty(value = "主要工作内容", index = 11)
@ColumnWidth(35)
private String workContent;

View File

@@ -22,7 +22,7 @@ public class IntermediaryEnterpriseRelationImportFailureVO implements Serializab
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
@Schema(description = "关联职务")
@Schema(description = "关联职务")
private String relationPersonPost;
@Schema(description = "备注")

View File

@@ -79,12 +79,14 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
try {
validateExcel(excel);
String ownerBizId = ownerBizIdByPersonId.get(excel.getOwnerPersonId());
String ownerPersonId = trim(excel.getOwnerPersonId());
String socialCreditCode = trim(excel.getSocialCreditCode());
String ownerBizId = ownerBizIdByPersonId.get(ownerPersonId);
if (StringUtils.isEmpty(ownerBizId)) {
throw new RuntimeException("中介本人不存在,请先导入或维护中介本人信息");
}
String combination = ownerBizId + "|" + excel.getSocialCreditCode();
String combination = ownerBizId + "|" + socialCreditCode;
if (existingCombinations.contains(combination)) {
throw new RuntimeException("中介实体关联关系已存在,请勿重复导入");
}
@@ -95,6 +97,9 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
BeanUtils.copyProperties(excel, relation);
relation.setIntermediaryBizId(ownerBizId);
relation.setSocialCreditCode(socialCreditCode);
relation.setRelationPersonPost(trim(excel.getRelationPersonPost()));
relation.setRemark(trim(excel.getRemark()));
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
successRecords.add(relation);
@@ -165,6 +170,7 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
private Map<String, String> getOwnerBizIdByPersonId(List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
List<String> ownerPersonIds = excelList.stream()
.map(CcdiIntermediaryEnterpriseRelationExcel::getOwnerPersonId)
.map(this::trim)
.filter(StringUtils::isNotEmpty)
.distinct()
.collect(Collectors.toList());
@@ -183,11 +189,12 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
List<String> combinations = excelList.stream()
.map(excel -> {
String ownerBizId = ownerBizIdByPersonId.get(excel.getOwnerPersonId());
if (StringUtils.isEmpty(ownerBizId) || StringUtils.isEmpty(excel.getSocialCreditCode())) {
String ownerBizId = ownerBizIdByPersonId.get(trim(excel.getOwnerPersonId()));
String socialCreditCode = trim(excel.getSocialCreditCode());
if (StringUtils.isEmpty(ownerBizId) || StringUtils.isEmpty(socialCreditCode)) {
return null;
}
return ownerBizId + "|" + excel.getSocialCreditCode();
return ownerBizId + "|" + socialCreditCode;
})
.filter(StringUtils::isNotEmpty)
.distinct()
@@ -199,24 +206,33 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
}
private void validateExcel(CcdiIntermediaryEnterpriseRelationExcel excel) {
if (StringUtils.isEmpty(excel.getOwnerPersonId())) {
String ownerPersonId = trim(excel.getOwnerPersonId());
String socialCreditCode = trim(excel.getSocialCreditCode());
String relationPersonPost = trim(excel.getRelationPersonPost());
String remark = trim(excel.getRemark());
if (StringUtils.isEmpty(ownerPersonId)) {
throw new RuntimeException("中介本人证件号码不能为空");
}
if (StringUtils.isEmpty(excel.getSocialCreditCode())) {
if (StringUtils.isEmpty(socialCreditCode)) {
throw new RuntimeException("统一社会信用代码不能为空");
}
String ownerPersonIdError = IdCardUtil.getErrorMessage(excel.getOwnerPersonId());
String ownerPersonIdError = IdCardUtil.getErrorMessage(ownerPersonId);
if (ownerPersonIdError != null) {
throw new RuntimeException("中介本人证件号码" + ownerPersonIdError);
}
if (StringUtils.isNotEmpty(excel.getRelationPersonPost()) && excel.getRelationPersonPost().length() > 100) {
throw new RuntimeException("关联职务长度不能超过100个字符");
if (StringUtils.isNotEmpty(relationPersonPost) && relationPersonPost.length() > 100) {
throw new RuntimeException("关联职务长度不能超过100个字符");
}
if (StringUtils.isNotEmpty(excel.getRemark()) && excel.getRemark().length() > 500) {
if (StringUtils.isNotEmpty(remark) && remark.length() > 500) {
throw new RuntimeException("备注长度不能超过500个字符");
}
}
private String trim(String value) {
return value == null ? null : value.trim();
}
private IntermediaryEnterpriseRelationImportFailureVO createFailureVO(CcdiIntermediaryEnterpriseRelationExcel excel,
String errorMessage) {
IntermediaryEnterpriseRelationImportFailureVO failure = new IntermediaryEnterpriseRelationImportFailureVO();

View File

@@ -178,7 +178,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
try {
CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO();
BeanUtils.copyProperties(excel, addDTO);
addDTO.setRecruitType(RecruitType.inferCode(addDTO.getRecruitName()));
addDTO.setRecruitType(normalizeRecruitType(excel.getRecruitType()));
validateRecruitmentData(addDTO, mainRow.sheetRowNum());
@@ -376,22 +376,22 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位描述不能为空");
}
if (StringUtils.isEmpty(addDTO.getCandName())) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员姓名不能为空");
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "候选人姓名不能为空");
}
if (StringUtils.isEmpty(addDTO.getCandEdu())) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员学历不能为空");
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "学历不能为空");
}
if (StringUtils.isEmpty(addDTO.getCandId())) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "证件号码不能为空");
}
if (StringUtils.isEmpty(addDTO.getCandSchool())) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员毕业院校不能为空");
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "毕业院校不能为空");
}
if (StringUtils.isEmpty(addDTO.getCandMajor())) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员专业不能为空");
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "专业不能为空");
}
if (StringUtils.isEmpty(addDTO.getCandGrad())) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员毕业年月不能为空");
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "毕业年月不能为空");
}
if (StringUtils.isEmpty(addDTO.getAdmitStatus())) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "录用情况不能为空");
@@ -414,10 +414,23 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
}
if (RecruitType.getDescByCode(addDTO.getRecruitType()) == null) {
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘类型只能填写'SOCIAL'或'CAMPUS'");
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘类型只能填写'SOCIAL/社招'或'CAMPUS/校招'");
}
}
private String normalizeRecruitType(String recruitType) {
String value = trim(recruitType);
if (StringUtils.isEmpty(value)) {
return value;
}
for (RecruitType type : RecruitType.values()) {
if (type.getCode().equals(value) || type.getDesc().equals(value)) {
return type.getCode();
}
}
return value;
}
private void validateWorkGroup(List<WorkImportRow> workRows, CcdiStaffRecruitment recruitment) {
Set<Integer> processedSortOrders = new HashSet<>();
for (WorkImportRow workRow : workRows) {
@@ -451,14 +464,14 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "工作单位不能为空");
}
if (StringUtils.isEmpty(trim(excel.getPositionName()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "岗位不能为空");
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "岗位名称不能为空");
}
if (StringUtils.isEmpty(trim(excel.getJobStartMonth()))) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "入职年月不能为空");
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "入职时间不能为空");
}
validateMonth(excel.getJobStartMonth(), "入职年月", sheetRowNum);
validateMonth(excel.getJobStartMonth(), "入职时间", sheetRowNum);
if (StringUtils.isNotEmpty(trim(excel.getJobEndMonth()))) {
validateMonth(excel.getJobEndMonth(), "离职年月", sheetRowNum);
validateMonth(excel.getJobEndMonth(), "离职时间", sheetRowNum);
}
if (recruitment == null) {
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不存在,请先维护招聘主信息");

View File

@@ -53,6 +53,9 @@
<if test="query.relationName != null and query.relationName != ''">
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
</if>
<if test="query.relationCertNo != null and query.relationCertNo != ''">
AND r.relation_cert_no LIKE CONCAT('%', #{query.relationCertNo}, '%')
</if>
ORDER BY r.create_time DESC
</select>

View File

@@ -61,6 +61,9 @@
<if test="query.relationName != null and query.relationName != ''">
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
</if>
<if test="query.relationCertNo != null and query.relationCertNo != ''">
AND r.relation_cert_no LIKE CONCAT('%', #{query.relationCertNo}, '%')
</if>
<if test="query.status != null">
AND r.status = #{query.status}
</if>
@@ -115,6 +118,9 @@
<if test="query.relationName != null and query.relationName != ''">
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
</if>
<if test="query.relationCertNo != null and query.relationCertNo != ''">
AND r.relation_cert_no LIKE CONCAT('%', #{query.relationCertNo}, '%')
</if>
<if test="query.status != null">
AND r.status = #{query.status}
</if>

View File

@@ -96,6 +96,26 @@ class CcdiAssetInfoImportServiceImplTest {
assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
}
@Test
void importAssetInfoSync_shouldResolveFamilyIdFromCurrentWorkbookRelation() {
CcdiAssetInfoExcel excel = buildExcel("320101199001010033", "股权");
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199001010033")))
.thenReturn(List.of());
service.importAssetInfoSync(
List.of(excel),
"task-current-workbook",
"tester",
Map.of("320101199001010033", Set.of("320101199009090099"))
);
ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(assetInfoMapper).insertBatch(captor.capture());
assertEquals("320101199009090099", captor.getValue().get(0).getFamilyId());
assertEquals("320101199001010033", captor.getValue().get(0).getPersonId());
}
@Test
void importAssetInfoAsync_shouldFailWhenEmployeeIdCardIsUsedForFamilyAssetImport() {
CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产");

View File

@@ -19,6 +19,7 @@ import org.springframework.data.redis.core.ValueOperations;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -79,6 +80,26 @@ class CcdiBaseStaffAssetImportServiceImplTest {
assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
}
@Test
void importAssetInfoSync_shouldImportWhenOwnerComesFromCurrentWorkbook() {
CcdiBaseStaffAssetInfoExcel excel = buildExcel("320101199001010033", "存款");
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
when(assetInfoMapper.selectOwnerCandidatesByBaseStaffIdCards(List.of("320101199001010033")))
.thenReturn(List.of());
service.importAssetInfoSync(
List.of(excel),
"task-current-workbook",
"tester",
Map.of("320101199001010033", Set.of("320101199001010033"))
);
ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(assetInfoMapper).insertBatch(captor.capture());
assertEquals("320101199001010033", captor.getValue().get(0).getFamilyId());
assertEquals("320101199001010033", captor.getValue().get(0).getPersonId());
}
@Test
void importAssetInfoAsync_shouldFailWhenFamilyCertificateIsUsed() {
CcdiBaseStaffAssetInfoExcel excel = buildExcel("320101199201010022", "车辆");

View File

@@ -2,6 +2,8 @@ package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
@@ -10,8 +12,10 @@ import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.service.impl.CcdiStaffEnterpriseRelationServiceImpl;
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.session.Configuration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -20,13 +24,17 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -55,8 +63,17 @@ class CcdiStaffEnterpriseRelationServiceImplTest {
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Mock
private EnterpriseAutoFillService enterpriseAutoFillService;
@AfterEach
void clearSecurityContext() {
SecurityContextHolder.clearContext();
}
@Test
void insertRelation_shouldAllowValidFamily() {
mockLoginUser("tester");
CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();
CcdiStaffFmyRelation familyRelation = new CcdiStaffFmyRelation();
familyRelation.setRelationCertNo(addDTO.getPersonId());
@@ -75,6 +92,13 @@ class CcdiStaffEnterpriseRelationServiceImplTest {
assertEquals(1, captor.getValue().getStatus());
assertEquals("MANUAL", captor.getValue().getDataSource());
assertEquals(1, captor.getValue().getIsEmpFamily());
verify(enterpriseAutoFillService).ensureExists(argThat(item ->
"91310000123456789A".equals(item.socialCreditCode())
&& "测试企业".equals(item.enterpriseName())
&& "EMP_RELATION".equals(item.entSource())
&& "MANUAL".equals(item.dataSource())
&& "tester".equals(item.userName())
));
}
@Test
@@ -153,4 +177,13 @@ class CcdiStaffEnterpriseRelationServiceImplTest {
assistant.setCurrentNamespace(namespace);
TableInfoHelper.initTableInfo(assistant, entityClass);
}
private void mockLoginUser(String userName) {
SysUser user = new SysUser();
user.setUserName(userName);
LoginUser loginUser = new LoginUser(1L, 1L, user, Set.of());
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(loginUser, null, List.of());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}

View File

@@ -0,0 +1,29 @@
# 员工亲属关系与信贷客户家庭关系关系人身份证号展示实施记录
## 修改内容
- 在【员工亲属关系维护】列表新增“关系人身份证号”列,展示接口返回的 `relationCertNo`
- 在【信贷客户家庭关系】列表新增“关系人身份证号”列,展示接口返回的 `relationCertNo`
- 两个页面查询区新增“关系人身份证号”筛选项,支持按关系人身份证号模糊查询。
- 后端查询 DTO 与 MyBatis 分页 SQL 补充 `relationCertNo` 查询条件,保持页面查询条件与接口过滤逻辑一致。
## 影响范围
- 前端页面:
- `ruoyi-ui/src/views/ccdiStaffFmyRelation/index.vue`
- `ruoyi-ui/src/views/ccdiCustFmyRelation/index.vue`
- 后端查询:
- `CcdiStaffFmyRelationQueryDTO`
- `CcdiCustFmyRelationQueryDTO`
- `CcdiStaffFmyRelationMapper.xml`
- `CcdiCustFmyRelationMapper.xml`
## 验证情况
- 已确认两个分页接口原 SQL 与 VO 均包含 `relation_cert_no` / `relationCertNo` 字段。
- 已执行 `mvn -pl ccdi-info-collection -am compile -DskipTests`,编译通过。
- 已按 `ruoyi-ui/.nvmrc` 使用 Node `14.21.3` 执行 `npm run build:prod`,构建通过,仅存在既有资源体积告警。
- 已通过 `browser-use` 打开真实前端页面验证:
- `/maintain/staffFmyRelation` 展示“关系人身份证号”筛选项与表格列,并可按 `330101196501010011` 查询到对应员工亲属关系记录。
- `/maintain/custFmyRelation` 展示“关系人身份证号”筛选项与表格列,并可按 `330101197806060077` 查询到对应信贷客户家庭关系记录。
- 浏览器控制台未发现错误日志。

View File

@@ -0,0 +1,67 @@
# 招聘信息与中介实体关系导入模板修复实施记录
## 背景
- 【招聘信息维护】当前页面与数据库口径已调整为“招聘记录编号”,原导入模板仍存在旧字段名称和字段顺序不一致问题,导致按模板导入失败。
- 【中介库管理】“导入中介实体关联关系”按钮对应模板字段与新增关联机构弹窗不一致,且导入提示仍要求统一社会信用代码必须已存在于机构表。
## 修改内容
### 招聘信息维护
- 更新招聘主 Sheet 模板字段顺序,使其与新增页字段顺序一致:
- 招聘记录编号、招聘项目名称、职位名称、职位类别、职位描述、录用情况、候选人姓名、招聘类型、学历、证件号码、毕业年月、毕业院校、专业、面试官1姓名、面试官1工号、面试官2姓名、面试官2工号。
- 更新历史工作经历 Sheet 字段文案:
- 岗位名称、入职时间、离职时间、主要工作内容。
- 导入逻辑不再从招聘项目名称推断招聘类型,改为读取模板中的“招聘类型”字段。
- 招聘类型支持填写编码或页面文案:
- `SOCIAL` / `社招`
- `CAMPUS` / `校招`
- 同步调整导入校验提示,使错误信息与当前页面字段保持一致。
- 前端导入弹窗增加招聘类型填写说明。
### 中介库管理
- 更新“导入中介实体关联关系”模板字段:
- 中介本人证件号码、统一社会信用代码、关联职务、备注。
- 将必填标识改为 `@Required`,避免字段标题携带 `*` 后与页面字段不一致。
- 导入逻辑统一 trim 证件号、统一社会信用代码、关联职务、备注,避免空格导致查询不到中介本人或重复判断失效。
- 失败记录字段文案由“关联人职务”统一为“关联职务”。
- 前端导入说明调整为:
- 中介本人证件号码用于定位新增弹窗中的所属中介;
- 其余字段与新增关联机构弹窗一致;
- 统一社会信用代码未存在于实体库时会自动补入。
## 影响范围
- 后端导入模板与导入解析:
- `CcdiStaffRecruitmentExcel`
- `CcdiStaffRecruitmentWorkExcel`
- `CcdiStaffRecruitmentImportServiceImpl`
- `CcdiIntermediaryEnterpriseRelationExcel`
- `CcdiIntermediaryEnterpriseRelationImportServiceImpl`
- `IntermediaryEnterpriseRelationImportFailureVO`
- 前端导入弹窗与失败记录展示:
- 招聘信息维护导入提示
- 中介库管理导入中介实体关联关系提示
- 中介实体关系导入失败记录字段标签
## 验证情况
- 后端编译:`mvn -pl ccdi-info-collection -am compile -DskipTests` 通过。
- 前端构建:`ruoyi-ui` 下执行 `nvm use` 后,`npm run build:prod` 通过。
- 真实模板下载:
- `/ccdi/staffRecruitment/importTemplate` 下载模板成功,表头已变为当前字段顺序。
- `/ccdi/intermediary/importEnterpriseRelationTemplate` 下载模板成功,表头已变为“中介本人证件号码、统一社会信用代码、关联职务、备注”。
- 真实接口导入:
- 招聘信息基于下载模板造数,主 Sheet 与历史工作经历 Sheet 共 2 行导入成功,详情接口回查历史工作经历 1 条。
- 中介实体关联关系基于下载模板造数,导入成功 1 条,列表可回查关联职务。
- 清理情况:
- 已删除本轮成功导入的招聘记录及历史工作经历。
- 已删除本轮成功导入的中介实体关联关系。
- 已删除本轮由导入自动补入的实体库测试数据。
- 真实页面检查:
- 使用 `browser-use` 打开真实页面 `/maintain/staffRecruitment`,确认招聘信息导入弹窗显示新的双 Sheet 和招聘类型说明。
- 使用 `browser-use` 打开真实页面 `/maintain/intermediary`,确认“导入中介实体关联关系”按钮可打开导入弹窗,字段说明与新增关联机构口径一致。
- `browser-use` 当前不支持文件上传,页面文件选择动作无法在浏览器插件内完成;文件上传动作已通过同一真实导入接口完成验证。
- 测试完成后已停止本轮启动的后端与前端进程。

View File

@@ -24,6 +24,13 @@
- 招投标供应商新增、编辑和导入时,对合法统一社会信用代码的供应商自动补入实体库。
- 新增企业来源枚举 `SUPPLIER`,用于标识供应商来源。
### 测试补充
- 同步调整员工信息维护、员工亲属关系维护导入 Controller 单测,按新的统一编排入口断言返回任务 ID。
- 补充员工资产导入单测,验证同一模板中本轮成功导入的员工身份证号可作为员工资产归属。
- 补充亲属资产导入单测,验证同一模板中本轮成功导入的亲属关系可作为亲属资产归属。
- 补充员工亲属实体关联新增单测,验证成功新增时调用实体库自动补入服务,来源为 `EMP_RELATION`、数据来源为 `MANUAL`
## 影响范围
- `/ccdi/baseStaff/importData`
@@ -36,6 +43,16 @@
- 执行 `mvn -pl ccdi-info-collection -am -DskipTests compile`结果BUILD SUCCESS。
- 执行 `mvn -DskipTests compile`结果BUILD SUCCESS。
- 代码路径核对:员工主 Sheet 为空时仅调用员工资产导入服务并返回 `assetTaskId`;亲属关系主 Sheet 为空时仅调用亲属资产导入服务并返回 `assetTaskId`
- 复测执行 `mvn -pl ccdi-info-collection -am -Dtest=CcdiBaseStaffControllerTest,CcdiStaffFmyRelationControllerTest,CcdiBaseStaffAssetImportServiceImplTest,CcdiAssetInfoImportServiceImplTest,CcdiBaseStaffDualImportServiceTest,CcdiStaffFmyRelationImportServiceImplTest,CcdiStaffEnterpriseRelationServiceImplTest,CcdiStaffEnterpriseRelationImportServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`结果BUILD SUCCESSTests run: 39, Failures: 0, Errors: 0, Skipped: 0。
- 复测执行 `mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false test`结果BUILD FAILURE本次问题相关用例均已通过剩余失败为中介实体关联测试未注入自动补入服务以及 `CcdiPurchaseTransactionFeatureContractTest` 依赖的 `sql/ccdi_purchase_transaction.sql` 文件不存在。
- 使用 `bin/restart_java_backend.sh` 重启后端并通过 `/login/test` 探活结果HTTP 200。
- 通过真实接口下载当前导入模板,基于模板生成测试文件,执行 `/ccdi/baseStaff/importData`:员工任务 `8ea63988-deb2-4791-a24a-f15ca2c8cd6e` 与员工资产任务 `f281beca-bb58-4076-86db-6f9f948bbaf0` 均为 `SUCCESS`,成功 1 条、失败 0 条。
- 回查 `ccdi_base_staff``ccdi_asset_info`:员工主数据写入成功;员工资产 `family_id``person_id` 均为本轮员工身份证号,第二个 Sheet 未再出现“未找到资产归属员工”。
- 执行 `/ccdi/staffFmyRelation/importData`:亲属关系任务 `702466a9-0113-4e89-bcf1-8d760ee34543` 与亲属资产任务 `f11906d4-b9f3-4656-834c-fc9dc1a27704` 均为 `SUCCESS`,成功 1 条、失败 0 条。
- 回查 `ccdi_staff_fmy_relation``ccdi_asset_info`:亲属关系主数据写入成功;亲属资产 `family_id` 为员工身份证号、`person_id` 为亲属身份证号,第二个 Sheet 已正确关联到本轮亲属主数据。
- 执行 `/ccdi/staffEnterpriseRelation/importData`:员工亲属实体关联任务 `6361fc94-0d32-4da0-b1a0-7419b399710d``SUCCESS`,成功 1 条、失败 0 条。
- 回查 `ccdi_staff_enterprise_relation``ccdi_enterprise_base_info`:实体关联写入成功;实体库自动生成对应企业,`ent_source=EMP_RELATION``data_source=IMPORT`
- 验证结束后执行清理 SQL回查本轮员工、亲属、资产、亲属实体关联和实体库测试数据计数均为 0。
## 备注

View File

@@ -36,6 +36,17 @@
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="关系人身份证号" prop="relationCertNo">
<el-input
v-model="queryParams.relationCertNo"
placeholder="请输入关系人身份证号"
clearable
style="width: 100%"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
@@ -100,6 +111,7 @@
</template>
</el-table-column>
<el-table-column label="关系人姓名" align="center" prop="relationName" :show-overflow-tooltip="true"/>
<el-table-column label="关系人身份证号" align="center" prop="relationCertNo" width="180"/>
<el-table-column label="性别" align="center" prop="gender" width="80">
<template slot-scope="scope">
<dict-tag :options="dict.type.ccdi_indiv_gender" :value="scope.row.gender"/>
@@ -497,6 +509,7 @@ export default {
personId: null,
relationType: null,
relationName: null,
relationCertNo: null,
status: null
},
// 表单参数

View File

@@ -130,7 +130,9 @@ export default {
statusApi: getEnterpriseRelationImportStatus,
tips: [
"只导入中介与机构关系;",
"统一社会信用代码必须已存在于系统机构表。"
"中介本人证件号码用于定位新增弹窗中的所属中介;",
"其余字段与新增关联机构弹窗一致;",
"统一社会信用代码未存在于实体库时会自动补入。"
]
};
}

View File

@@ -189,7 +189,7 @@
<el-table :data="enterpriseRelationFailureList" v-loading="enterpriseRelationFailureLoading">
<el-table-column label="中介本人证件号码" prop="ownerPersonId" align="center" min-width="180" />
<el-table-column label="统一社会信用代码" prop="socialCreditCode" align="center" min-width="180" />
<el-table-column label="关联职务" prop="relationPersonPost" align="center" />
<el-table-column label="关联职务" prop="relationPersonPost" align="center" />
<el-table-column label="失败原因" prop="errorMessage" align="center" min-width="220" :show-overflow-tooltip="true" />
</el-table>

View File

@@ -36,6 +36,17 @@
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="关系人身份证号" prop="relationCertNo">
<el-input
v-model="queryParams.relationCertNo"
placeholder="请输入关系人身份证号"
clearable
style="width: 100%"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
@@ -115,6 +126,7 @@
</template>
</el-table-column>
<el-table-column label="关系人姓名" align="center" prop="relationName" :show-overflow-tooltip="true"/>
<el-table-column label="关系人身份证号" align="center" prop="relationCertNo" width="180"/>
<el-table-column label="家庭成员年收入" align="center" prop="annualIncome" width="160"/>
<el-table-column label="性别" align="center" prop="gender" width="80">
<template slot-scope="scope">
@@ -710,6 +722,7 @@ export default {
personName: null,
relationType: null,
relationName: null,
relationCertNo: null,
status: null
},
// 表单参数

View File

@@ -833,7 +833,7 @@ export default {
// 弹出层标题
title: "招聘信息数据导入",
// 弹窗提示
tip: "仅允许导入\"xls\"或\"xlsx\"格式文件。模板包含“招聘信息”和“历史工作经历”两个 Sheet。",
tip: "仅允许导入\"xls\"或\"xlsx\"格式文件。模板包含“招聘信息”和“历史工作经历”两个 Sheet,招聘类型填写“社招/SOCIAL”或“校招/CAMPUS”。",
// 是否禁用上传
isUploading: false,
// 设置上传的请求头部
@@ -1222,7 +1222,7 @@ export default {
openImportDialog() {
this.upload.title = "招聘信息数据导入";
this.upload.url = process.env.VUE_APP_BASE_API + "/ccdi/staffRecruitment/importData";
this.upload.tip = "仅允许导入\"xls\"或\"xlsx\"格式文件。模板包含“招聘信息”和“历史工作经历”两个 Sheet。";
this.upload.tip = "仅允许导入\"xls\"或\"xlsx\"格式文件。模板包含“招聘信息”和“历史工作经历”两个 Sheet,招聘类型填写“社招/SOCIAL”或“校招/CAMPUS”。";
if (this.isPreviewMode()) {
this.upload.open = true;
return;