|
|
|
@ -3,12 +3,13 @@ package digital.laboratory.platform.inspection.service.impl; |
|
|
|
|
import cn.hutool.core.collection.CollUtil; |
|
|
|
|
import cn.hutool.core.io.IoUtil; |
|
|
|
|
import cn.hutool.core.util.StrUtil; |
|
|
|
|
import com.alibaba.fastjson.JSONArray; |
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils; |
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
|
|
|
|
import com.deepoove.poi.XWPFTemplate; |
|
|
|
|
import com.deepoove.poi.config.Configure; |
|
|
|
|
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; |
|
|
|
|
import com.deepoove.poi.util.TableTools; |
|
|
|
|
import com.deepoove.poi.xwpf.NiceXWPFDocument; |
|
|
|
|
import digital.laboratory.platform.common.core.util.R; |
|
|
|
|
import digital.laboratory.platform.common.feign.RemoteWord2PDFService; |
|
|
|
@ -31,6 +32,7 @@ import digital.laboratory.platform.sys.entity.Drug; |
|
|
|
|
import digital.laboratory.platform.sys.enums.entrust.EntrustBiologyType; |
|
|
|
|
import feign.Response; |
|
|
|
|
import org.apache.commons.io.output.ByteArrayOutputStream; |
|
|
|
|
import org.apache.poi.xwpf.usermodel.XWPFTable; |
|
|
|
|
import org.springframework.beans.BeanUtils; |
|
|
|
|
import org.springframework.mock.web.MockMultipartFile; |
|
|
|
|
import org.springframework.stereotype.Service; |
|
|
|
@ -39,6 +41,7 @@ import javax.annotation.Resource; |
|
|
|
|
import javax.servlet.http.HttpServletResponse; |
|
|
|
|
import java.io.ByteArrayInputStream; |
|
|
|
|
import java.math.BigDecimal; |
|
|
|
|
import java.math.RoundingMode; |
|
|
|
|
import java.util.*; |
|
|
|
|
import java.util.function.Function; |
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
@ -163,7 +166,7 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
|
|
|
|
|
// 根据化合物名称分组
|
|
|
|
|
Map<String, List<TestRecordSampleData>> dataMap = dataList.stream() |
|
|
|
|
.collect(Collectors.groupingBy(TestRecordSampleData::getCompoundName)); |
|
|
|
|
.collect(Collectors.groupingBy(TestRecordSampleData::getCompoundCnName)); |
|
|
|
|
|
|
|
|
|
List<TestRecordSampleDataDocDTO> dataDtos = new ArrayList<>(); |
|
|
|
|
|
|
|
|
@ -265,20 +268,28 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
// 返回处理后的数据
|
|
|
|
|
data.put("dataDtos", dataDtos); |
|
|
|
|
data.put("sampleSize", dataDtos.size() / 2); |
|
|
|
|
data.put("inspectOpinion", this.buildInspectOpinion(dataList)); |
|
|
|
|
data.put("inspectOpinion", this.buildInspectOpinion(testRecordSampleDataService |
|
|
|
|
.lambdaQuery() |
|
|
|
|
.eq(TestRecordSampleData::getTestId, testRecord.getId()) |
|
|
|
|
.eq(TestRecordSampleData::getSampleType, "Analyte") |
|
|
|
|
.list())); |
|
|
|
|
return data; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Map<String, Object> buildInVitroRecordData(EntrustInfo entrustInfo, TestRecord testRecord) { |
|
|
|
|
Map<String, Object> data = new HashMap<>(); |
|
|
|
|
public Map<String, Object> buildInVitroRecordData(EntrustInfo entrustInfo, TestRecord testRecord, Map<String, Object> data) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1️⃣ 按化合物名称分组 TestRecordSampleData
|
|
|
|
|
Map<String, List<TestRecordSampleData>> compoundSampleDataMap = testRecordSampleDataService |
|
|
|
|
|
|
|
|
|
List<TestRecordSampleData> dataList = testRecordSampleDataService |
|
|
|
|
.lambdaQuery() |
|
|
|
|
.eq(TestRecordSampleData::getTestId, testRecord.getId()) |
|
|
|
|
.list() |
|
|
|
|
.list(); |
|
|
|
|
|
|
|
|
|
Map<String, List<TestRecordSampleData>> compoundSampleDataMap = dataList |
|
|
|
|
.stream() |
|
|
|
|
.collect(Collectors.groupingBy(TestRecordSampleData::getCompoundName)); |
|
|
|
|
.collect(Collectors.groupingBy(TestRecordSampleData::getCompoundCnName)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2️⃣ 过滤出 "标准物质" 类别的试剂,并转换为 Map (key=药品名称, value=试剂VO)
|
|
|
|
|
List<String> reagentConsumablesList = testRecord.getReagentConsumablesList(); |
|
|
|
@ -306,35 +317,44 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
TestRecordSampleData stdSample = stdList.get(0); |
|
|
|
|
|
|
|
|
|
// 4️⃣ 获取 STD 样本的扩展数据 (以 MassToChargeRatio 为 key)
|
|
|
|
|
Map<BigDecimal, TestRecordSampleDataExpand> expandMap = testRecordSampledataExpandService.lambdaQuery() |
|
|
|
|
Map<String, TestRecordSampleDataExpand> expandMap = testRecordSampledataExpandService.lambdaQuery() |
|
|
|
|
.eq(TestRecordSampleDataExpand::getTestDataId, stdSample.getId()) |
|
|
|
|
.list() |
|
|
|
|
.stream() |
|
|
|
|
.collect(Collectors.toMap(TestRecordSampleDataExpand::getMassToChargeRatio, Function.identity())); |
|
|
|
|
.collect(Collectors.toMap( |
|
|
|
|
e -> e.getMassToChargeRatio().setScale(0, RoundingMode.FLOOR).toString(), // 保留整数部分
|
|
|
|
|
Function.identity() |
|
|
|
|
)); |
|
|
|
|
|
|
|
|
|
// 5️⃣ 获取当前化合物对应的试剂,提取特征离子
|
|
|
|
|
TestRecordReagentVO reagentVO = reagentMap.get(compoundName); |
|
|
|
|
if (reagentVO == null || reagentVO.getDrug().getCharacteristicIons() == null) { |
|
|
|
|
if (reagentVO == null || StringUtils.isBlank(reagentVO.getDrug().getCharacteristicIons())) { |
|
|
|
|
continue; // 没有对应试剂或特征离子数据,跳过
|
|
|
|
|
} |
|
|
|
|
String[] peaks = reagentVO.getDrug().getCharacteristicIons().split("、"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 6️⃣ 处理 "标样" 数据 (STD)
|
|
|
|
|
for (int i = 0; i < peaks.length; i++) { |
|
|
|
|
String peak = (i == 0) ? peaks[i].substring(0, peaks[i].length() - 1) : peaks[i]; |
|
|
|
|
TestRecordSampleDataExpand expand = expandMap.get(new BigDecimal(peak)); |
|
|
|
|
|
|
|
|
|
TestRecordSampleDataExpand expand = expandMap.get(peak); |
|
|
|
|
if (expand != null) { |
|
|
|
|
TestRecordSampleDataDocDTO dto = new TestRecordSampleDataDocDTO(); |
|
|
|
|
BeanUtils.copyProperties(stdSample, dto); |
|
|
|
|
dto.setName("标样"); |
|
|
|
|
dto.setPTargetRtTime(dto.getTargetRtTime().toString()); |
|
|
|
|
dto.setCompoundName(compoundName); |
|
|
|
|
dto.setPTargetRtTime(Optional.ofNullable(dto.getTargetRtTime()).map(Object::toString).orElse("/")); |
|
|
|
|
dto.setPRtTimeError("±1"); |
|
|
|
|
dto.setPRtTimeWithinError("/"); |
|
|
|
|
dto.setPMassToChargeRatio(i == 0 ? peak + "(基峰)" : peak); |
|
|
|
|
dto.setPIonAbundanceRatio(expand.getIonAbundanceRatio().toString()); |
|
|
|
|
dto.setPIonAbundanceRatioError(expand.getIonAbundanceRatioError().toString()); |
|
|
|
|
dto.setPIonAbundanceRatioWithinError(expand.getIonAbundanceRatioWithinError()); |
|
|
|
|
dto.setPIonAbundanceRatio(Optional.ofNullable(expand.getIonAbundanceRatio()) |
|
|
|
|
.map(value -> value.setScale(2, RoundingMode.HALF_UP).toString()) |
|
|
|
|
.orElse("/")); |
|
|
|
|
|
|
|
|
|
dto.setPIonAbundanceRatioError(Optional.ofNullable(expand.getIonAbundanceRatioError()) |
|
|
|
|
.map(value -> value.setScale(2, RoundingMode.HALF_UP).toString()) |
|
|
|
|
.orElse("/")); |
|
|
|
|
dto.setPIonAbundanceRatioWithinError(Optional.ofNullable(expand.getIonAbundanceRatioWithinError()).filter(s -> !s.isEmpty()).orElse("/")); |
|
|
|
|
dto.setPIsDetected("/"); |
|
|
|
|
dataDtos.add(dto); |
|
|
|
|
} |
|
|
|
@ -348,7 +368,7 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
dto.setPTargetRtTime("/"); |
|
|
|
|
dto.setPRtTimeError("/"); |
|
|
|
|
dto.setPRtTimeWithinError("/"); |
|
|
|
|
dto.setPMassToChargeRatio(peak.equals(peaks[0]) ? peak + "(基峰)" : peak); |
|
|
|
|
dto.setPMassToChargeRatio(peak.equals(peaks[0]) ? peaks[0].substring(0, peaks[0].length() - 1) + "(基峰)" : peak); |
|
|
|
|
dto.setPIonAbundanceRatio("/"); |
|
|
|
|
dto.setPIonAbundanceRatioError("/"); |
|
|
|
|
dto.setPIonAbundanceRatioWithinError(peak.equals(peaks[0]) ? "/" : "否"); |
|
|
|
@ -362,27 +382,37 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
analyteList.sort(testRecordSampleDataService.getSortBySampleNo()); // 按样本编号排序
|
|
|
|
|
|
|
|
|
|
for (TestRecordSampleData analyte : analyteList) { |
|
|
|
|
Map<BigDecimal, TestRecordSampleDataExpand> analyteExpandMap = testRecordSampledataExpandService.lambdaQuery() |
|
|
|
|
Map<String, TestRecordSampleDataExpand> analyteExpandMap = testRecordSampledataExpandService.lambdaQuery() |
|
|
|
|
.eq(TestRecordSampleDataExpand::getTestDataId, analyte.getId()) |
|
|
|
|
.list() |
|
|
|
|
.stream() |
|
|
|
|
.collect(Collectors.toMap(TestRecordSampleDataExpand::getMassToChargeRatio, Function.identity())); |
|
|
|
|
.collect(Collectors.toMap( |
|
|
|
|
e -> e.getMassToChargeRatio().setScale(0, RoundingMode.FLOOR).toString(), // 保留整数部分
|
|
|
|
|
Function.identity() |
|
|
|
|
)); |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < peaks.length; i++) { |
|
|
|
|
String peak = (i == 0) ? peaks[i].substring(0, peaks[i].length() - 1) : peaks[i]; |
|
|
|
|
TestRecordSampleDataExpand expand = analyteExpandMap.get(new BigDecimal(peak)); |
|
|
|
|
|
|
|
|
|
TestRecordSampleDataExpand expand = analyteExpandMap.get(peak); |
|
|
|
|
if (expand != null) { |
|
|
|
|
TestRecordSampleDataDocDTO dto = new TestRecordSampleDataDocDTO(); |
|
|
|
|
BeanUtils.copyProperties(analyte, dto); |
|
|
|
|
dto.setPTargetRtTime(dto.getTargetRtTime().toString()); |
|
|
|
|
dto.setPRtTimeError(dto.getRtTimeWithinError()); |
|
|
|
|
dto.setPRtTimeWithinError(dto.getRtTimeWithinError()); |
|
|
|
|
dto.setPTargetRtTime(Optional.ofNullable(dto.getTargetRtTime()).map(Object::toString).orElse("/")); |
|
|
|
|
dto.setPRtTimeError(Optional.ofNullable(dto.getRtTimeError()) |
|
|
|
|
.map(value -> new BigDecimal(value.toString()).setScale(2, RoundingMode.HALF_UP).toString()) |
|
|
|
|
.orElse("/")); |
|
|
|
|
dto.setPRtTimeWithinError(Optional.ofNullable(dto.getRtTimeWithinError()).orElse("/")); |
|
|
|
|
dto.setPMassToChargeRatio(i == 0 ? peak + "(基峰)" : peak); |
|
|
|
|
dto.setPIonAbundanceRatio(expand.getIonAbundanceRatio().toString()); |
|
|
|
|
dto.setPIonAbundanceRatioError(expand.getIonAbundanceRatioError().toString()); |
|
|
|
|
dto.setPIonAbundanceRatioWithinError(expand.getIonAbundanceRatioWithinError()); |
|
|
|
|
dto.setPIsDetected(dto.getIsDetected() == 1 ? "是" : "否"); |
|
|
|
|
dto.setPIonAbundanceRatio(Optional.ofNullable(expand.getIonAbundanceRatio()) |
|
|
|
|
.map(value -> value.setScale(2, RoundingMode.HALF_UP).toString()) |
|
|
|
|
.orElse("/")); |
|
|
|
|
|
|
|
|
|
dto.setPIonAbundanceRatioError(Optional.ofNullable(expand.getIonAbundanceRatioError()) |
|
|
|
|
.map(value -> value.setScale(2, RoundingMode.HALF_UP).toString()) |
|
|
|
|
.orElse("/")); |
|
|
|
|
dto.setPIonAbundanceRatioWithinError(Optional.ofNullable(expand.getIonAbundanceRatioWithinError()).orElse("/")); |
|
|
|
|
dto.setPIsDetected(dto.getIsDetected() != null && dto.getIsDetected() == 1 ? "是" : "否"); |
|
|
|
|
dto.setCompoundName(compoundName); |
|
|
|
|
dataDtos.add(dto); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -392,6 +422,76 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
|
|
|
|
|
// 9️⃣ 返回处理后的数据
|
|
|
|
|
data.put("dataDtos", dataDtos); |
|
|
|
|
data.put("sampleSize", dataDtos.size()); |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
Map<Integer, List<TestRecordSampleData>> isDetectedMap = testRecordSampleDataService |
|
|
|
|
.lambdaQuery() |
|
|
|
|
.eq(TestRecordSampleData::getTestId, testRecord.getId()) |
|
|
|
|
.eq(TestRecordSampleData::getSampleType, "Analyte") |
|
|
|
|
.list() |
|
|
|
|
.stream() |
|
|
|
|
.filter(Objects::nonNull) // 避免空值
|
|
|
|
|
.collect(Collectors.groupingBy(TestRecordSampleData::getIsDetected)); |
|
|
|
|
|
|
|
|
|
List<String> isDetectedStrs = new ArrayList<>(); |
|
|
|
|
List<String> notDetectedStrs = 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) |
|
|
|
|
.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()); |
|
|
|
|
|
|
|
|
|
String collect = entryValue.stream() |
|
|
|
|
.map(item -> { |
|
|
|
|
String[] parts = item.getSampleNo().split("-"); |
|
|
|
|
return (parts.length > 2) ? parts[2] + "号" : item.getSampleNo(); // 预防数组越界
|
|
|
|
|
}) |
|
|
|
|
.collect(Collectors.joining("、")); |
|
|
|
|
|
|
|
|
|
String description; |
|
|
|
|
if (entry.getKey() == 1) { |
|
|
|
|
description = "☑ " + collect + "样品空白对照无干扰,在相同条件下进行测定时,样品溶液中目标物与浓度相近的标准工作溶液中的目标物相比,色谱峰保留时间一致、质谱特征离子一致,各离子丰度比相对偏差符合规定范围,判定样品中检出" + listEntry.getKey() + "。"; |
|
|
|
|
isDetectedStrs.add(description); |
|
|
|
|
} else { |
|
|
|
|
description = "☑ " + collect + "样品空白对照无干扰,样品溶液中未检出符合阳性结果评价要求的色谱峰,标准溶液空白无干扰,判定样品中为未检出" + listEntry.getKey() + "。"; |
|
|
|
|
notDetectedStrs.add(description); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!isDetectedStrs.isEmpty()) { |
|
|
|
|
data.put("isDetectedStrs", "阳性结果:" + "\n" + isDetectedStrs.stream() |
|
|
|
|
.map(item -> "\u3000\u3000" + item) |
|
|
|
|
.collect(Collectors.joining("\u2611"))); |
|
|
|
|
} |
|
|
|
|
if (!notDetectedStrs.isEmpty()) { |
|
|
|
|
data.put("notDetectedStrs", "阴性结果:" + "\n" + notDetectedStrs.stream() |
|
|
|
|
.map(item -> "\u3000\u3000" + item) |
|
|
|
|
.collect(Collectors.joining("\u2611"))); |
|
|
|
|
} |
|
|
|
|
} 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -470,8 +570,7 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
* @return 上传后的文件路径 |
|
|
|
|
* @throws Exception 可能抛出的异常 |
|
|
|
|
*/ |
|
|
|
|
private String buildDocFileAndUploadToOss(String entrustId, String templatePath, Configure |
|
|
|
|
config, Map<String, Object> data) 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(); |
|
|
|
@ -481,11 +580,26 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
XWPFTemplate template = XWPFTemplate.compile(bis, config).render(data); |
|
|
|
|
NiceXWPFDocument document = template.getXWPFDocument(); |
|
|
|
|
|
|
|
|
|
// XWPFTable table = document.getTables().get(3);
|
|
|
|
|
// int sampleSize = (int) data.get("sampleSize");
|
|
|
|
|
// //合并单元格
|
|
|
|
|
//
|
|
|
|
|
// TableTools.mergeCellsVertically(table, 1, 1, sampleSize);
|
|
|
|
|
List<XWPFTable> tables = document.getTables(); |
|
|
|
|
XWPFTable table = tables.get(tables.size() - 1); // 获取最后一个表格
|
|
|
|
|
|
|
|
|
|
int sampleSize = (int) data.get("sampleSize"); |
|
|
|
|
|
|
|
|
|
// **处理合并单元格**
|
|
|
|
|
int startRow = 1; // 从第二行开始(索引从0开始,所以第二行是索引1)
|
|
|
|
|
int mergeStep = 4; // 每4行合并一次
|
|
|
|
|
|
|
|
|
|
// 指定需要合并的列(注意索引从0开始)
|
|
|
|
|
int[] mergeColumns = {0, 1, 2, 3, 4, 9}; // 1、2、3、4、5、10列的索引分别是 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); // 计算合并的终止行,防止越界
|
|
|
|
|
|
|
|
|
|
for (int col : mergeColumns) { |
|
|
|
|
TableTools.mergeCellsVertically(table, col, rowIndex, endRow); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bis.close(); |
|
|
|
|
ByteArrayOutputStream fosWord = new ByteArrayOutputStream(); |
|
|
|
@ -494,12 +608,14 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 生成常规毒品的检验记录(贵阳禁毒) |
|
|
|
|
*/ |
|
|
|
@ -509,8 +625,7 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
TestRecord testRecord = testRecordService.lambdaQuery() |
|
|
|
|
.eq(TestRecord::getBusinessId, businessId) |
|
|
|
|
.one(); |
|
|
|
|
Map<String, Object> data = this.buildInVitroRecordData(entrustInfo, testRecord); |
|
|
|
|
docMap.putAll(data); |
|
|
|
|
Map<String, Object> data = this.buildInVitroRecordData(entrustInfo, testRecord, docMap); |
|
|
|
|
String templatePath = "/template" + "/贵阳禁毒常规毒品检验记录模板.docx"; |
|
|
|
|
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); |
|
|
|
|
Configure config = Configure.builder(). |
|
|
|
@ -528,7 +643,9 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
EntrustInfo entrustInfo = entrustInfoService.getById(businessId); |
|
|
|
|
|
|
|
|
|
// 这里因为检验记录实体并未关联业务id, 只能先查询实验数据,在通过实验数据获取到检验记录信息
|
|
|
|
|
TestRecord testRecord = testRecordService.getTestRecordByBusinessId(entrustInfo.getId()); |
|
|
|
|
TestRecord testRecord = testRecordService.lambdaQuery() |
|
|
|
|
.eq(TestRecord::getBusinessId, businessId) |
|
|
|
|
.one(); |
|
|
|
|
|
|
|
|
|
// 封装数据到map中
|
|
|
|
|
Map<String, Object> docMap = buildCommonInspectRecordDocMap(entrustInfo, testRecord, ""); |
|
|
|
@ -561,28 +678,17 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
data.put("inspectYear", testRecord.getTestStartDate().getYear()); |
|
|
|
|
data.put("inspectMonth", testRecord.getTestStartDate().getMonthValue()); |
|
|
|
|
data.put("inspectDay", testRecord.getTestStartDate().getDayOfMonth()); |
|
|
|
|
String[] split = entrustInfo.getAcceptNo().split("-"); |
|
|
|
|
if (split.length == 2) { |
|
|
|
|
data.put("acceptNo", "[" + split[0] + "]" + split[1] + "号"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
data.put("acceptNo", entrustInfo.getAcceptNo()); |
|
|
|
|
|
|
|
|
|
// 检材性状描述和检验要求成分
|
|
|
|
|
List<SampleInfo> sampleInfoList = getSampleInfosByMaterialType(entrustInfo, materialType); |
|
|
|
|
String materialCharacterDesc = buildMaterialCharacterDesc(sampleInfoList); |
|
|
|
|
String targetObjectStr = sampleInfoList |
|
|
|
|
.stream() |
|
|
|
|
.flatMap( |
|
|
|
|
sampleInfo -> { |
|
|
|
|
String toJSONString = JSONArray.toJSONString(sampleInfo.getTargetObject()); |
|
|
|
|
List<TargetObject> drugLiteList = JSONArray.parseArray(toJSONString, TargetObject.class); |
|
|
|
|
return drugLiteList.stream(); |
|
|
|
|
}) |
|
|
|
|
.collect(Collectors.toList()) // 得到转换好的筛查物对象
|
|
|
|
|
.stream() |
|
|
|
|
.map(TargetObject::getName) |
|
|
|
|
.collect(Collectors.toSet()) // 区筛查物对象名称并去重
|
|
|
|
|
.stream() |
|
|
|
|
.collect(Collectors.joining("、")); // 拼接筛查物名称
|
|
|
|
|
// 检材性状描述
|
|
|
|
|
String materialCharacterDesc = buildMaterialCharacterDesc(sampleInfoService |
|
|
|
|
.lambdaQuery() |
|
|
|
|
.in(SampleInfo::getId, testRecord.getSampleTestList()) |
|
|
|
|
.list()); |
|
|
|
|
data.put("materialCharacterDesc", materialCharacterDesc); |
|
|
|
|
data.put("materialIngredient", targetObjectStr); |
|
|
|
|
|
|
|
|
|
// 获取仪器设备数据(批量查询,减少数据库压力)
|
|
|
|
|
List<String> deviceIdList = testRecord.getDeviceIdList(); |
|
|
|
@ -591,11 +697,19 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
: testRecordInstrumentService.list(Wrappers.<TestRecordInstrument>lambdaQuery() |
|
|
|
|
.in(TestRecordInstrument::getId, deviceIdList)); |
|
|
|
|
|
|
|
|
|
data.put("instrumentName", CollectionUtils.isEmpty(instruments) |
|
|
|
|
? "未找到仪器设备数据!" |
|
|
|
|
: instruments.stream() |
|
|
|
|
.map(i -> "\u3000\u3000" + i.getInstrumentName()) // 使用 Unicode 全角空格
|
|
|
|
|
.collect(Collectors.joining("\n"))); |
|
|
|
|
if (materialType.isEmpty()) { |
|
|
|
|
data.put("instrumentName", CollectionUtils.isEmpty(instruments) |
|
|
|
|
? "未找到仪器设备数据!" |
|
|
|
|
: instruments.stream() |
|
|
|
|
.map(i -> "\u3000\u3000\u2611" + i.getInstrumentName()) // 使用 Unicode 全角空格
|
|
|
|
|
.collect(Collectors.joining("\n"))); |
|
|
|
|
} else { |
|
|
|
|
data.put("instrumentName", CollectionUtils.isEmpty(instruments) |
|
|
|
|
? "未找到仪器设备数据!" |
|
|
|
|
: instruments.stream() |
|
|
|
|
.map(i -> "\u3000\u3000" + i.getInstrumentName()) // 使用 Unicode 全角空格
|
|
|
|
|
.collect(Collectors.joining("\n"))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置使用的标准物质和试剂
|
|
|
|
@ -606,14 +720,18 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
if (references == null || references.isEmpty()) { |
|
|
|
|
data.put("referenceMaterialName", "未找到试剂耗材数据!"); |
|
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
String firstStr = "\u2611" + " " + references.get(0).getReagentConsumableName() + "\n"; |
|
|
|
|
references.remove(references.get(0)); |
|
|
|
|
String referenceMaterialName = references.stream() |
|
|
|
|
.map(reagent -> "\u2611" + " " + reagent.getReagentConsumableName()) |
|
|
|
|
.map(reagent -> "\u3000\u3000\u2611" + " " + reagent.getReagentConsumableName()) |
|
|
|
|
.collect(Collectors.joining("\n")); |
|
|
|
|
|
|
|
|
|
data.put("referenceMaterialName", referenceMaterialName); |
|
|
|
|
} |
|
|
|
|
data.put("referenceMaterialName", firstStr + referenceMaterialName); |
|
|
|
|
|
|
|
|
|
data.put("materialIngredient", references.stream() |
|
|
|
|
.map(reagent -> reagent.getReagentConsumableName()) |
|
|
|
|
.collect(Collectors.joining("、"))); |
|
|
|
|
} |
|
|
|
|
List<TestRecordReagent> reagents = testRecordReagentService.list(Wrappers.<TestRecordReagent>lambdaQuery() |
|
|
|
|
.in(TestRecordReagent::getId, testRecord.getReagentConsumablesList()) |
|
|
|
|
.eq(TestRecordReagent::getCategory, "试剂")); |
|
|
|
@ -657,136 +775,167 @@ public class InspectRecordServiceImpl implements InspectRecordService { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 构建检材性状描述 |
|
|
|
|
* 构建检材性状描述(使用检材编号尾号,并合并连续编号) |
|
|
|
|
* <p> |
|
|
|
|
* 规则: |
|
|
|
|
* 1. **提取编号尾号**(如 `2025007001` → `1`) |
|
|
|
|
* 2. **按尾号排序** |
|
|
|
|
* 3. **连续编号 & 物质性状相同时合并** |
|
|
|
|
* 4. **只有一个检材时,省略编号,直接描述** |
|
|
|
|
* 5. **输出格式示例:** |
|
|
|
|
* - `"1号至4号检材为粉状"` |
|
|
|
|
* - `"检材为粉状"`(仅有一个检材时) |
|
|
|
|
* |
|
|
|
|
* @param sampleInfoList 样本信息列表 |
|
|
|
|
* @return 物料特性描述,如果无法构建则返回null |
|
|
|
|
* @return 物料特性描述(如果列表为空,则返回空字符串) |
|
|
|
|
*/ |
|
|
|
|
private String buildMaterialCharacterDesc(List<SampleInfo> sampleInfoList) { |
|
|
|
|
if (CollUtil.isEmpty(sampleInfoList)) { |
|
|
|
|
return StrUtil.EMPTY; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 仅有一个样本时,直接返回
|
|
|
|
|
if (sampleInfoList.size() == 1) { |
|
|
|
|
return "检材为" + sampleInfoList.get(0).getForm() + "。"; |
|
|
|
|
} |
|
|
|
|
// 按尾号排序(提取编号尾号进行比较)
|
|
|
|
|
sampleInfoList.sort(Comparator.comparing(sample -> extractTailNumber(sample.getAcceptNo()))); |
|
|
|
|
|
|
|
|
|
// 按 form 分组
|
|
|
|
|
Map<String, List<SampleInfo>> sampleMapGroupByForm = sampleInfoList.stream() |
|
|
|
|
.collect(Collectors.groupingBy(SampleInfo::getForm)); |
|
|
|
|
List<String> descriptions = new ArrayList<>(); |
|
|
|
|
int start = -1, prev = -1; |
|
|
|
|
String prevForm = null; |
|
|
|
|
List<String> mergedOrderNos = new ArrayList<>(); |
|
|
|
|
|
|
|
|
|
// 遍历排序后的检材编号,合并相邻且性状相同的编号
|
|
|
|
|
for (SampleInfo sample : sampleInfoList) { |
|
|
|
|
int current = extractTailNumber(sample.getAcceptNo()); // 提取尾号
|
|
|
|
|
String form = sample.getForm(); |
|
|
|
|
|
|
|
|
|
if (start == -1) { // 初始化
|
|
|
|
|
start = prev = current; |
|
|
|
|
prevForm = form; |
|
|
|
|
} else if (current == prev + 1 && form.equals(prevForm)) { |
|
|
|
|
// 只有连续尾号 + 相同性状才能合并
|
|
|
|
|
prev = current; |
|
|
|
|
} else { |
|
|
|
|
// 遇到新性状或不连续尾号,存储前一段描述
|
|
|
|
|
mergedOrderNos.add(formatRange(start, prev) + "检材为" + prevForm); |
|
|
|
|
start = prev = current; |
|
|
|
|
prevForm = form; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// 处理最后一组数据
|
|
|
|
|
mergedOrderNos.add(formatRange(start, prev) + "检材为" + prevForm); |
|
|
|
|
|
|
|
|
|
// 所有样本 form 相同
|
|
|
|
|
if (sampleMapGroupByForm.size() == 1) { |
|
|
|
|
String form = sampleMapGroupByForm.keySet().iterator().next(); // 取唯一的 key
|
|
|
|
|
String orderNoStr = sampleInfoList.stream() |
|
|
|
|
.map(sample -> sample.getOrderNo() + "号") |
|
|
|
|
.collect(Collectors.joining("、")); |
|
|
|
|
return orderNoStr + "检材均为" + form + "。"; |
|
|
|
|
// **如果只有一个检材,省略编号**
|
|
|
|
|
if (mergedOrderNos.size() == 1 && sampleInfoList.size() == 1) { |
|
|
|
|
return "检材为" + prevForm + "。"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 存在多个 form 的情况
|
|
|
|
|
List<String> descriptions = new ArrayList<>(); |
|
|
|
|
sampleMapGroupByForm.forEach((form, value) -> { |
|
|
|
|
String orderNoStr = value.stream() |
|
|
|
|
.map(sample -> sample.getOrderNo() + "号") |
|
|
|
|
.collect(Collectors.joining("、")); |
|
|
|
|
descriptions.add(orderNoStr + "检材均为" + form); |
|
|
|
|
}); |
|
|
|
|
return String.join(";", mergedOrderNos) + "。"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 提取检材编号的尾号(即最后一个 `-` 之后的数值) |
|
|
|
|
* |
|
|
|
|
* @param orderNo 检材编号(格式:2025-89-13) |
|
|
|
|
* @return 尾号(示例:返回 13) |
|
|
|
|
*/ |
|
|
|
|
private int extractTailNumber(String orderNo) { |
|
|
|
|
String[] parts = orderNo.split("-"); |
|
|
|
|
if (parts.length > 0) { |
|
|
|
|
return Integer.parseInt(parts[parts.length - 1]); // 取最后一段
|
|
|
|
|
} |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return String.join(";", descriptions) + "。"; // 用分号连接不同类型的描述
|
|
|
|
|
/** |
|
|
|
|
* 格式化编号范围: |
|
|
|
|
* - 单个编号:返回 "1号" |
|
|
|
|
* - 连续编号:返回 "1号至3号" |
|
|
|
|
* |
|
|
|
|
* @param start 起始编号 |
|
|
|
|
* @param end 结束编号 |
|
|
|
|
* @return 格式化的编号字符串(如果 start == end,返回单号) |
|
|
|
|
*/ |
|
|
|
|
private String formatRange(int start, int end) { |
|
|
|
|
return (start == end) ? start + "号" : start + "号至" + end + "号"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public String buildInspectOpinion(List<TestRecordSampleData> dataList) { |
|
|
|
|
// 1. 检查输入的dataList是否为空,如果为空直接返回当前数据。
|
|
|
|
|
if (dataList == null || dataList.isEmpty()) { |
|
|
|
|
return ""; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 2. 初始化数据结构,用于分类和记录检出与未检出的物质
|
|
|
|
|
// - detectedMap 用来存储检出物质的样本信息,key为样本编号,value为检出的物质列表
|
|
|
|
|
// - notDetectedMap 用来存储未检出物质的样本信息,key为样本编号,value为未检出的物质列表
|
|
|
|
|
// - allCompoundNames 用来存储所有物质名称的集合,方便后续处理
|
|
|
|
|
Map<String, List<String>> detectedMap = new LinkedHashMap<>(); |
|
|
|
|
Map<String, List<String>> notDetectedMap = new LinkedHashMap<>(); |
|
|
|
|
Set<String> allCompoundNames = new HashSet<>(); |
|
|
|
|
|
|
|
|
|
// 3. 遍历dataList,按每个样本的检出状态(是否检出)对物质进行分类
|
|
|
|
|
for (TestRecordSampleData record : dataList) { |
|
|
|
|
String sampleNo = record.getSampleNo(); // 获取样本编号
|
|
|
|
|
String compoundName = record.getCompoundName(); // 获取化合物名称
|
|
|
|
|
allCompoundNames.add(compoundName); // 将化合物名称添加到所有化合物名称集合中
|
|
|
|
|
String sampleNo = record.getSampleNo(); |
|
|
|
|
String compoundName = record.getCompoundCnName(); |
|
|
|
|
|
|
|
|
|
// 根据isDetected值将物质添加到相应的Map中
|
|
|
|
|
if (record.getIsDetected() == 1) { |
|
|
|
|
detectedMap.computeIfAbsent(sampleNo, k -> new ArrayList<>()).add(compoundName); // 如果检出,将物质加入到检测的Map中
|
|
|
|
|
// 确保样本编号有效
|
|
|
|
|
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 { |
|
|
|
|
notDetectedMap.computeIfAbsent(sampleNo, k -> new ArrayList<>()).add(compoundName); // 如果未检出,将物质加入到未检测的Map中
|
|
|
|
|
notDetectedMap.computeIfAbsent(sampleNo, k -> new ArrayList<>()).add(compoundName); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 4. 定义一个lambda函数,提取样本编号中的流水号(例如:"S001" -> "1号")
|
|
|
|
|
// 目的是生成以“号”结尾的样本编号格式。
|
|
|
|
|
Function<String, String> extractSampleNumber = sampleNo -> sampleNo.substring(sampleNo.lastIndexOf('-') + 1) + "号"; |
|
|
|
|
Function<String, String> extractSampleNumber = sampleNo -> sampleNo.matches(".*\\d+") |
|
|
|
|
? sampleNo.replaceAll(".*?(\\d+)$", "$1号") |
|
|
|
|
: sampleNo; |
|
|
|
|
|
|
|
|
|
// 5. 处理“检出”数据
|
|
|
|
|
// 先根据样本编号分组相同的检出物质,之后生成相应的语句描述
|
|
|
|
|
List<String> detectedSentences = new ArrayList<>(); // 存储“检出”物质的描述
|
|
|
|
|
Map<Set<String>, List<String>> groupedDetectedSamples = new LinkedHashMap<>(); // 用来存储按物质分组的样本编号
|
|
|
|
|
List<String> detectedSentences = new ArrayList<>(); |
|
|
|
|
Map<Set<String>, List<String>> groupedDetectedSamples = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
// 遍历检测到的物质,将相同物质的样本编号进行分组
|
|
|
|
|
for (Map.Entry<String, List<String>> entry : detectedMap.entrySet()) { |
|
|
|
|
Set<String> compoundSet = new HashSet<>(entry.getValue()); // 去重,保证每个物质只记录一次
|
|
|
|
|
groupedDetectedSamples.computeIfAbsent(compoundSet, k -> new ArrayList<>()).add(extractSampleNumber.apply(entry.getKey())); // 根据物质的组合来分组样本编号
|
|
|
|
|
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 = String.join("、", entry.getValue()); // 将所有样本编号合并为字符串
|
|
|
|
|
String compounds = String.join("、", entry.getKey()); // 将所有检出的物质合并为字符串
|
|
|
|
|
String sampleNumbers = String.join("、", entry.getValue()); |
|
|
|
|
String compounds = String.join("、", entry.getKey()); |
|
|
|
|
|
|
|
|
|
// 如果多个样本检出相同的物质,描述中使用“均检出”,否则使用“检出”
|
|
|
|
|
if (entry.getValue().size() > 1) { |
|
|
|
|
detectedSentences.add(String.format("从%s检材样品中均检出%s成分", sampleNumbers, compounds)); // 多个样本检出
|
|
|
|
|
detectedSentences.add(String.format("从%s检材样品中均检出%s成分", sampleNumbers, compounds)); |
|
|
|
|
} else { |
|
|
|
|
detectedSentences.add(String.format("从%s检材样品中检出%s成分", sampleNumbers, compounds)); // 单个样本检出
|
|
|
|
|
detectedSentences.add(String.format("从%s检材样品中检出%s成分", sampleNumbers, compounds)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 6. 处理“未检出”数据
|
|
|
|
|
// 先根据未检出的物质及样本编号进行分组,然后生成相应的描述
|
|
|
|
|
List<String> notDetectedSentences = new ArrayList<>(); // 存储“未检出”物质的描述
|
|
|
|
|
Map<Set<String>, List<String>> groupedNotDetectedSamples = new LinkedHashMap<>(); // 用来存储按物质分组的样本编号
|
|
|
|
|
List<String> notDetectedSentences = new ArrayList<>(); |
|
|
|
|
Map<Set<String>, List<String>> groupedNotDetectedSamples = new LinkedHashMap<>(); |
|
|
|
|
|
|
|
|
|
// 遍历未检出的物质,找到未检出的样本并将其分组
|
|
|
|
|
for (Map.Entry<String, List<String>> entry : notDetectedMap.entrySet()) { |
|
|
|
|
String sampleNo = entry.getKey(); // 获取样本编号
|
|
|
|
|
if (detectedMap.containsKey(sampleNo)) { |
|
|
|
|
continue; // 如果该样本已经出现在检出组中,跳过,不再处理
|
|
|
|
|
} |
|
|
|
|
String sampleNo = entry.getKey(); |
|
|
|
|
Set<String> compoundSet = new HashSet<>(entry.getValue()); |
|
|
|
|
|
|
|
|
|
Set<String> compoundSet = new HashSet<>(entry.getValue()); // 获取未检出的物质,去重
|
|
|
|
|
groupedNotDetectedSamples.computeIfAbsent(compoundSet, k -> new ArrayList<>()).add(extractSampleNumber.apply(sampleNo)); // 将未检出的物质分组
|
|
|
|
|
// 仅当该样本没有任何检出的物质时,才记录未检出
|
|
|
|
|
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 = String.join("、", entry.getValue()); // 将未检出的样本编号合并为字符串
|
|
|
|
|
String compounds = String.join("、", entry.getKey()); // 将未检出的物质合并为字符串
|
|
|
|
|
String sampleNumbers = String.join("、", entry.getValue()); |
|
|
|
|
String compounds = String.join("、", entry.getKey()); |
|
|
|
|
|
|
|
|
|
// 如果多个样本未检出相同的物质,描述中使用“均未检出”,否则使用“未检出”
|
|
|
|
|
if (entry.getValue().size() > 1) { |
|
|
|
|
notDetectedSentences.add(String.format("从%s检材样品中均未检出%s成分", sampleNumbers, compounds)); // 多个样本未检出
|
|
|
|
|
notDetectedSentences.add(String.format("从%s检材样品中均未检出%s成分", sampleNumbers, compounds)); |
|
|
|
|
} else { |
|
|
|
|
notDetectedSentences.add(String.format("从%s检材样品中未检出%s成分", sampleNumbers, compounds)); // 单个样本未检出
|
|
|
|
|
notDetectedSentences.add(String.format("从%s检材样品中未检出%s成分", sampleNumbers, compounds)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 7. 将“检出”和“未检出”描述合并到最终结果中
|
|
|
|
|
List<String> finalSentences = new ArrayList<>(detectedSentences); // 先将检出物质的描述加入
|
|
|
|
|
finalSentences.addAll(notDetectedSentences); // 将未检出的描述添加到末尾
|
|
|
|
|
List<String> finalSentences = new ArrayList<>(detectedSentences); |
|
|
|
|
finalSentences.addAll(notDetectedSentences); |
|
|
|
|
|
|
|
|
|
// 8. 将最终的描述字符串添加到结果数据Map中,字段名为"detectionStr"返回结果
|
|
|
|
|
return String.join(";", finalSentences); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|