feat(ai-report): 新增委托单位管理和报告审批功能

- 新增委托单位管理模块,支持增删改查、导入导出功能
- 实现委托单位 Excel 模板下载和数据导入功能
- 添加报告审批抽象处理器,支持审批流程日志记录
- 统一文件存储路径配置,将各模块独立路径改为统一根目录
- 新增 PQDIF 文件名存储字段,完善文件管理功能
- 修复代码中的乱码问题和错误提示信息
- 优化系统常量定义和字典配置管理
This commit is contained in:
2026-07-01 13:15:57 +08:00
parent 0ea686d3cb
commit c1ad7feec2
99 changed files with 6511 additions and 24 deletions

View File

@@ -18,9 +18,10 @@ import java.util.UUID;
public class PqdifFileStorageService {
private static final String DEFAULT_STORAGE_DIR = "data/parse-pqdif";
private static final String PQDIF_STORAGE_DIR = "pqdif-files";
@Value("${parse-pqdif.storage.path:}")
private String storagePath;
@Value("${files.path:}")
private String filesPath;
public String save(MultipartFile pqdifFile) {
if (pqdifFile == null || pqdifFile.isEmpty()) {
@@ -47,8 +48,8 @@ public class PqdifFileStorageService {
}
private Path resolveStorageDir() {
String configuredPath = trimToNull(storagePath);
Path path = configuredPath == null ? Paths.get(DEFAULT_STORAGE_DIR) : Paths.get(configuredPath);
String configuredPath = trimToNull(filesPath);
Path path = configuredPath == null ? Paths.get(DEFAULT_STORAGE_DIR) : Paths.get(configuredPath, PQDIF_STORAGE_DIR);
return path.toAbsolutePath().normalize();
}

View File

@@ -145,6 +145,7 @@ public class CsPqdifPathController extends BaseController {
private void fillPqdifFilePath(CsPqdifPathParam param, MultipartFile pqdifFile) {
param.setFilePath(pqdifFileStorageService.save(pqdifFile));
param.setFileName(resolveFileName(pqdifFile));
}
private String resolveFileName(MultipartFile pqdifFile) {

View File

@@ -8,6 +8,7 @@
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="filePath" property="filePath"/>
<result column="fileName" property="fileName"/>
<result column="recordCount" property="recordCount"/>
<result column="observationCount" property="observationCount"/>
<result column="sampleValueCount" property="sampleValueCount"/>
@@ -27,6 +28,7 @@
ID AS id,
Name AS name,
File_Path AS filePath,
File_Name AS fileName,
Record_Count AS recordCount,
Observation_Count AS observationCount,
Sample_Value_Count AS sampleValueCount,
@@ -60,7 +62,8 @@
SELECT
ID AS id,
Name AS name,
File_Path AS filePath
File_Path AS filePath,
File_Name AS fileName
FROM cs_pqdif_path
WHERE ID = #{id}
AND State = 1

View File

@@ -23,6 +23,10 @@ public class CsPqdifPathParam {
@JsonIgnore
private String filePath;
@ApiModelProperty("PQDIF鍘熷鏂囦欢鍚?")
@JsonIgnore
private String fileName;
/**
* PQDIF 存储记录编辑参数。
*/

View File

@@ -28,6 +28,9 @@ public class CsPqdifPathPO implements Serializable {
@TableField("File_Path")
private String filePath;
@TableField("File_Name")
private String fileName;
@TableField("Record_Count")
private Long recordCount;

View File

@@ -19,4 +19,7 @@ public class CsPqdifPathDetailVO {
@ApiModelProperty("PQDIF原始文件存储路径")
private String filePath;
@ApiModelProperty("PQDIF鍘熷鏂囦欢鍚?")
private String fileName;
}

View File

@@ -23,6 +23,9 @@ public class CsPqdifPathVO {
@ApiModelProperty("PQDIF原始文件存储路径")
private String filePath;
@ApiModelProperty("PQDIF鍘熷鏂囦欢鍚?")
private String fileName;
@ApiModelProperty("Record总数")
private Long recordCount;

View File

@@ -122,6 +122,7 @@ public class CsPqdifPathServiceImpl implements CsPqdifPathService {
CsPqdifPathPO pqdifPath = new CsPqdifPathPO();
pqdifPath.setName(requireText(param.getName(), "PQDIF名称不能为空"));
pqdifPath.setFilePath(trimToNull(param.getFilePath()));
pqdifPath.setFileName(trimToNull(param.getFileName()));
return pqdifPath;
}

View File

@@ -0,0 +1,28 @@
package com.njcn.gather.tool.parsepqdif.component;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PqdifFileStorageServiceTest {
@Test
public void saveUsesPqdifFilesDirectoryUnderFilesRoot() throws Exception {
Path filesRoot = Files.createTempDirectory("files-root");
PqdifFileStorageService service = new PqdifFileStorageService();
ReflectionTestUtils.setField(service, "filesPath", filesRoot.toString());
String storedPath = service.save(new MockMultipartFile("pqdifFile", "sample.pqd",
"application/octet-stream", "content".getBytes("UTF-8")));
Path expectedRoot = filesRoot.resolve("pqdif-files").toAbsolutePath().normalize();
Path actualPath = Paths.get(storedPath).toAbsolutePath().normalize();
Assert.assertTrue(actualPath.startsWith(expectedRoot));
Assert.assertTrue(Files.exists(actualPath));
}
}

View File

@@ -0,0 +1,61 @@
package com.njcn.gather.tool.parsepqdif.service.impl;
import com.njcn.gather.tool.parsepqdif.mapper.CsPqdifPathMapper;
import com.njcn.gather.tool.parsepqdif.pojo.param.CsPqdifPathParam;
import com.njcn.gather.tool.parsepqdif.pojo.po.CsPqdifPathPO;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathDetailVO;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class CsPqdifPathServiceImplTest {
@Test
public void addPqdifPathShouldPersistFileName() {
CsPqdifPathMapper mapper = mock(CsPqdifPathMapper.class);
when(mapper.insert(any(CsPqdifPathPO.class))).thenReturn(1);
CsPqdifPathServiceImpl service = new CsPqdifPathServiceImpl(mapper);
CsPqdifPathParam param = new CsPqdifPathParam();
param.setName("sample");
param.setFilePath("D:/files/pqdif-files/uuid_sample.pqd");
param.setFileName("sample.pqd");
boolean result = service.addPqdifPath(param);
Assert.assertTrue(result);
ArgumentCaptor<CsPqdifPathPO> captor = ArgumentCaptor.forClass(CsPqdifPathPO.class);
verify(mapper).insert(captor.capture());
Assert.assertEquals("sample.pqd", captor.getValue().getFileName());
}
@Test
public void updatePqdifPathShouldPersistFileName() {
CsPqdifPathMapper mapper = mock(CsPqdifPathMapper.class);
CsPqdifPathDetailVO detail = new CsPqdifPathDetailVO();
detail.setId("pqdif-id");
when(mapper.selectPqdifPathDetailById("pqdif-id")).thenReturn(detail);
when(mapper.updateById(any(CsPqdifPathPO.class))).thenReturn(1);
CsPqdifPathServiceImpl service = new CsPqdifPathServiceImpl(mapper);
CsPqdifPathParam.UpdateParam param = new CsPqdifPathParam.UpdateParam();
param.setId("pqdif-id");
param.setName("sample");
param.setFilePath("D:/files/pqdif-files/uuid_sample_new.pqd");
param.setFileName("sample-new.pqd");
boolean result = service.updatePqdifPath(param);
Assert.assertTrue(result);
ArgumentCaptor<CsPqdifPathPO> captor = ArgumentCaptor.forClass(CsPqdifPathPO.class);
verify(mapper).updateById(captor.capture());
Assert.assertEquals("sample-new.pqd", captor.getValue().getFileName());
verify(mapper).selectPqdifPathDetailById(eq("pqdif-id"));
}
}