master
杨海航 3 weeks ago
parent 7a73da34f2
commit 4a9e59d15d
  1. 2
      dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/EntrustInfoServiceImpl.java
  2. 519
      dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/InspectRecordServiceImpl.java

@ -110,7 +110,7 @@ public class EntrustInfoServiceImpl extends ServiceImpl<EntrustInfoMapper, Entru
.eq(StringUtils.isNotBlank(entrustInfo.getId()), EntrustInfo::getId, entrustInfo.getId())
.like(StringUtils.isNotBlank(entrustInfo.getCaseName()), EntrustInfo::getCaseName, entrustInfo.getCaseName())
.eq(entrustInfo.getStatus() != null, EntrustInfo::getStatus, status)
.orderByDesc(EntrustInfo::getCreateTime));
.last("ORDER BY CAST(SUBSTRING_INDEX(accept_no,'-',1) AS UNSIGNED)DESC,CAST(SUBSTRING_INDEX(accept_no,'-',-1) AS UNSIGNED) DESC"));
List<EntrustInfo> records = ret.getRecords();
for (EntrustInfo info : records) {
List<AssignmentInfo> list = assignmentInfoService.list(Wrappers.<AssignmentInfo>lambdaQuery().eq(AssignmentInfo::getBusinessId, info.getId()));

@ -128,22 +128,32 @@ public class InspectRecordServiceImpl implements InspectRecordService {
System.out.println(String.format("转换为 PDF 结束"));
}
/**
* 构建体内检验记录数据
*
* @param entrustInfo 委托信息对象
* @param materialType 材料类型如血液尿液等
* @return 包含体内检验记录的Map数据
* @throws Exception 如果未找到检验记录则抛出异常
*/
public Map<String, Object> buildInVivoRecordData(EntrustInfo entrustInfo, String materialType) throws Exception {
// 获取检验记录信息
// 1 获取当前委托业务对应的检验记录信息
TestRecord testRecord = testRecordService.getTestRecordByBusinessId(entrustInfo.getId());
// 2 如果找不到对应的检验记录,则抛出异常
if (testRecord == null) {
throw new Exception("未找到检验记录信息");
}
// 生成基础数据
// 3 生成基础数据,构建通用的检验记录文档数据
HashMap<String, Object> data = buildCommonInspectRecordDocMap(entrustInfo, testRecord, materialType);
// 获取样品数据
// 4 查询该检验记录下的所有样品数据
List<TestRecordSampleData> dataList = testRecordSampleDataService.list(
Wrappers.<TestRecordSampleData>lambdaQuery().eq(TestRecordSampleData::getTestId, testRecord.getId()));
// 5 如果没有样品数据,则直接返回空数据
if (CollectionUtils.isEmpty(dataList)) {
data.put("dataDtos", Collections.emptyList());
data.put("sampleSize", 0);
@ -151,41 +161,48 @@ public class InspectRecordServiceImpl implements InspectRecordService {
return data;
}
// 批量查询所有样品扩展数据,避免多次查询数据库
// 6 批量查询所有样品扩展数据,避免多次查询数据库,提高效率
Map<String, List<TestRecordSampleDataExpand>> dataExpandMap = testRecordSampledataExpandService.list(
Wrappers.<TestRecordSampleDataExpand>lambdaQuery()
.in(TestRecordSampleDataExpand::getTestDataId, dataList.stream().map(TestRecordSampleData::getId).collect(Collectors.toList()))
).stream().collect(Collectors.groupingBy(TestRecordSampleDataExpand::getTestDataId));
// 根据化合物名称分组
// 7 按化合物名称进行分组,确保同一化合物的数据在一起处理
Map<String, List<TestRecordSampleData>> dataMap = dataList.stream()
.collect(Collectors.groupingBy(TestRecordSampleData::getCompoundCnName));
// 8 获取标准品列表,并按照其中的化合物顺序进行排序
String referenceMaterialName = (String) data.get("referenceMaterialName");
List<String> collect = Arrays.stream(referenceMaterialName.split("\n")) // 按换行拆分
.map(line -> line.replaceFirst("^\u3000\u3000\u2611 ", "") // 去掉前缀 "  ✔ "
.replaceFirst("^\u2611 ", "")) // 去掉前缀 "✔ "
.collect(Collectors.toList()); // 转换为列表
// 9 用于存储最终的检验数据
List<TestRecordSampleDataDocDTO> dataDtos = new ArrayList<>();
// 遍历化合物组
for (Map.Entry<String, List<TestRecordSampleData>> entry : dataMap.entrySet()) {
String compoundName = entry.getKey();
List<TestRecordSampleData> list = entry.getValue();
// 🔟 遍历化合物列表,按照标准品顺序构建数据
for (String compoundName : collect) {
List<TestRecordSampleData> list = dataMap.get(compoundName);
// 添加空白数据
// 10.1添加一个空白样品数据
TestRecordSampleDataDocDTO blankVo = new TestRecordSampleDataDocDTO();
blankVo.setName("空白" + materialType);
blankVo.setCompoundName(compoundName);
blankVo.setPTargetRtTime("/"); // 设置空值为"/"
blankVo.setPRtTimeError("/"); // 设置空值为"/"
blankVo.setPRtTimeWithinError("否"); // 默认"否"
blankVo.setPIonAbundanceRatio("/"); // 设置空值为"/"
blankVo.setPIonAbundanceRatioError("/"); // 设置空值为"/"
blankVo.setPIonAbundanceRatioWithinError("否"); // 默认"否"
blankVo.setPIsDetected("否"); // 默认"否"
blankVo.setPTargetRtTime("/");
blankVo.setPRtTimeError("/");
blankVo.setPRtTimeWithinError("否");
blankVo.setPIonAbundanceRatio("/");
blankVo.setPIonAbundanceRatioError("/");
blankVo.setPIonAbundanceRatioWithinError("否");
blankVo.setPIsDetected("否");
dataDtos.add(blankVo);
// 根据样品类型分组
// 10.2 按样品类型(STD:标准样品,Analyte:检材样品)分组
Map<String, List<TestRecordSampleData>> map = list.stream()
.collect(Collectors.groupingBy(TestRecordSampleData::getSampleType));
// 处理标准样品(STD)
// 11 处理标准样品(STD)
if (map.containsKey("STD")) {
TestRecordSampleData std = map.get("STD").get(0);
TestRecordSampleDataDocDTO stdVo = new TestRecordSampleDataDocDTO();
@ -196,22 +213,22 @@ public class InspectRecordServiceImpl implements InspectRecordService {
for (TestRecordSampleDataExpand expand : expandList) {
if (!expand.getBasePeak()) {
stdVo.setPIonAbundanceRatio(expand.getIonAbundanceRatio() != null ? expand.getIonAbundanceRatio().setScale(2, RoundingMode.HALF_UP).toString() : "/");
stdVo.setPIonAbundanceRatioError("/"); // 设置空值为"/"
stdVo.setPIonAbundanceRatioWithinError("/"); // 设置空值为"/"
stdVo.setPIonAbundanceRatioError("/");
stdVo.setPIonAbundanceRatioWithinError("/");
break; // 只取第一个符合条件的扩展数据
}
}
}
stdVo.setName("空白" + materialType + "加标");
stdVo.setPTargetRtTime(std.getTargetRtTime() != null ? String.format("%.2f", std.getTargetRtTime()) : "/");
stdVo.setPRtTimeError("/"); // 设置空值为"/"
stdVo.setPRtTimeWithinError("/"); // 设置空值为"/"
stdVo.setPIsDetected("是"); // 默认"是"
stdVo.setPRtTimeError("/");
stdVo.setPRtTimeWithinError("/");
stdVo.setPIsDetected("是");
stdVo.setCompoundName(compoundName);
dataDtos.add(stdVo);
}
// 处理检材样品(Analyte)
// 12 处理检材样品(Analyte)
if (map.containsKey("Analyte")) {
List<TestRecordSampleData> analyte = map.get("Analyte");
analyte.sort(testRecordSampleDataService.getSortBySampleNo());
@ -227,24 +244,7 @@ public class InspectRecordServiceImpl implements InspectRecordService {
vo.setPRtTimeWithinError(item.getRtTimeWithinError() != null ? item.getRtTimeWithinError().toString() : "/");
vo.setPIsDetected(item.getIsDetected() != null && item.getIsDetected() == 1 ? "是" : "否");
List<TestRecordSampleDataExpand> expandList = dataExpandMap.get(item.getId());
if (expandList != null) {
for (TestRecordSampleDataExpand expand : expandList) {
if (!expand.getBasePeak()) {
vo.setPIonAbundanceRatio(expand.getIonAbundanceRatio() != null ? expand.getIonAbundanceRatio().setScale(2, RoundingMode.HALF_UP).toString() : "/");
vo.setPIonAbundanceRatioError(expand.getIonAbundanceRatioError() != null ? expand.getIonAbundanceRatioError().setScale(2, RoundingMode.HALF_UP).toString() : "/");
vo.setPIonAbundanceRatioWithinError(expand.getIonAbundanceRatioWithinError() != null ? expand.getIonAbundanceRatioWithinError() : "/");
break; // 只取第一个符合条件的扩展数据
}
}
}
// 重新命名样品
vo.setName((dataList
.stream()
.collect(Collectors.groupingBy(TestRecordSampleData::getSampleNo))
.keySet()
.size() == 1) ? "检材样品" : (i + 1) + "号检材样品");
vo.setName((dataList.stream().collect(Collectors.groupingBy(TestRecordSampleData::getSampleNo)).keySet().size() == 1) ? "检材样品" : (i + 1) + "号检材样品");
vo.setCompoundName(compoundName);
dataVOS.add(vo);
}
@ -252,28 +252,25 @@ public class InspectRecordServiceImpl implements InspectRecordService {
}
}
// 加入图谱序号
// 13 为所有数据项添加序号
int indexNum = 1;
for (int i = 0; i < dataDtos.size(); i++) {
dataDtos.get(i).setIndexNum(String.valueOf(indexNum++));
for (TestRecordSampleDataDocDTO dto : dataDtos) {
dto.setIndexNum(String.valueOf(indexNum++));
}
this.buildIonPairAndCE(data, testRecordReagentService
.list(Wrappers.<TestRecordReagent>lambdaQuery()
// 14 构建离子对和CE值数据
this.buildIonPairAndCE(data, testRecordReagentService.list(
Wrappers.<TestRecordReagent>lambdaQuery()
.in(TestRecordReagent::getId, testRecord.getReagentConsumablesList())
.eq(TestRecordReagent::getCategory, "标准物质"))
.stream()
.map(item -> item.getId())
.collect(Collectors.toList()));
.stream().map(TestRecordReagent::getId).collect(Collectors.toList()));
// 返回处理的数据
// 15 返回最终处理的数据
data.put("dataDtos", dataDtos);
data.put("type", "inVivo");
data.put("inspectOpinion", this.buildInspectOpinion(testRecordSampleDataService
.lambdaQuery()
data.put("type", materialType);
data.put("inspectOpinion", this.buildInspectOpinion(testRecordSampleDataService.lambdaQuery()
.eq(TestRecordSampleData::getTestId, testRecord.getId())
.eq(TestRecordSampleData::getSampleType, "Analyte")
.list()));
.eq(TestRecordSampleData::getSampleType, "Analyte").list()));
return data;
}
@ -425,116 +422,163 @@ public class InspectRecordServiceImpl implements InspectRecordService {
// 9 返回处理后的数据
data.put("dataDtos", dataDtos);
data.put("sampleSize", dataDtos.size());
data.put("type", "inVitro");
try {
Map<Integer, List<TestRecordSampleData>> isDetectedMap = testRecordSampleDataService
// 10 添加检测结果与分析结果判定
List<TestRecordSampleData> sampleDataList = testRecordSampleDataService
.lambdaQuery()
.eq(TestRecordSampleData::getTestId, testRecord.getId())
.eq(TestRecordSampleData::getSampleType, "Analyte")
.list()
.stream()
.filter(Objects::nonNull) // 避免空值
.list();
data.put("type", "常规毒品");
data.put("inspectOpinion", sampleDataList.size() > 0 ? this.buildInspectOpinion(sampleDataList) : "");
this.processDetectionResults(data, testRecord);
return data;
}
/**
* 📌 处理检测结果并格式化
*
* @param data 存储检测结果的 Map
* @param testRecord 当前检测记录
*/
public void processDetectionResults(Map<String, Object> data, TestRecord testRecord) {
// 查询所有 "Analyte" 类型的样本数据
List<TestRecordSampleData> analyteDataList = testRecordSampleDataService
.lambdaQuery()
.eq(TestRecordSampleData::getTestId, testRecord.getId())
.eq(TestRecordSampleData::getSampleType, "Analyte")
.list();
if (analyteDataList.isEmpty()) {
return; // 如果没有数据,直接返回
}
// 按 `isDetected` 分组,1 为检出,0 为未检出
Map<Integer, List<TestRecordSampleData>> isDetectedMap = analyteDataList.stream()
.filter(Objects::nonNull)
.collect(Collectors.groupingBy(TestRecordSampleData::getIsDetected));
List<String> isDetectedStrs = new ArrayList<>();
List<String> notDetectedStrs = new ArrayList<>();
List<String> isDetectedStr = new ArrayList<>();
List<String> notDetectedStr = new ArrayList<>();
// 遍历检测结果,生成描述
for (Map.Entry<Integer, List<TestRecordSampleData>> entry : isDetectedMap.entrySet()) {
if (entry.getKey() != null && entry.getValue() != null && !entry.getValue().isEmpty()) {
List<TestRecordSampleData> value = entry.getValue();
Map<String, List<TestRecordSampleData>> map = value.stream()
.filter(Objects::nonNull)
.filter(item -> item.getCompoundName() != null && item.getSampleNo() != null)
List<TestRecordSampleData> samples = entry.getValue();
if (samples == null || samples.isEmpty()) {
continue; // 如果样本数据为空,跳过
}
// 按化合物名称分组样本
Map<String, List<TestRecordSampleData>> compoundSampleMap = samples.stream()
.filter(sample -> sample.getCompoundCnName() != null && sample.getSampleNo() != null)
.collect(Collectors.groupingBy(TestRecordSampleData::getCompoundCnName));
for (Map.Entry<String, List<TestRecordSampleData>> listEntry : map.entrySet()) {
if (listEntry.getKey() != null && listEntry.getValue() != null && !listEntry.getValue().isEmpty()) {
List<TestRecordSampleData> entryValue = listEntry.getValue();
entryValue.sort(testRecordSampleDataService.getSortBySampleNo());
for (Map.Entry<String, List<TestRecordSampleData>> compoundEntry : compoundSampleMap.entrySet()) {
String compoundName = compoundEntry.getKey();
List<TestRecordSampleData> compoundSamples = compoundEntry.getValue();
if (compoundSamples == null || compoundSamples.isEmpty()) {
continue; // 如果化合物名称或样本为空,跳过
}
String collect = entryValue.stream()
.map(item -> {
String[] parts = item.getSampleNo().split("-");
return (parts.length > 2) ? parts[2] + "号" : item.getSampleNo(); // 预防数组越界
// 按 `sampleNo` 排序样本
compoundSamples.sort(testRecordSampleDataService.getSortBySampleNo());
// 拼接样本编号
String sampleNos = compoundSamples.stream()
.map(sample -> {
String[] parts = sample.getSampleNo().split("-");
return (parts.length > 2) ? parts[2] + "号" : sample.getSampleNo(); // 防止数组越界
})
.collect(Collectors.joining("、"));
// 生成描述
String description;
if (entry.getKey() == 1) {
description = "☑ " + collect + "样品空白对照无干扰,在相同条件下进行测定时,样品溶液中目标物与浓度相近的标准工作溶液中的目标物相比,色谱峰保留时间一致、质谱特征离子一致,各离子丰度比相对偏差符合规定范围,判定样品中检出" + listEntry.getKey() + "。";
isDetectedStrs.add(description);
description = String.format(
"☑ %s样品空白对照无干扰,在相同条件下进行测定时,样品溶液中目标物与浓度相近的标准工作溶液中的目标物相比," +
"色谱峰保留时间一致、质谱特征离子一致,各离子丰度比相对偏差符合规定范围,判定样品中检出%s。",
sampleNos, compoundName);
isDetectedStr.add(description);
} else {
description = "☑ " + collect + "样品空白对照无干扰,样品溶液中未检出符合阳性结果评价要求的色谱峰,标准溶液空白无干扰,判定样品中为未检出" + listEntry.getKey() + "。";
notDetectedStrs.add(description);
}
}
description = String.format(
"☑ %s样品空白对照无干扰,样品溶液中未检出符合阳性结果评价要求的色谱峰,标准溶液空白无干扰,判定样品中未检出%s。",
sampleNos, compoundName);
notDetectedStr.add(description);
}
}
}
if (!isDetectedStrs.isEmpty()) {
data.put("isDetectedStrs", "阳性结果:" + "\n" + isDetectedStrs.stream()
.map(item -> "\u3000\u3000" + item)
// 将阳性结果和阴性结果放入数据 Map 中
if (!isDetectedStr.isEmpty()) {
data.put("isDetectedStrs", "阳性结果:" + "\n" + isDetectedStr.stream()
.map(item -> "  " + item) // 使用全角空格缩进
.collect(Collectors.joining("\n")));
}
if (!notDetectedStrs.isEmpty()) {
data.put("notDetectedStrs", "阴性结果:" + "\n" + notDetectedStrs.stream()
.map(item -> "\u3000\u3000" + item)
if (!notDetectedStr.isEmpty()) {
data.put("notDetectedStrs", "阴性结果:" + "\n" + notDetectedStr.stream()
.map(item -> "  " + item) // 使用全角空格缩进
.collect(Collectors.joining("\n")));
}
} catch (Exception e) {
}
List<TestRecordSampleData> sampleDataList = testRecordSampleDataService
.lambdaQuery()
.eq(TestRecordSampleData::getTestId, testRecord.getId())
.eq(TestRecordSampleData::getSampleType, "Analyte")
.list();
// 添加非空校验,防止传入 `null` 进入 `buildInspectOpinion`
data.put("inspectOpinion", sampleDataList.size() > 0 ? this.buildInspectOpinion(sampleDataList) : "");
return data;
}
/**
* 📌 构建离子对和碰撞能量信息并存入数据 Map
*
* @param data 存储结果的 Map
* @param reagentIdList 试剂 ID 列表
*/
public void buildIonPairAndCE(Map<String, Object> data, List<String> reagentIdList) {
List<IonPairAndCEVO> ionPairAndCEVOS = new ArrayList<>();
// 遍历 reagentIdList
// 🔄 遍历 reagentIdList 获取试剂信息
for (String id : reagentIdList) {
TestRecordReagentVO vo = testRecordReagentService.getVOById(id);
if (vo == null) {
continue; // 如果 vo 为空,跳过当前循环
continue; // 如果 vo 为空,跳过当前循环
}
Drug drug = vo.getDrug();
if (drug == null) {
continue; // 如果 drug 为空,跳过当前循环
continue; // 如果 drug 为空,跳过当前循环
}
// 添加 ionPairAndCEVO1 和 ionPairAndCEVO2
// 📌 创建主产物离子信息并添加到列表
ionPairAndCEVOS.add(createIonPairAndCEVO(drug.getName(), drug.getMainProductIon(),
drug.getMainDeclusteringPotential(), drug.getMainCollisionEnergy()));
// 📌 创建次产物离子信息并添加到列表
ionPairAndCEVOS.add(createIonPairAndCEVO(drug.getName(), drug.getMinorProductIon(),
drug.getMinorDeclusteringPotential(), drug.getMinorCollisionEnergy()));
}
// 将结果放入数据 map 中
// 📌 将结果放入数据 map 中,供后续使用
data.put("ionPairAndCEVOS", ionPairAndCEVOS);
data.put("ionPairAndCEVOSize", ionPairAndCEVOS.size() / 2);
}
// 创建 IonPairAndCEVO 对象的辅助方法
/**
* 📌 创建 IonPairAndCEVO 对象并初始化属性
*
* @param compoundName 化合物名称
* @param productIon 产物离子
* @param declusteringPotential 去簇电位DP
* @param collisionEnergy 碰撞能量CE
* @return 初始化后的 IonPairAndCEVO 对象
*/
private IonPairAndCEVO createIonPairAndCEVO(String compoundName, String productIon,
Integer declusteringPotential, Integer collisionEnergy) {
IonPairAndCEVO ionPairAndCEVO = new IonPairAndCEVO();
ionPairAndCEVO.setCompoundName(compoundName);
ionPairAndCEVO.setProductIon(productIon);
ionPairAndCEVO.setDeclusteringPotential(declusteringPotential);
ionPairAndCEVO.setCollisionEnergy(collisionEnergy);
return ionPairAndCEVO;
ionPairAndCEVO.setCompoundName(compoundName); // 设置化合物名称
ionPairAndCEVO.setProductIon(productIon); // 设置产物离子
ionPairAndCEVO.setDeclusteringPotential(declusteringPotential); // 设置去簇电位(DP)
ionPairAndCEVO.setCollisionEnergy(collisionEnergy); // 设置碰撞能量(CE)
return ionPairAndCEVO; // 返回创建的对象
}
@ -556,7 +600,6 @@ public class InspectRecordServiceImpl implements InspectRecordService {
} else {
templatePath = "/template" + "/贵阳生物样本尿液检验记录模板.docx";
}
System.out.println(data);
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
Configure config = Configure.builder().
bind("dataDtos", policy)
@ -565,106 +608,134 @@ public class InspectRecordServiceImpl implements InspectRecordService {
}
/**
* 根据委托信息模板路径配置和数据构建文档文件并上传到OSS
* 📄 构建 Word 文档并上传至 OSS
*
* @param entrustId 委托id
* @param entrustId 委托 ID
* @param templatePath 模板文件路径
* @param config 配置信息
* @param data 要填充到模板中的数据
* @return 上传的文件路径
* @param config 配置对象
* @param data 数据映射
* @return 上传 OSS 的文件路径
* @throws Exception 可能抛出的异常
*/
private String buildDocFileAndUploadToOss(String entrustId, String templatePath, Configure config, Map<String, Object> data) throws Exception {
// 📌 读取模板文件
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ossFile.fileGet(templatePath, bos);
byte[] templateArray = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(templateArray);
bos.close();
try (ByteArrayInputStream bis = new ByteArrayInputStream(templateArray);
ByteArrayOutputStream fosWord = new ByteArrayOutputStream()) {
// 📌 渲染模板数据
XWPFTemplate template = XWPFTemplate.compile(bis, config).render(data);
NiceXWPFDocument document = template.getXWPFDocument();
List<XWPFTable> tables = document.getTables();
// 📌 读取数据类型并安全处理
String type = (String) data.getOrDefault("type", ""); // 避免空指针
XWPFTable table = tables.get(tables.size() - 1); // 获取最后一个表格
if ("常规毒品".equals(type)) {
mergeRegularDrugTable(table);
} else if ("尿液".equals(type) || "毛发".equals(type)) {
mergeNonRegularDrugTable(table);
mergeAdditionalTable(type, tables);
}
// 📌 写入文档流
template.write(fosWord);
template.close();
document.close();
// 📌 构建文件存储路径
ByteArrayInputStream fisWord = new ByteArrayInputStream(fosWord.toByteArray());
String path = TestRecordFileUrl.TEST_RECORD_CATALOGUE.getFileUrl() + "/" + entrustId + "/" + "贵阳生物样本检验记录.docx";
ossFile.fileSave(path, fisWord);
String type = (String) data.get("type");
return path; // 📌 返回 OSS 文件路径
}
}
if (StringUtils.isNotBlank(type) && type.equals("inVitro")) {
int startRow = 1; // 从第二行开始(表头是第一行,索引从0开始)
// **处理合并单元格**
int mergeStep = 4; // 每4行合并一次
int[] mergeColumns = {0, 1, 2, 3, 4, 9}; // 合并的列:第1、2、3、4、5、10列,索引分别是 0, 1, 2, 3, 4, 9
/**
* 📌 合并常规毒品检验记录检测数据部分表格的单元格
*/
private void mergeRegularDrugTable(XWPFTable table) {
int startRow = 1; // 🔹 从第二行开始(索引从0开始)
int mergeStep = 4; // 🔹 每 4 行合并一次
int[] mergeColumns = {0, 1, 2, 3, 4, 9}; // 🔹 需要合并的列
// 遍历表格数据行
for (int rowIndex = startRow; rowIndex < table.getNumberOfRows(); rowIndex += mergeStep) {
int endRow = Math.min(rowIndex + mergeStep - 1, table.getNumberOfRows() - 1); // 计算合并的终止行,防止越界
int endRow = Math.min(rowIndex + mergeStep - 1, table.getNumberOfRows() - 1); // 防止越界
for (int col : mergeColumns) {
TableTools.mergeCellsVertically(table, col, rowIndex, endRow);
}
}
} else {
// **处理合并单元格**
int startRow = 1; // 从第二行开始(表头是第一行,索引从0开始)
}
/**
* 📌 合并生物样本检验记录检测数据部分表格的单元格
*/
private void mergeNonRegularDrugTable(XWPFTable table) {
int startRow = 1;
String currentCompoundName = null;
int mergeStartRow = -1;
for (int rowIndex = startRow; rowIndex < table.getNumberOfRows(); rowIndex++) {
XWPFTableRow row = table.getRow(rowIndex);
XWPFTableCell cell = row.getCell(1); // 获取第二列的单元格(索引1)
XWPFTableCell cell = row.getCell(1); // 🔹 第二列(索引1)
String compoundName = cell.getText().trim();
if (currentCompoundName == null || !currentCompoundName.equals(compoundName)) {
// 如果当前化合物名称与上一行不同,则结束之前的合并
if (!compoundName.equals(currentCompoundName)) {
if (mergeStartRow >= 0 && rowIndex > mergeStartRow + 1) {
TableTools.mergeCellsVertically(table, 1, mergeStartRow, rowIndex - 1); // 合并第二列(列索引1)
TableTools.mergeCellsVertically(table, 1, mergeStartRow, rowIndex - 1);
}
currentCompoundName = compoundName;
mergeStartRow = rowIndex;
}
}
// 处理最后一组合并(循环结束后可能残留未合并的区域)
// 📌 处理最后一组合并
if (mergeStartRow >= 0 && mergeStartRow < table.getNumberOfRows() - 1) {
TableTools.mergeCellsVertically(table, 1, mergeStartRow, table.getNumberOfRows() - 1);
}
}
System.out.println("总共的表格数量:" + tables.size());
XWPFTable xwpfTable = tables.get(2);
/**
* 📌 处理毛发或尿液表格目标物定性离子对碰撞能量 (CE)等参数表格的合并逻辑
* 📌 因为毛发和尿液的配置表格数量不同因此下标不同
*/
private void mergeAdditionalTable(String type, List<XWPFTable> tables) {
if (!"毛发".equals(type) && !"尿液".equals(type)) {
return; // 如果不是毛发或尿液类型,则不处理
}
int tableIndex = "毛发".equals(type) ? 2 : 1;
if (tables.size() <= tableIndex) {
return; // 避免索引越界
}
XWPFTable xwpfTable = tables.get(tableIndex);
List<XWPFTableRow> rows = xwpfTable.getRows();
// 跳过第一行(表头),从第二行开始处理
for (int i = 1; i < rows.size() - 1; i++) { // 注意:i 从 1 开始
for (int i = 1; i < rows.size() - 1; i++) { // 🔹 从第二行开始
XWPFTableRow currentRow = rows.get(i);
XWPFTableRow nextRow = rows.get(i + 1);
// 获取当前行和下一行的化合物名称
String currentCompoundName = currentRow.getCell(0).getText().trim();
String nextCompoundName = nextRow.getCell(0).getText().trim();
// 如果当前行和下一行的化合物名称相同,则合并它们的第一列
if (currentCompoundName.equals(nextCompoundName)) {
// 合并单元格
currentRow.getCell(0).getCTTc().addNewTcPr().addNewVMerge().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART);
nextRow.getCell(0).getCTTc().addNewTcPr().addNewVMerge().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE);
// 📌 合并单元格
currentRow.getCell(0).getCTTc().addNewTcPr().addNewVMerge()
.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART);
nextRow.getCell(0).getCTTc().addNewTcPr().addNewVMerge()
.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE);
// 删除下一行的第一个单元格的内容(可选)
// 📌 清空被合并行的内容
nextRow.getCell(0).setText("");
}
}
bis.close();
ByteArrayOutputStream fosWord = new ByteArrayOutputStream();
template.write(fosWord);
template.close();
ByteArrayInputStream fisWord = new ByteArrayInputStream(fosWord.toByteArray());
fosWord.close();
document.close();
String path = TestRecordFileUrl.TEST_RECORD_CATALOGUE.getFileUrl() + "/" + entrustId + "/" + "贵阳生物样本检验记录.docx";
ossFile.fileSave(path, fisWord);
return path;
}
@ -769,22 +840,23 @@ public class InspectRecordServiceImpl implements InspectRecordService {
.in(TestRecordReagent::getId, testRecord.getReagentConsumablesList())
.eq(TestRecordReagent::getCategory, "标准物质"));
if (references == null || references.isEmpty()) {
data.put("referenceMaterialName", "未找到标准物质数据!");
} else {
String referenceMaterialName = "";
if (references.size() == 1) {
referenceMaterialName = "\u2611" + " " + references.get(0).getReagentConsumableName();
} else {
TestRecordReagent firstReagent = references.get(0);
String firstStr = "\u2611" + " " + references.get(0).getReagentConsumableName() + "\n";
references.remove(references.get(0));
referenceMaterialName = firstStr + references.stream()
.map(reagent -> "\u3000\u3000\u2611" + " " + reagent.getReagentConsumableName())
.collect(Collectors.joining("\n"));
references.add(0, firstReagent);
}
data.put("referenceMaterialName", referenceMaterialName);
data.put("materialIngredient", references.stream()
.map(reagent -> reagent.getReagentConsumableName())
.collect(Collectors.joining("、")));
@ -896,10 +968,29 @@ public class InspectRecordServiceImpl implements InspectRecordService {
*/
private int extractTailNumber(String orderNo) {
String[] parts = orderNo.split("-");
if (parts.length > 0) {
return Integer.parseInt(parts[parts.length - 1]); // 取最后一段
return (parts.length > 0) ? Integer.parseInt(parts[parts.length - 1]) : 0;
}
return 0;
/**
* 对样品编号进行排序
*
* @param sampleNumbers 样品编号列表例如 ["3号", "1号", "2号"]
* @return 排序后的样品编号列表例如 ["1号", "2号", "3号"]
*/
private List<String> sortSampleNumbers(List<String> sampleNumbers) {
return sampleNumbers.stream()
.sorted(Comparator.comparingInt(this::extractNumber))
.collect(Collectors.toList());
}
/**
* 从样品编号中提取数值部分
*
* @param sampleNumber 样品编号例如 "12号"
* @return 提取出的数字例如 12
*/
private int extractNumber(String sampleNumber) {
return sampleNumber.matches("\\d+号") ? Integer.parseInt(sampleNumber.replace("号", "")) : Integer.MAX_VALUE;
}
/**
@ -907,48 +998,74 @@ public class InspectRecordServiceImpl implements InspectRecordService {
* - 单个编号返回 "1号"
* - 连续编号返回 "1号至3号"
*
* @param start 起始编号
* @param end 结束编号
* @return 格式化的编号字符串如果 start == end返回单号
* @param sortedSampleNumbers 已排序的样品编号列表
* @return 格式化的编号字符串
*/
private String formatRange(int start, int end) {
return (start == end) ? start + "号" : start + "号至" + end + "号";
}
private String formatSampleNumbers(List<String> sampleNumbers) {
List<Integer> numbers = sampleNumbers.stream()
private String formatSampleNumbers(List<String> sortedSampleNumbers) {
List<Integer> numbers = sortedSampleNumbers.stream()
.map(s -> s.replace("号", "")) // 移除"号"字
.map(Integer::parseInt) // 转换为整数
.sorted() // 排序
.collect(Collectors.toList());
if (numbers.size() > 1 && numbers.get(numbers.size() - 1) - numbers.get(0) == numbers.size() - 1) {
return numbers.get(0) + "至" + numbers.get(numbers.size() - 1) + "号";
List<String> formattedList = new ArrayList<>();
int start = numbers.get(0), prev = start;
for (int i = 1; i < numbers.size(); i++) {
int current = numbers.get(i);
if (current != prev + 1) {
formattedList.add(formatRange(start, prev));
start = current;
}
prev = current;
}
formattedList.add(formatRange(start, prev));
return String.join("、", sampleNumbers);
return String.join("、", formattedList);
}
/**
* 生成编号范围的字符串
*
* @param start 起始编号
* @param end 结束编号
* @return 生成的范围字符串例如 "1号至3号" "5号"
*/
private String formatRange(int start, int end) {
return (start == end) ? start + "号" : start + "号至" + end + "号";
}
/**
* 构建检测意见根据样品的检出与未检出情况生成描述性文本
*
* @param dataList 检测记录数据列表每个对象包含样品编号化合物名称及是否检出标识
* @return 格式化的检测意见字符串
*/
public String buildInspectOpinion(List<TestRecordSampleData> dataList) {
// 1. **处理空数据情况**
if (dataList == null || dataList.isEmpty()) {
return "";
}
Map<String, List<String>> detectedMap = new LinkedHashMap<>();
Map<String, List<String>> notDetectedMap = new LinkedHashMap<>();
Set<String> allCompoundNames = new HashSet<>();
// 2. **定义存储结构**
Map<String, List<String>> detectedMap = new LinkedHashMap<>(); // 记录检出化合物的样品编号映射(样品编号 -> 该样品检出的化合物列表)
Map<String, List<String>> notDetectedMap = new LinkedHashMap<>(); // 记录未检出化合物的样品编号映射(样品编号 -> 该样品未检出的化合物列表)
Set<String> allCompoundNames = new HashSet<>(); // 存储所有涉及的化合物名称(用于后续分析)
// 3. **遍历数据列表,整理样品的检出与未检出情况**
for (TestRecordSampleData record : dataList) {
String sampleNo = record.getSampleNo();
String compoundName = record.getCompoundCnName();
String sampleNo = record.getSampleNo(); // 样品编号
String compoundName = record.getCompoundCnName(); // 化合物名称
// **跳过无效数据**
if (sampleNo == null || compoundName == null || sampleNo.trim().isEmpty()) {
continue;
}
// **记录化合物名称**
allCompoundNames.add(compoundName);
// **分类存储检出与未检出数据**
if (record.getIsDetected() != null && record.getIsDetected() == 1) {
detectedMap.computeIfAbsent(sampleNo, k -> new ArrayList<>()).add(compoundName);
} else {
@ -956,56 +1073,70 @@ public class InspectRecordServiceImpl implements InspectRecordService {
}
}
// 4. **定义样品编号格式化方法**
// 目标是将形如 "A12"、"B34" 这样的样品编号转换成 "12号"、"34号"
Function<String, String> extractSampleNumber = sampleNo -> sampleNo.matches(".*\\d+")
? sampleNo.replaceAll(".*?(\\d+)$", "$1号")
? sampleNo.replaceAll(".*?(\\d+)$", "$1号") // 只提取最后的数字并添加 "号"
: sampleNo;
// 5. **构建检出化合物的描述**
List<String> detectedSentences = new ArrayList<>();
Map<Set<String>, List<String>> groupedDetectedSamples = new LinkedHashMap<>();
Map<Set<String>, List<String>> groupedDetectedSamples = new LinkedHashMap<>(); // 用于按相同化合物组合分组样品
// **分组整理检出样品**
for (Map.Entry<String, List<String>> entry : detectedMap.entrySet()) {
Set<String> compoundSet = new HashSet<>(entry.getValue());
Set<String> compoundSet = new HashSet<>(entry.getValue()); // 将检出的化合物集合化(去重)
groupedDetectedSamples.computeIfAbsent(compoundSet, k -> new ArrayList<>()).add(extractSampleNumber.apply(entry.getKey()));
}
// **遍历分组后的数据,生成描述文本**
for (Map.Entry<Set<String>, List<String>> entry : groupedDetectedSamples.entrySet()) {
String sampleNumbers = formatSampleNumbers(entry.getValue()); // 这里也应用连续编号格式化
String compounds = String.join("、", entry.getKey());
List<String> sortedSampleNumbers = sortSampleNumbers(entry.getValue()); // 对样品编号排序
String sampleNumbers = formatSampleNumbers(sortedSampleNumbers); // 处理编号连续情况
String compounds = String.join("、", entry.getKey()); // 多个化合物用 "、" 连接
if (entry.getValue().size() > 1) {
if (sortedSampleNumbers.size() > 1) {
detectedSentences.add(String.format("从%s检材样品中均检出%s成分", sampleNumbers, compounds));
} else {
detectedSentences.add(String.format("从%s检材样品中检出%s成分", sampleNumbers, compounds));
}
}
// 6. **构建未检出化合物的描述**
List<String> notDetectedSentences = new ArrayList<>();
Map<Set<String>, List<String>> groupedNotDetectedSamples = new LinkedHashMap<>();
Map<Set<String>, List<String>> groupedNotDetectedSamples = new LinkedHashMap<>(); // 用于按相同化合物组合分组未检出样品
// **分组整理未检出样品**
for (Map.Entry<String, List<String>> entry : notDetectedMap.entrySet()) {
String sampleNo = entry.getKey();
Set<String> compoundSet = new HashSet<>(entry.getValue());
// 只有当该样品编号未出现在 detectedMap(即没有检出任何化合物)时,才记录未检出的情况
if (!detectedMap.containsKey(sampleNo)) {
groupedNotDetectedSamples.computeIfAbsent(compoundSet, k -> new ArrayList<>()).add(extractSampleNumber.apply(sampleNo));
}
}
// **遍历分组后的数据,生成描述文本**
for (Map.Entry<Set<String>, List<String>> entry : groupedNotDetectedSamples.entrySet()) {
String sampleNumbers = formatSampleNumbers(entry.getValue()); // 这里也应用连续编号格式化
String compounds = String.join("、", entry.getKey());
List<String> sortedSampleNumbers = sortSampleNumbers(entry.getValue()); // 对样品编号排序
String sampleNumbers = formatSampleNumbers(sortedSampleNumbers); // 处理编号连续情况
String compounds = String.join("、", entry.getKey()); // 多个化合物用 "、" 连接
if (entry.getValue().size() > 1) {
if (sortedSampleNumbers.size() > 1) {
notDetectedSentences.add(String.format("从%s检材样品中均未检出%s成分", sampleNumbers, compounds));
} else {
notDetectedSentences.add(String.format("从%s检材样品中未检出%s成分", sampleNumbers, compounds));
}
}
List<String> finalSentences = new ArrayList<>(detectedSentences);
finalSentences.addAll(notDetectedSentences);
// 7. **最终结果合并**
List<String> finalSentences = new ArrayList<>(detectedSentences); // 先添加检出的描述
finalSentences.addAll(notDetectedSentences); // 再添加未检出的描述
// 使用 ";" 连接所有描述,并返回最终的检测意见字符串
return String.join(";", finalSentences);
}
}

Loading…
Cancel
Save