From b7d020c0b23b2ca4bfadc5f12be20f4f2f29b429 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Thu, 23 Apr 2026 17:31:56 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=AE=9E=E4=BD=93=E5=BA=93?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=95=B0=E6=8D=AE=E6=9D=A5=E6=BA=90=E7=BB=B4?= =?UTF-8?q?=E6=8A=A4=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/CcdiEnterpriseBaseInfoAddDTO.java | 2 - .../dto/CcdiEnterpriseBaseInfoEditDTO.java | 1 - .../excel/CcdiEnterpriseBaseInfoExcel.java | 6 +- ...diEnterpriseBaseInfoImportServiceImpl.java | 28 +-- .../CcdiEnterpriseBaseInfoServiceImpl.java | 27 ++- ...terpriseBaseInfoImportServiceImplTest.java | 77 +++++++ ...CcdiEnterpriseBaseInfoServiceImplTest.java | 204 +++++++++++++++++ .../utils/EasyExcelUtilTemplateTest.java | 26 +++ ...-info-add-dialog-backend-implementation.md | 50 ++++ ...info-add-dialog-frontend-implementation.md | 41 ++++ ...ise-base-info-add-dialog-implementation.md | 80 +++++++ .../views/ccdiEnterpriseBaseInfo/index.vue | 213 ++++++++++-------- 12 files changed, 630 insertions(+), 125 deletions(-) create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoImportServiceImplTest.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoServiceImplTest.java create mode 100644 docs/plans/backend/2026-04-23-enterprise-base-info-add-dialog-backend-implementation.md create mode 100644 docs/plans/frontend/2026-04-23-enterprise-base-info-add-dialog-frontend-implementation.md create mode 100644 docs/reports/implementation/2026-04-23-enterprise-base-info-add-dialog-implementation.md diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoAddDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoAddDTO.java index 9cf5ef22..ffea39ef 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoAddDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoAddDTO.java @@ -89,7 +89,6 @@ public class CcdiEnterpriseBaseInfoAddDTO implements Serializable { private String shareholder5; @Schema(description = "经营状态") - @NotBlank(message = "经营状态不能为空") @Size(max = 50, message = "经营状态长度不能超过50个字符") private String status; @@ -102,6 +101,5 @@ public class CcdiEnterpriseBaseInfoAddDTO implements Serializable { private String entSource; @Schema(description = "数据来源") - @NotBlank(message = "数据来源不能为空") private String dataSource; } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoEditDTO.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoEditDTO.java index dc015fea..c16d9ce7 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoEditDTO.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoEditDTO.java @@ -89,7 +89,6 @@ public class CcdiEnterpriseBaseInfoEditDTO implements Serializable { private String shareholder5; @Schema(description = "经营状态") - @NotBlank(message = "经营状态不能为空") @Size(max = 50, message = "经营状态长度不能超过50个字符") private String status; diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiEnterpriseBaseInfoExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiEnterpriseBaseInfoExcel.java index 9ea0f78a..a3bc16fa 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiEnterpriseBaseInfoExcel.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiEnterpriseBaseInfoExcel.java @@ -88,7 +88,7 @@ public class CcdiEnterpriseBaseInfoExcel implements Serializable { @ColumnWidth(18) private String shareholder5; - @ExcelProperty(value = "经营状态*", index = 16) + @ExcelProperty(value = "经营状态", index = 16) @ColumnWidth(16) private String status; @@ -99,8 +99,4 @@ public class CcdiEnterpriseBaseInfoExcel implements Serializable { @ExcelProperty(value = "企业来源*", index = 18) @ColumnWidth(18) private String entSource; - - @ExcelProperty(value = "数据来源*", index = 19) - @ColumnWidth(18) - private String dataSource; } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoImportServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoImportServiceImpl.java index 81bdd562..9480001a 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoImportServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoImportServiceImpl.java @@ -131,10 +131,6 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB if (!excel.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) { throw new RuntimeException("统一社会信用代码格式不正确"); } - if (StringUtils.isEmpty(excel.getStatus())) { - throw new RuntimeException("经营状态不能为空"); - } - String riskLevel = EnterpriseRiskLevel.resolveCode(StringUtils.trim(excel.getRiskLevel())); if (riskLevel == null) { throw new RuntimeException("风险等级不在允许范围内"); @@ -143,10 +139,6 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB if (entSource == null) { throw new RuntimeException("企业来源不在允许范围内"); } - String dataSource = resolveDataSourceCode(StringUtils.trim(excel.getDataSource())); - if (dataSource == null) { - throw new RuntimeException("数据来源不在允许范围内"); - } if (existingCreditCodes.contains(excel.getSocialCreditCode())) { throw new RuntimeException(String.format("统一社会信用代码[%s]已存在,请勿重复导入", excel.getSocialCreditCode())); @@ -159,8 +151,8 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB BeanUtils.copyProperties(excel, entity); entity.setRiskLevel(riskLevel); entity.setEntSource(entSource); - entity.setDataSource(dataSource); - entity.setStatus(StringUtils.trim(excel.getStatus())); + entity.setDataSource(DataSource.IMPORT.getCode()); + entity.setStatus(trimToNull(excel.getStatus())); entity.setCreatedBy(userName); entity.setUpdatedBy(userName); return entity; @@ -206,15 +198,6 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB redisTemplate.opsForHash().putAll(buildStatusKey(taskId), statusData); } - private String resolveDataSourceCode(String value) { - for (DataSource source : DataSource.values()) { - if (source.getCode().equals(value) || source.getDesc().equals(value)) { - return source.getCode(); - } - } - return null; - } - private String buildStatusKey(String taskId) { return "import:enterpriseBaseInfo:" + taskId; } @@ -222,4 +205,11 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB private String buildFailuresKey(String taskId) { return "import:enterpriseBaseInfo:" + taskId + ":failures"; } + + private String trimToNull(String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + return value.trim(); + } } diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoServiceImpl.java index f6c1f95f..91bc4aad 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoServiceImpl.java @@ -85,10 +85,12 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf if (enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode()) != null) { throw new RuntimeException("该统一社会信用代码已存在"); } - validateEnumFields(addDTO.getStatus(), addDTO.getRiskLevel(), addDTO.getEntSource(), addDTO.getDataSource()); + validateRiskLevelAndEnterpriseSource(addDTO.getRiskLevel(), addDTO.getEntSource()); CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo(); BeanUtils.copyProperties(addDTO, entity); + entity.setStatus(trimToNull(addDTO.getStatus())); + entity.setDataSource(DataSource.MANUAL.getCode()); return enterpriseBaseInfoMapper.insert(entity); } @@ -103,6 +105,8 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo(); BeanUtils.copyProperties(editDTO, entity); + entity.setStatus(trimToNull(editDTO.getStatus())); + entity.setDataSource(existing.getDataSource()); return enterpriseBaseInfoMapper.updateById(entity); } @@ -176,18 +180,22 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf } private void validateEnumFields(String status, String riskLevel, String entSource, String dataSource) { - if (StringUtils.isEmpty(status)) { - throw new RuntimeException("经营状态不能为空"); + validateRiskLevelAndEnterpriseSource(riskLevel, entSource); + if (StringUtils.isNotEmpty(status) && StringUtils.trim(status).length() > 50) { + throw new RuntimeException("经营状态长度不能超过50个字符"); } + if (!containsDataSource(dataSource)) { + throw new RuntimeException("数据来源不在允许范围内"); + } + } + + private void validateRiskLevelAndEnterpriseSource(String riskLevel, String entSource) { if (!EnterpriseRiskLevel.contains(riskLevel)) { throw new RuntimeException("风险等级不在允许范围内"); } if (!EnterpriseSource.contains(entSource)) { throw new RuntimeException("企业来源不在允许范围内"); } - if (!containsDataSource(dataSource)) { - throw new RuntimeException("数据来源不在允许范围内"); - } } private boolean containsDataSource(String code) { @@ -199,6 +207,13 @@ public class CcdiEnterpriseBaseInfoServiceImpl implements ICcdiEnterpriseBaseInf return false; } + private String trimToNull(String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + return value.trim(); + } + private void validateDeleteRelations(String socialCreditCode) { StringJoiner relationTypes = new StringJoiner("、"); if (staffEnterpriseRelationMapper.selectCount(new LambdaQueryWrapper() diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoImportServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoImportServiceImplTest.java new file mode 100644 index 00000000..1791ae31 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoImportServiceImplTest.java @@ -0,0 +1,77 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo; +import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel; +import com.ruoyi.info.collection.service.impl.CcdiEnterpriseBaseInfoImportServiceImpl; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CcdiEnterpriseBaseInfoImportServiceImplTest { + + private final CcdiEnterpriseBaseInfoImportServiceImpl service = new CcdiEnterpriseBaseInfoImportServiceImpl(); + + @Test + void validateAndBuildEntity_shouldRejectWhenDatabaseAlreadyContainsCreditCode() { + CcdiEnterpriseBaseInfoExcel excel = buildExcel(); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> service.validateAndBuildEntity(excel, Set.of("91310000123456789A"), new HashSet<>(), "admin")); + + assertEquals("统一社会信用代码[91310000123456789A]已存在,请勿重复导入", exception.getMessage()); + } + + @Test + void validateAndBuildEntity_shouldRejectWhenExcelContainsDuplicateCreditCode() { + CcdiEnterpriseBaseInfoExcel excel = buildExcel(); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> service.validateAndBuildEntity(excel, Set.of(), new HashSet<>(Set.of("91310000123456789A")), "admin")); + + assertEquals("统一社会信用代码[91310000123456789A]在导入文件中重复,已跳过此条记录", exception.getMessage()); + } + + @Test + void validateAndBuildEntity_shouldNormalizeEnumTextToCode() { + CcdiEnterpriseBaseInfoExcel excel = buildExcel(); + excel.setRiskLevel("高风险"); + excel.setEntSource("一般企业"); + + CcdiEnterpriseBaseInfo entity = service.validateAndBuildEntity(excel, Set.of(), new HashSet<>(), "admin"); + + assertEquals("1", entity.getRiskLevel()); + assertEquals("GENERAL", entity.getEntSource()); + assertEquals("IMPORT", entity.getDataSource()); + assertEquals("admin", entity.getCreatedBy()); + } + + @Test + void validateAndBuildEntity_shouldAllowBlankStatus() { + CcdiEnterpriseBaseInfoExcel excel = buildExcel(); + excel.setStatus(null); + + CcdiEnterpriseBaseInfo entity = service.validateAndBuildEntity(excel, Set.of(), new HashSet<>(), "admin"); + + assertNull(entity.getStatus()); + assertEquals("IMPORT", entity.getDataSource()); + } + + private CcdiEnterpriseBaseInfoExcel buildExcel() { + CcdiEnterpriseBaseInfoExcel excel = new CcdiEnterpriseBaseInfoExcel(); + excel.setSocialCreditCode("91310000123456789A"); + excel.setEnterpriseName("测试企业"); + excel.setEnterpriseType("有限责任公司"); + excel.setEnterpriseNature("民营企业"); + excel.setIndustryClass("制造业"); + excel.setIndustryName("电子设备"); + excel.setStatus("存续"); + excel.setRiskLevel("1"); + excel.setEntSource("GENERAL"); + return excel; + } +} diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoServiceImplTest.java new file mode 100644 index 00000000..6021203c --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoServiceImplTest.java @@ -0,0 +1,204 @@ +package com.ruoyi.info.collection.service; + +import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo; +import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoAddDTO; +import com.ruoyi.info.collection.domain.dto.CcdiEnterpriseBaseInfoEditDTO; +import com.ruoyi.info.collection.domain.vo.CcdiEnterpriseBaseInfoVO; +import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper; +import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper; +import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper; +import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper; +import com.ruoyi.info.collection.service.impl.CcdiEnterpriseBaseInfoServiceImpl; +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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CcdiEnterpriseBaseInfoServiceImplTest { + + @InjectMocks + private CcdiEnterpriseBaseInfoServiceImpl service; + + @Mock + private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper; + + @Mock + private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper; + + @Mock + private CcdiCustEnterpriseRelationMapper custEnterpriseRelationMapper; + + @Mock + private CcdiIntermediaryEnterpriseRelationMapper intermediaryEnterpriseRelationMapper; + + @Mock + private ICcdiEnterpriseBaseInfoImportService enterpriseBaseInfoImportService; + + @Mock + private RedisTemplate redisTemplate; + + @Test + void insertEnterpriseBaseInfo_shouldPersistWhenSocialCreditCodeIsUnique() { + CcdiEnterpriseBaseInfoAddDTO addDTO = buildAddDto(); + when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null); + when(enterpriseBaseInfoMapper.insert(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1); + + int result = service.insertEnterpriseBaseInfo(addDTO); + + assertEquals(1, result); + ArgumentCaptor captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class); + verify(enterpriseBaseInfoMapper).insert(captor.capture()); + assertEquals("测试企业", captor.getValue().getEnterpriseName()); + assertEquals("GENERAL", captor.getValue().getEntSource()); + } + + @Test + void insertEnterpriseBaseInfo_shouldSetManualDataSourceAndAllowBlankStatus() { + CcdiEnterpriseBaseInfoAddDTO addDTO = buildAddDto(); + addDTO.setStatus(null); + addDTO.setDataSource("API"); + when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null); + when(enterpriseBaseInfoMapper.insert(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1); + + int result = service.insertEnterpriseBaseInfo(addDTO); + + assertEquals(1, result); + ArgumentCaptor captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class); + verify(enterpriseBaseInfoMapper).insert(captor.capture()); + assertEquals("MANUAL", captor.getValue().getDataSource()); + assertNull(captor.getValue().getStatus()); + } + + @Test + void insertEnterpriseBaseInfo_shouldRejectInvalidRiskLevel() { + CcdiEnterpriseBaseInfoAddDTO addDTO = buildAddDto(); + addDTO.setRiskLevel("9"); + when(enterpriseBaseInfoMapper.selectById(addDTO.getSocialCreditCode())).thenReturn(null); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> service.insertEnterpriseBaseInfo(addDTO)); + + assertEquals("风险等级不在允许范围内", exception.getMessage()); + } + + @Test + void updateEnterpriseBaseInfo_shouldRejectWhenRecordMissing() { + CcdiEnterpriseBaseInfoEditDTO editDTO = buildEditDto(); + when(enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode())).thenReturn(null); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> service.updateEnterpriseBaseInfo(editDTO)); + + assertEquals("实体库记录不存在", exception.getMessage()); + } + + @Test + void updateEnterpriseBaseInfo_shouldKeepExistingDataSource() { + CcdiEnterpriseBaseInfoEditDTO editDTO = buildEditDto(); + editDTO.setDataSource("API"); + CcdiEnterpriseBaseInfo existing = new CcdiEnterpriseBaseInfo(); + existing.setSocialCreditCode(editDTO.getSocialCreditCode()); + existing.setDataSource("MANUAL"); + when(enterpriseBaseInfoMapper.selectById(editDTO.getSocialCreditCode())).thenReturn(existing); + when(enterpriseBaseInfoMapper.updateById(any(CcdiEnterpriseBaseInfo.class))).thenReturn(1); + + int result = service.updateEnterpriseBaseInfo(editDTO); + + assertEquals(1, result); + ArgumentCaptor captor = ArgumentCaptor.forClass(CcdiEnterpriseBaseInfo.class); + verify(enterpriseBaseInfoMapper).updateById(captor.capture()); + assertEquals("MANUAL", captor.getValue().getDataSource()); + } + + @Test + void selectEnterpriseBaseInfoById_shouldConvertEntityToVo() { + CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo(); + entity.setSocialCreditCode("91310000123456789A"); + entity.setEnterpriseName("测试企业"); + entity.setRiskLevel("1"); + entity.setEntSource("GENERAL"); + when(enterpriseBaseInfoMapper.selectById("91310000123456789A")).thenReturn(entity); + + CcdiEnterpriseBaseInfoVO vo = service.selectEnterpriseBaseInfoById("91310000123456789A"); + + assertNotNull(vo); + assertEquals("测试企业", vo.getEnterpriseName()); + assertEquals("1", vo.getRiskLevel()); + } + + @Test + void deleteEnterpriseBaseInfoByIds_shouldDeleteInBatch() { + when(staffEnterpriseRelationMapper.selectCount(any())).thenReturn(0L); + when(custEnterpriseRelationMapper.selectCount(any())).thenReturn(0L); + when(intermediaryEnterpriseRelationMapper.selectCount(any())).thenReturn(0L); + when(enterpriseBaseInfoMapper.deleteBatchIds(java.util.List.of("91310000123456789A", "91310000123456789B"))) + .thenReturn(2); + + int result = service.deleteEnterpriseBaseInfoByIds(new String[]{"91310000123456789A", "91310000123456789B"}); + + assertEquals(2, result); + verify(enterpriseBaseInfoMapper).deleteBatchIds(java.util.List.of("91310000123456789A", "91310000123456789B")); + } + + @Test + void deleteEnterpriseBaseInfoByIds_shouldRejectWhenStaffRelationExists() { + when(staffEnterpriseRelationMapper.selectCount(any())).thenReturn(1L); + when(custEnterpriseRelationMapper.selectCount(any())).thenReturn(0L); + when(intermediaryEnterpriseRelationMapper.selectCount(any())).thenReturn(0L); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> service.deleteEnterpriseBaseInfoByIds(new String[]{"91310000123456789A"})); + + assertEquals("统一社会信用代码[91310000123456789A]已关联员工,删除失败", exception.getMessage()); + } + + @Test + void deleteEnterpriseBaseInfoByIds_shouldRejectWhenMultipleRelationsExist() { + when(staffEnterpriseRelationMapper.selectCount(any())).thenReturn(1L); + when(custEnterpriseRelationMapper.selectCount(any())).thenReturn(1L); + when(intermediaryEnterpriseRelationMapper.selectCount(any())).thenReturn(1L); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> service.deleteEnterpriseBaseInfoByIds(new String[]{"91310000123456789A"})); + + assertEquals("统一社会信用代码[91310000123456789A]已关联员工、信贷客户、中介,删除失败", exception.getMessage()); + } + + private CcdiEnterpriseBaseInfoAddDTO buildAddDto() { + CcdiEnterpriseBaseInfoAddDTO dto = new CcdiEnterpriseBaseInfoAddDTO(); + dto.setSocialCreditCode("91310000123456789A"); + dto.setEnterpriseName("测试企业"); + dto.setEnterpriseType("有限责任公司"); + dto.setEnterpriseNature("民营企业"); + dto.setIndustryClass("制造业"); + dto.setIndustryName("电子设备"); + dto.setStatus("存续"); + dto.setRiskLevel("1"); + dto.setEntSource("GENERAL"); + dto.setDataSource("MANUAL"); + return dto; + } + + private CcdiEnterpriseBaseInfoEditDTO buildEditDto() { + CcdiEnterpriseBaseInfoEditDTO dto = new CcdiEnterpriseBaseInfoEditDTO(); + dto.setSocialCreditCode("91310000123456789A"); + dto.setEnterpriseName("测试企业"); + dto.setStatus("存续"); + dto.setRiskLevel("1"); + dto.setEntSource("GENERAL"); + dto.setDataSource("MANUAL"); + return dto; + } +} diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java index 1cb590d2..44a5ec81 100644 --- a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java @@ -4,11 +4,13 @@ import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.utils.DictUtils; import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel; import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel; +import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel; import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellRangeAddress; @@ -129,6 +131,30 @@ class EasyExcelUtilTemplateTest { } } + @Test + void importTemplateWithDictDropdown_shouldUseUpdatedEnterpriseHeaders() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + + try (MockedStatic mocked = mockStatic(DictUtils.class)) { + mocked.when(() -> DictUtils.getDictCache("ccdi_entity_type")) + .thenReturn(List.of(buildDictData("有限责任公司"))); + mocked.when(() -> DictUtils.getDictCache("ccdi_enterprise_nature")) + .thenReturn(List.of(buildDictData("民营企业"))); + mocked.when(() -> DictUtils.getDictCache("ccdi_certificate_type")) + .thenReturn(List.of(buildDictData("居民身份证"))); + + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiEnterpriseBaseInfoExcel.class, "实体库管理"); + } + + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) { + Row headerRow = workbook.getSheetAt(0).getRow(0); + assertEquals("经营状态", headerRow.getCell(16).getStringCellValue()); + assertEquals("风险等级*", headerRow.getCell(17).getStringCellValue()); + assertEquals("企业来源*", headerRow.getCell(18).getStringCellValue()); + assertEquals(19, headerRow.getLastCellNum()); + } + } + private void assertTextColumn(Sheet sheet, int columnIndex) { CellStyle style = sheet.getColumnStyle(columnIndex); assertNotNull(style, "文本列应设置默认样式"); diff --git a/docs/plans/backend/2026-04-23-enterprise-base-info-add-dialog-backend-implementation.md b/docs/plans/backend/2026-04-23-enterprise-base-info-add-dialog-backend-implementation.md new file mode 100644 index 00000000..a48f171f --- /dev/null +++ b/docs/plans/backend/2026-04-23-enterprise-base-info-add-dialog-backend-implementation.md @@ -0,0 +1,50 @@ +# 实体库管理新增弹窗后端实施计划 + +## 文档路径确认 + +- 后端实施计划保存路径:`docs/plans/backend/` +- 本文档文件名:`2026-04-23-enterprise-base-info-add-dialog-backend-implementation.md` + +## 需求目标 + +- 实体库管理新增场景不再要求前端传入数据来源。 +- 新增时由后端自动写入数据来源,人工新增统一落为 `MANUAL`。 +- 编辑时后端保留原有数据来源,不允许通过请求修改。 +- 经营状态调整为非必填项。 +- 导入模板同步去除数据来源输入列,并将经营状态改为非必填展示。 +- 导入时由后端自动写入 `IMPORT`,不再依赖模板传值。 + +## 实施范围 + +- `CcdiEnterpriseBaseInfoAddDTO` +- `CcdiEnterpriseBaseInfoEditDTO` +- `CcdiEnterpriseBaseInfoServiceImpl` +- `CcdiEnterpriseBaseInfoImportServiceImpl` +- `CcdiEnterpriseBaseInfoExcel` + +## 实施步骤 + +1. 去除新增 DTO 中 `status`、`dataSource` 的必填约束,编辑 DTO 中去除 `status` 的必填约束。 +2. 调整新增服务逻辑,仅校验风险等级和企业来源,新增数据时后端固定写入 `MANUAL`。 +3. 调整编辑服务逻辑,更新时始终沿用数据库中的原始数据来源。 +4. 调整状态字段赋值逻辑,将空白字符串统一收敛为 `null`,避免出现空串脏数据。 +5. 调整导入服务逻辑,去掉对模板数据来源的解析和校验,导入时固定写入 `IMPORT`。 +6. 修改导入模板对象,移除数据来源列,并把“经营状态*”调整为“经营状态”。 +7. 补充/更新单元测试,覆盖新增自动写入 `MANUAL`、编辑保留原数据来源、导入自动写入 `IMPORT`、经营状态非必填和模板表头变更。 + +## 验证方案 + +- 执行定向测试: + `mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiEnterpriseBaseInfoServiceImplTest,CcdiEnterpriseBaseInfoImportServiceImplTest,EasyExcelUtilTemplateTest test` +- 通过真实后端接口验证: + - 新增不传 `dataSource`、不传 `status` 仍可成功 + - 查询结果中 `dataSource=MANUAL` + - 删除测试数据成功 +- 下载导入模板并核对首行表头: + - 包含 `经营状态` + - 不包含 `数据来源` + +## 风险与注意事项 + +- 当前工作区存在其他未提交改动,本次仅处理实体库管理相关文件,不回退无关内容。 +- 导入模板对象同时承担导入解析职责,本次变更后模板和导入字段保持一致。 diff --git a/docs/plans/frontend/2026-04-23-enterprise-base-info-add-dialog-frontend-implementation.md b/docs/plans/frontend/2026-04-23-enterprise-base-info-add-dialog-frontend-implementation.md new file mode 100644 index 00000000..ed2067ca --- /dev/null +++ b/docs/plans/frontend/2026-04-23-enterprise-base-info-add-dialog-frontend-implementation.md @@ -0,0 +1,41 @@ +# 实体库管理新增弹窗前端实施计划 + +## 文档路径确认 + +- 前端实施计划保存路径:`docs/plans/frontend/` +- 本文档文件名:`2026-04-23-enterprise-base-info-add-dialog-frontend-implementation.md` + +## 需求目标 + +- 实体库管理新增弹窗隐藏“数据来源”字段。 +- 经营状态改为非必填。 +- 编辑弹窗仍保留数据来源展示,但不允许修改。 +- 导入入口沿用现有交互,但模板下载出的字段定义要与后端保持一致。 + +## 实施范围 + +- `ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue` + +## 实施步骤 + +1. 在表单层按 `isAdd` 区分新增与编辑模式,新增时隐藏“数据来源”表单项。 +2. 去除经营状态前端必填校验。 +3. 编辑态将数据来源改为只读展示,避免用户误改。 +4. 调整数据来源校验逻辑,仅用于保持表单已有值,不提供编辑能力。 +5. 新增提交时从请求载荷中删除 `dataSource` 字段,避免旧表单状态误传。 +6. 保留列表、详情、编辑页中的数据来源展示,避免影响已有查看能力。 + +## 验证方案 + +- 使用 `nvm use` 切换到仓库要求的 Node 版本后启动前端: + `npm run dev` +- 浏览器进入 `实体库管理` 页面,核对: + - 新增弹窗没有“数据来源”字段 + - 经营状态没有必填星号 + - 风险等级、企业来源仍为必填 + - 导入弹窗可正常打开 + +## 风险与注意事项 + +- 当前页面文件存在其他在途改动,本次仅追加最小范围修改,不覆盖无关调整。 +- 新增与编辑共用同一个表单对象,需要通过条件渲染和条件校验避免互相影响。 diff --git a/docs/reports/implementation/2026-04-23-enterprise-base-info-add-dialog-implementation.md b/docs/reports/implementation/2026-04-23-enterprise-base-info-add-dialog-implementation.md new file mode 100644 index 00000000..96a3499c --- /dev/null +++ b/docs/reports/implementation/2026-04-23-enterprise-base-info-add-dialog-implementation.md @@ -0,0 +1,80 @@ +# 实体库管理新增弹窗与导入模板调整实施记录 + +## 文档路径确认 + +- 实施记录保存路径:`docs/reports/implementation/` +- 本文档文件名:`2026-04-23-enterprise-base-info-add-dialog-implementation.md` + +## 本次修改内容 + +- 新增弹窗隐藏“数据来源”字段。 +- 新增时后端自动写入 `MANUAL`,不再依赖前端传值。 +- 编辑时数据来源改为只读展示,后端更新时保留原值不变。 +- 经营状态改为非必填,空值统一按 `null` 落库。 +- 导入模板移除“数据来源”列,并将“经营状态*”改为“经营状态”。 +- 导入时后端自动写入 `IMPORT`。 + +## 影响范围 + +- 后端: + - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoAddDTO.java` + - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiEnterpriseBaseInfoEditDTO.java` + - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiEnterpriseBaseInfoExcel.java` + - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoServiceImpl.java` + - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiEnterpriseBaseInfoImportServiceImpl.java` +- 前端: + - `ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue` +- 测试: + - `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoServiceImplTest.java` + - `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiEnterpriseBaseInfoImportServiceImplTest.java` + - `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java` + +## 验证结果 + +### 1. 定向单元测试 + +- 命令: + `mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiEnterpriseBaseInfoServiceImplTest,CcdiEnterpriseBaseInfoImportServiceImplTest,EasyExcelUtilTemplateTest test` +- 结果: + 17 个定向测试全部通过。 + +### 补充调整 + +- 根据追加要求,编辑弹窗中的数据来源已改为不可修改展示。 +- 后端更新接口忽略请求里的 `dataSource` 变更,统一沿用数据库原值。 + +### 2. 真实后端接口验证 + +- 使用 `/login/test` 获取 token 后,调用新增接口仅传: + - `socialCreditCode` + - `enterpriseName` + - `riskLevel` + - `entSource` +- 结果: + - 新增成功 + - 查询结果中 `status=null` + - 查询结果中 `dataSource=MANUAL` + - 删除测试数据成功 + +### 3. 导入模板实际下载校验 + +- 通过真实接口下载模板文件到 `output/spreadsheet/enterprise-base-info-template-20260423.xlsx` +- 核对首行表头结果: + - 包含 `经营状态` + - 包含 `风险等级*` + - 包含 `企业来源*` + - 不包含 `数据来源` + +### 4. 浏览器实际页面验证 + +- 前端按仓库要求执行 `nvm use`,确认使用 `Node v14.21.3` +- 启动 `ruoyi-ui` 开发服务并用 Playwright 打开真实页面 `实体库管理` +- 实际观察结果: + - 新增弹窗中“数据来源”字段已隐藏 + - 经营状态不再显示为必填项 + - 导入入口可正常打开 + +## 过程说明 + +- 验证时发现 `62318` 初始运行的是旧后端进程,仍返回“经营状态不能为空”,随后已按规范使用 `bin/restart_java_backend.sh restart` 重启后端并完成回归验证。 +- 浏览器内尝试直接提交测试数据时页面没有明确反馈,因此补充了真实接口新增/查询/删除验证来完成链路闭环,并已清理测试数据。 diff --git a/ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue b/ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue index 8ef6ba33..d6b9dbeb 100644 --- a/ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue +++ b/ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue @@ -1,84 +1,102 @@