remoteUploadFile(MultipartFile file, String path);
+}
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/InspectRecordService.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/InspectRecordService.java
index 083abb5..f900c95 100644
--- a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/InspectRecordService.java
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/InspectRecordService.java
@@ -1,8 +1,11 @@
package digital.laboratory.platform.inspection.service;
import digital.laboratory.platform.common.core.util.R;
+import digital.laboratory.platform.inspection.entity.TestRecordSampleData;
+import digital.laboratory.platform.inspetion.api.entity.SampleInfo;
import javax.servlet.http.HttpServletResponse;
+import java.util.List;
/**
* @author ChenJiangBao
@@ -21,4 +24,29 @@ public interface InspectRecordService {
R buildInspectionRecord(String businessId, String materialType) throws Exception;
void previewInspectionRecord(String businessId, String materialType, HttpServletResponse servletResponse) throws Exception;
+
+ /**
+ * 构建检材性状描述(使用检材编号尾号,并合并连续编号)
+ *
+ * 规则:
+ * 1. **提取编号尾号**(如 `2025007001` → `1`)
+ * 2. **按尾号排序**
+ * 3. **连续编号 & 物质性状相同时合并**
+ * 4. **只有一个检材时,省略编号,直接描述**
+ * 5. **输出格式示例:**
+ * - `"1号至4号检材为粉状"`
+ * - `"检材为粉状"`(仅有一个检材时)
+ *
+ * @param sampleInfoList 样本信息列表
+ * @return 物料特性描述(如果列表为空,则返回空字符串)
+ */
+ String buildMaterialCharacterDesc(List sampleInfoList);
+
+ /**
+ * 根据传入的TestRecordSampleData列表生成鉴定意见
+ *
+ * @param dataList 包含检验数据的列表
+ * @return 检测意见字符串,以“;”分隔
+ */
+ String buildInspectOpinion(List dataList);
}
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/ProcessInspectDataService.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/ProcessInspectDataService.java
index caaa833..1b74c7e 100644
--- a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/ProcessInspectDataService.java
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/ProcessInspectDataService.java
@@ -26,6 +26,14 @@ public interface ProcessInspectDataService {
*/
String calculateHairCaseIonAbundanceRatioWithinError(double ionAbundanceRatioWithinError, double stdIonAbundanceRatio);
+ /**
+ * 根据给定的标准离子丰度比获取生物样本案件离子丰度比相对误差范围内的最大允许相对误差
+ *
+ * @param stdIonAbundanceRatio 标准离子丰度比
+ * @return 发案离子丰度比允许的最大误差值
+ */
+ Double getHairCaseIonAbundanceRatioWithinErrorRange(double stdIonAbundanceRatio);
+
/**
* 计算离子丰度比偏差
*
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/PushDataToLabsCareService.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/PushDataToLabsCareService.java
new file mode 100644
index 0000000..fabb8b7
--- /dev/null
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/PushDataToLabsCareService.java
@@ -0,0 +1,17 @@
+package digital.laboratory.platform.inspection.service;
+
+/**
+ * @author ChenJiangBao
+ * @version 1.0
+ * @description: 推送数据到labscare平台接口
+ * @date 2025/3/31 9:28
+ */
+public interface PushDataToLabsCareService {
+
+ /**
+ * 推送生物检材定性记录
+ * @param testId 实验id
+ */
+ void pushBiologyQualitativeRecordData(String testId);
+
+}
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/TestRecordReagentService.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/TestRecordReagentService.java
index ec473d7..6c83522 100644
--- a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/TestRecordReagentService.java
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/TestRecordReagentService.java
@@ -1,5 +1,6 @@
package digital.laboratory.platform.inspection.service;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
@@ -29,6 +30,13 @@ public interface TestRecordReagentService extends IService {
*/
TestRecordReagentVO getVOById(String id);
+ /**
+ * 根据wrapper获取vo类
+ * @param queryWrapper
+ * @return
+ */
+ List voListByWrapper(Wrapper queryWrapper);
+
TestRecordReagent addTestRecordReagent(TestRecordReagent testRecordReagent);//添加实验使用方法
TestRecordReagent updateTestRecordReagent(TestRecordReagent testRecordReagent);//修改实验使用方法
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/CommonFeignServiceImpl.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/CommonFeignServiceImpl.java
new file mode 100644
index 0000000..c26f83b
--- /dev/null
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/CommonFeignServiceImpl.java
@@ -0,0 +1,262 @@
+package digital.laboratory.platform.inspection.service.impl;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.StrUtil;
+import com.deepoove.poi.XWPFTemplate;
+import digital.laboratory.platform.common.core.constant.CommonConstants;
+import digital.laboratory.platform.common.core.util.R;
+import digital.laboratory.platform.common.feign.RemoteWord2PDFService;
+import digital.laboratory.platform.common.oss.service.OssFile;
+import digital.laboratory.platform.inspection.service.CommonFeignService;
+import digital.laboratory.platform.sys.dto.UserInfo;
+import digital.laboratory.platform.sys.entity.Area;
+import digital.laboratory.platform.sys.entity.SysOrg;
+import digital.laboratory.platform.sys.entity.SysUser;
+import digital.laboratory.platform.sys.feign.RemoteOrgService;
+import digital.laboratory.platform.sys.feign.RemoteUserService;
+import feign.Response;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 通用的feign请求封装接口服务层接口 实现类
+ */
+@Slf4j
+@Service
+public class CommonFeignServiceImpl implements CommonFeignService {
+
+ @Resource
+ private RemoteOrgService remoteOrgService;
+
+ @Resource
+ private RemoteUserService remoteUserService;
+
+ @Resource
+ private RemoteWord2PDFService remoteWord2PDFService;
+
+ @Resource
+ private OssFile ossFile;
+
+ /**
+ * 根据用户获取远程系统机构信息
+ *
+ * @param orgId 用户信息
+ * @return 对应的远程系统机构信息
+ * @throws RuntimeException 如果未找到对应的机构信息,则抛出运行时异常
+ */
+ @Override
+ public SysOrg remoteGetSysOrg(String orgId) {
+ SysOrg sysOrg = null;
+ R r = remoteOrgService.getByIdWithoutToken(orgId);
+ if (r != null && r.getCode() == CommonConstants.SUCCESS) {
+ sysOrg = r.getData();
+ } else {
+ throw new RuntimeException(String.format("没有找到 orgId 为 %s 的机构, 请确认用户所属机构的正确性!", orgId));
+ }
+ return sysOrg;
+ }
+
+ /**
+ * 取指定环节可用的用户列表
+ * 当流程进行到某个环节的时候, 需要某个用户对这个环节进行处理(通过或不通过)。
+ * 这里取可用的用户列表
+ *
+ * 涉及到的环境有以下几个:
+ * 1、创建委托及提交
+ * 委托的提交者就是委托的创建者。不存在不通过的可能。
+ * 2、审核
+ * 审核者有几个条件: (1)必须是鉴定中心的工作人员 (2)必须拥有委托审核权限
+ * 3、审批
+ * 审核者有几个条件: (1)必须是鉴定中心的工作人员 (2)必须拥有委托审批权限
+ * 4、送检确认
+ * 送检确认者有几个条件: (1)必须与委托的创建者是同一个机构, 或上级机构的人 (2)必须拥有委托送检确认权限
+ * 5、受理
+ * 受理者有几个条件: (1)必须是鉴定中心的工作人员 (2)必须拥有委托受理权限
+ *
+ * @return
+ */
+ /**
+ * 根据权限和机构远程获取用户列表
+ *
+ * @param orgId 组织ID
+ * @param permission 权限列表
+ * @return 用户列表
+ */
+ @Override
+ public List remoteGetUsersByPermission(String orgId, List permission) {
+ R> r = remoteUserService.innerGetUsersByPermission(orgId, permission);
+ if (r != null && r.getCode() == CommonConstants.SUCCESS) {
+ return r.getData();
+ } else {
+ throw new RuntimeException(String.format("根据权限 [%s] 和 机构id [%s] 远程获取用户列表!", permission, orgId));
+ }
+ }
+
+ /**
+ * 通过机构ID远程获取机构所在省市信息
+ *
+ * @param orgId 机构ID
+ * @return 包含机构所在省市信息的Area对象列表
+ * @throws RuntimeException 当根据机构ID获取机构所在省市信息失败时抛出
+ */
+ @Override
+ public List remoteGetProvinceCityInfo(String orgId) {
+ List result = null;
+ R> r = remoteOrgService.fetchProvinceCityInfoByOrgId(orgId);
+ if (r != null && r.getCode() == CommonConstants.SUCCESS) {
+ result = r.getData();
+ } else {
+ throw new RuntimeException("根据机构id获取机构所在省市信息失败!");
+ }
+ return result;
+ }
+
+ /**
+ * 远程调用或者用户信息
+ * @param username
+ * @return
+ */
+ @Override
+ public SysUser remoteGetUserByUsername(String username){
+ R info = remoteUserService.innerGetUserInfoByUsername(username);
+ if (info != null && info.getCode() == CommonConstants.FAIL) {
+ throw new RuntimeException(String.format("获取用户名为 %s 的用户信息失败!", username));
+ }
+ return info.getData().getSysUser();
+ }
+
+ /**
+ * 远程调用获取用户信息
+ * @param userId
+ * @return
+ */
+ @Override
+ public SysUser remoteGetUserById(String userId){
+ R info = remoteUserService.innerGetById(userId);
+ if (info != null && info.getCode() == CommonConstants.FAIL) {
+ throw new RuntimeException(String.format("获取用户名id为 %s 的用户信息失败!", userId));
+ }
+ return info.getData();
+ }
+
+ @Override
+ public boolean remoteGenerateWord2PDF(XWPFTemplate template, String originalFilename, String savePath) throws Exception{
+ ByteArrayOutputStream fosWord = new ByteArrayOutputStream();
+ template.write(fosWord);
+ template.close();
+
+ //------------
+ ByteArrayInputStream fisWord = new ByteArrayInputStream(fosWord.toByteArray());
+ fosWord.close();
+
+ MockMultipartFile mockMultipartFile = new MockMultipartFile("file", originalFilename + ".docx", "image/jpg", fisWord);
+ Response response = remoteWord2PDFService.word2pdf(mockMultipartFile);
+ fisWord.close();
+
+
+ ByteArrayOutputStream outPDF = new ByteArrayOutputStream();
+ IoUtil.copy(response.body().asInputStream(), outPDF, IoUtil.DEFAULT_MIDDLE_BUFFER_SIZE);
+ ByteArrayInputStream isPDF = new ByteArrayInputStream(outPDF.toByteArray());
+ outPDF.close();
+
+ boolean b = ossFile.fileSave(savePath, isPDF);
+
+ isPDF.close();
+
+ log.info("转换为 PDF 结束");
+ return b;
+ }
+
+ /**
+ * 远程调用根据文件路径获取文件
+ * @param filePath minio上的文件路径
+ * @return
+ * @throws Exception
+ */
+ @Override
+ public ByteArrayInputStream remoteGetFile(String filePath) throws Exception {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ossFile.fileGet(filePath, bos);
+
+ byte[] templateArray = bos.toByteArray();
+
+ ByteArrayInputStream bis = new ByteArrayInputStream(templateArray);
+ bos.close();
+ return bis;
+ }
+
+ /**
+ * 远程调用根据文件路径获取文件
+ * @param filePath minio上的文件路径
+ * @param fileName 名称
+ * @param httpServletResponse
+ * @throws Exception
+ */
+ @Override
+ public void remoteGetFile(String filePath, String fileName, HttpServletResponse httpServletResponse) throws Exception {
+ ossFile.fileGet(filePath, httpServletResponse.getOutputStream());
+ if (StrUtil.isNotBlank(fileName)) {
+ httpServletResponse.setContentType(fileName);
+ }
+ }
+
+ /**
+ * 远程调用根据文件路径获取文件列表
+ * @param filePath minio上的文件路径
+ */
+ @Override
+ public List remoteGetFileList(String filePath) {
+ if (StrUtil.isNotBlank(filePath)) {
+ List fileNameList = ossFile.fileList(filePath);
+ List fileList = fileNameList.stream().map(fileName -> {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ try {
+ ossFile.fileGet(filePath + "/" + fileName, byteArrayOutputStream);
+ return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
+ } catch (Exception e) {
+ e.printStackTrace();
+ log.error("文件获取失败, 文件路径为: {}", filePath + "/" + fileName);
+ }
+ return null;
+ }).collect(Collectors.toList());
+ return fileList;
+ } else {
+ throw new RuntimeException("文件路径不能为空!");
+ }
+ }
+
+ /**
+ * 远程调用-上传文件
+ * @param file 上传的文件对象
+ * @param path 上传到minio的位置
+ * @return
+ */
+ @Override
+ public Map remoteUploadFile(MultipartFile file, String path) {
+ boolean r = ossFile.fileUpload(file, path);
+ if (r) {
+ HashMap resultData = new HashMap<>();
+ resultData.put("fileName", FileNameUtil.getName(file.getOriginalFilename()));
+ resultData.put("path", path);
+ log.info("文件上传成功!");
+ return resultData;
+ } else {
+ String failMsg = String.format("文件名为 %s 的文件上传失败!", file.getOriginalFilename());
+ log.error(failMsg);
+ throw new RuntimeException(failMsg);
+ }
+ }
+}
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/ProcessInspectDataServiceImpl.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/ProcessInspectDataServiceImpl.java
index 9bbd364..883a9cb 100644
--- a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/ProcessInspectDataServiceImpl.java
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/ProcessInspectDataServiceImpl.java
@@ -48,33 +48,34 @@ public class ProcessInspectDataServiceImpl implements ProcessInspectDataService
public String calculateHairCaseIonAbundanceRatioWithinError(double ionAbundanceRatioWithinError, double stdIonAbundanceRatio) {
// 判断是否在离子丰度比允许的最大偏差范围
- if (stdIonAbundanceRatio > TestRecordSampleDataConstant.HAIR_CASE_ION_ABUNDANCE_RATIO_1) {
+ return setHairCaseWhetherCheckOut(
+ Math.abs(ionAbundanceRatioWithinError),
+ getHairCaseIonAbundanceRatioWithinErrorRange(stdIonAbundanceRatio));
+ }
+
+ /**
+ * 根据给定的标准离子丰度比获取生物样本案件离子丰度比相对误差范围内的最大允许相对误差
+ *
+ * @param stdIonAbundanceRatio 标准离子丰度比
+ * @return 发案离子丰度比允许的最大误差值
+ */
+ @Override
+ public Double getHairCaseIonAbundanceRatioWithinErrorRange(double stdIonAbundanceRatio) {
- return setHairCaseWhetherCheckOut(
- ionAbundanceRatioWithinError,
- TestRecordSampleDataConstant.HAIR_CASE_POSITIVE_MAX_ALLOW_ERROR_1,
- TestRecordSampleDataConstant.HAIR_CASE_NEGATIVE_MAX_ALLOW_ERROR_1);
+ // 判断是否在离子丰度比允许的最大偏差范围
+ if (stdIonAbundanceRatio > TestRecordSampleDataConstant.HAIR_CASE_ION_ABUNDANCE_RATIO_1) {
+ return TestRecordSampleDataConstant.HAIR_CASE_POSITIVE_MAX_ALLOW_ERROR_1;
} else if (stdIonAbundanceRatio > TestRecordSampleDataConstant.HAIR_CASE_ION_ABUNDANCE_RATIO_2
&& stdIonAbundanceRatio <= TestRecordSampleDataConstant.HAIR_CASE_ION_ABUNDANCE_RATIO_1) {
- return setHairCaseWhetherCheckOut(
- ionAbundanceRatioWithinError,
- TestRecordSampleDataConstant.HAIR_CASE_POSITIVE_MAX_ALLOW_ERROR_2,
- TestRecordSampleDataConstant.HAIR_CASE_NEGATIVE_MAX_ALLOW_ERROR_2);
-
+ return TestRecordSampleDataConstant.HAIR_CASE_POSITIVE_MAX_ALLOW_ERROR_2;
} else if (stdIonAbundanceRatio > TestRecordSampleDataConstant.HAIR_CASE_ION_ABUNDANCE_RATIO_3
&& stdIonAbundanceRatio <= TestRecordSampleDataConstant.HAIR_CASE_ION_ABUNDANCE_RATIO_2) {
- return setHairCaseWhetherCheckOut(
- ionAbundanceRatioWithinError,
- TestRecordSampleDataConstant.HAIR_CASE_POSITIVE_MAX_ALLOW_ERROR_3,
- TestRecordSampleDataConstant.HAIR_CASE_NEGATIVE_MAX_ALLOW_ERROR_3);
+ return TestRecordSampleDataConstant.HAIR_CASE_POSITIVE_MAX_ALLOW_ERROR_3;
} else {
- return setHairCaseWhetherCheckOut(
- ionAbundanceRatioWithinError,
- TestRecordSampleDataConstant.HAIR_CASE_POSITIVE_MAX_ALLOW_ERROR_4,
- TestRecordSampleDataConstant.HAIR_CASE_NEGATIVE_MAX_ALLOW_ERROR_4);
+ return TestRecordSampleDataConstant.HAIR_CASE_POSITIVE_MAX_ALLOW_ERROR_4;
}
}
@@ -166,11 +167,9 @@ public class ProcessInspectDataServiceImpl implements ProcessInspectDataService
*
* @param ionAbundanceRatioWithinError
* @param positive
- * @param negative
*/
- private String setHairCaseWhetherCheckOut(double ionAbundanceRatioWithinError, double positive, double negative) {
- if (ionAbundanceRatioWithinError < positive
- && ionAbundanceRatioWithinError > negative) {
+ private String setHairCaseWhetherCheckOut(double ionAbundanceRatioWithinError, double positive) {
+ if (ionAbundanceRatioWithinError < positive) {
return TestRecordSampleDataConstant.IS;
} else {
return TestRecordSampleDataConstant.NO;
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/PushDataToLabsCareServiceImpl.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/PushDataToLabsCareServiceImpl.java
new file mode 100644
index 0000000..6047a25
--- /dev/null
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/PushDataToLabsCareServiceImpl.java
@@ -0,0 +1,356 @@
+package digital.laboratory.platform.inspection.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import digital.laboratory.platform.inspection.config.ApiPathProperties;
+import digital.laboratory.platform.inspection.constant.TestRecordSampleDataConstant;
+import digital.laboratory.platform.inspection.convert.TestRecordSampleDataConverter;
+import digital.laboratory.platform.inspection.entity.TestRecordInstrument;
+import digital.laboratory.platform.inspection.entity.TestRecordMethod;
+import digital.laboratory.platform.inspection.entity.TestRecordReagent;
+import digital.laboratory.platform.inspection.entity.TestRecordSampleData;
+import digital.laboratory.platform.inspection.mapper.TestRecordSampleDataMapper;
+import digital.laboratory.platform.inspection.service.*;
+import digital.laboratory.platform.inspection.vo.TestRecordReagentVO;
+import digital.laboratory.platform.inspection.vo.TestRecordSampleDataExpandVO;
+import digital.laboratory.platform.inspection.vo.TestRecordSampleDataVO;
+import digital.laboratory.platform.inspetion.api.entity.EntrustInfo;
+import digital.laboratory.platform.inspetion.api.entity.SampleInfo;
+import digital.laboratory.platform.inspetion.api.entity.TestRecord;
+import digital.laboratory.platform.othersys.utils.HttpsUtils;
+import digital.laboratory.platform.sys.entity.Drug;
+import digital.laboratory.platform.sys.entity.SysUser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.*;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestClientException;
+
+import javax.annotation.Resource;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @author ChenJiangBao
+ * @version 1.0
+ * @description: 推送数据到labscare平台接口
+ * @date 2025/3/31 9:31
+ */
+@Slf4j
+@Service
+public class PushDataToLabsCareServiceImpl implements PushDataToLabsCareService {
+
+ @Resource
+ private TestRecordService testRecordService;
+
+ @Resource
+ private EntrustInfoService entrustInfoService;
+
+ @Resource
+ private SampleInfoService sampleInfoService;
+
+ @Resource
+ private TestRecordReagentService testRecordReagentService;
+
+ @Resource
+ private TestRecordSampleDataMapper testRecordSampleDataMapper;
+
+ @Resource
+ private TestRecordInstrumentService testRecordInstrumentService;
+
+ @Resource
+ private TestRecordMethodService testRecordMethodService;
+
+ @Resource
+ private InspectRecordService inspectRecordService;
+
+ @Resource
+ private ProcessInspectDataService processInspectDataService;
+
+ @Resource
+ private CommonFeignService commonFeignService;
+
+ @Resource
+ private ApiPathProperties apiPathProperties;
+
+
+ private final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
+
+ // "#NULL#" 作为空数据的占位符, 对于系统中没有的信息以占位符代替
+ public final static String NULL_PLACEHOLDER = "#NULL#";
+
+ private final static String BIOLOGY_QUALITATIVE_RECORD = "BiologyQualitativeRecord";
+
+ /**
+ * 做一个定时推送,对于推送失败的委托进行重新推送, 每天凌晨1点推送
+ */
+// @Scheduled(cron = "30 * * * * ?") // 测试
+ @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨 2 点执行
+ public void timingPushDataToLabsCare() {
+ // 查询检验鉴定推送失败的委托
+ List entrustList = entrustInfoService.list(
+ Wrappers.lambdaQuery()
+ .and(wrapper -> wrapper
+ .eq(EntrustInfo::getPushFlag, "")
+ .or()
+ .isNull(EntrustInfo::getPushFlag)
+ .or()
+ .like(EntrustInfo::getPushFlag, "false")
+ )
+ );
+ if (CollUtil.isEmpty(entrustList)) {
+ return;
+ }
+ Map entrustInfoMap = entrustList.stream().collect(Collectors.toMap(EntrustInfo::getId, Function.identity()));
+ // 获取所有完成实验的数据
+ List testRecordList = testRecordService.list(Wrappers.lambdaQuery()
+ .eq(TestRecord::getStatus, 5).in(TestRecord::getBusinessId, entrustList.stream()
+ .map(EntrustInfo::getId).collect(Collectors.toList()))
+ );
+ if (CollUtil.isEmpty(testRecordList)) {
+ return;
+ }
+
+ for (TestRecord testRecord : testRecordList) {
+ EntrustInfo entrustInfo = entrustInfoMap.get(testRecord.getBusinessId());
+ String pushFlag = entrustInfo.getPushFlag();
+ if (StrUtil.isBlank(pushFlag)) {
+ pushBiologyQualitativeRecordData(testRecord.getId());
+ } else {
+ List flagList = StrUtil.split(pushFlag, StrUtil.COMMA).stream().filter(str -> str.contains("false")).collect(Collectors.toList());
+ for (String flag : flagList) {
+ if (flag.contains(BIOLOGY_QUALITATIVE_RECORD)) {
+ pushBiologyQualitativeRecordData(testRecord.getId());
+ } else {
+
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 推送生物检材定性记录
+ * @param testId 实验id
+ */
+ @Override
+ public void pushBiologyQualitativeRecordData(String testId) {
+ TestRecord testRecord = testRecordService.getById(testId);
+ if (testRecord == null) {
+ log.error("实验id为 [{}] 的实验信息查询为空!", testId);
+ return;
+ }
+ // 获取委托信息
+ EntrustInfo entrustInfo = entrustInfoService.getById(testRecord.getBusinessId());
+
+ JSONObject recordJsonPayload = buildBiologyQualitativeRecordJsonPayload(entrustInfo, testRecord);
+
+ String successFlag = BIOLOGY_QUALITATIVE_RECORD + ":true";
+ String failureFlag = BIOLOGY_QUALITATIVE_RECORD + ":false";
+ try {
+ ResponseEntity response = exchangeRemoteApi(recordJsonPayload, apiPathProperties.getBiologyQualitativeRecord());
+
+ if (response.getStatusCode().is2xxSuccessful() && response.getBody().contains("\"status\":200")) {
+ updatePushFlag(entrustInfo, BIOLOGY_QUALITATIVE_RECORD, successFlag);
+ log.info("推送生物定性检验记录数据成功, 受理编号: {}, 响应: {}", entrustInfo.getAcceptNo(), response.getBody());
+ } else {
+ updatePushFlag(entrustInfo, BIOLOGY_QUALITATIVE_RECORD, failureFlag);
+ log.warn("推送生物定性检验记录数据失败, 受理编号: {}, 状态码: {}, 响应: {}",
+ entrustInfo.getAcceptNo(), response.getStatusCode(), response.getBody());
+ }
+ } catch (RestClientException e) {
+ log.error("推送生物定性检验记录数据到 LabsCare 失败, 受理编号: {}", entrustInfo.getAcceptNo(), e);
+ }
+ }
+
+ private JSONObject buildBiologyQualitativeRecordJsonPayload(EntrustInfo entrustInfo, TestRecord testRecord) {
+ // 查询封装需要用到的数据
+ List sampleInfoList = sampleInfoService
+ .lambdaQuery()
+ .in(SampleInfo::getId, testRecord.getSampleTestList())
+ .list();
+ // 检材转map
+ Map sampleInfoMap = sampleInfoList.stream().collect(Collectors.toMap(SampleInfo::getAcceptNo, Function.identity()));
+ // 对受理编号进行分隔
+ List splitAcceptNo = StrUtil.split(entrustInfo.getAcceptNo(), "-");
+
+ // 设置使用的标准物质和试剂
+ List references = testRecordReagentService.voListByWrapper(Wrappers.lambdaQuery()
+ .in(TestRecordReagent::getId, testRecord.getReagentConsumablesList())
+ .eq(TestRecordReagent::getCategory, "标准物质"));
+ // 本次实验使用的标准品
+ String stdStr = references.stream().map(TestRecordReagentVO::getReagentConsumableName).collect(Collectors.joining("、"));
+
+ // 获取标准物质对应的毒品成分
+ Map drugMap = references.stream().map(TestRecordReagentVO::getDrug).collect(Collectors.toMap(Drug::getEnglishName, Function.identity()));
+
+ // 获取检测人用户信息
+ SysUser testUser = commonFeignService.remoteGetUserById(testRecord.getTestUserId());
+
+ // 检测数据信息
+ Map> testDataGroupBySampleNO = testRecordSampleDataMapper
+ .queryTestRecordSampleDataVOList(Wrappers.lambdaQuery()
+ .eq(TestRecordSampleData::getTestId, testRecord.getId())
+ .eq(TestRecordSampleData::getSampleType, TestRecordSampleDataConstant.SAMPLE_TYPE_ANALYTE)
+ ) // 数据库查询, 这个是非标准物质的检测数据
+ .stream().collect(Collectors.groupingBy(TestRecordSampleDataVO::getSampleNo)); // 根据查询出来的结果进行分组
+
+ Map stdTestDataMap = testRecordSampleDataMapper
+ .queryTestRecordSampleDataVOList(Wrappers.lambdaQuery()
+ .eq(TestRecordSampleData::getTestId, testRecord.getId())
+ .eq(TestRecordSampleData::getSampleType, TestRecordSampleDataConstant.SAMPLE_TYPE_STD)
+ ) // 数据库查询, 这个是非标准物质的检测数据
+ .stream().collect(Collectors.toMap(TestRecordSampleDataVO::getCompoundName, Function.identity())); // 根据查询出来的结果进行分组
+
+ // 查询使用的仪器
+ List deviceIdList = testRecord.getDeviceIdList();
+ List instruments = CollectionUtils.isEmpty(deviceIdList)
+ ? Collections.emptyList()
+ : testRecordInstrumentService.list(Wrappers.lambdaQuery()
+ .in(TestRecordInstrument::getId, deviceIdList));
+ String useInstrumentNameStr = CollectionUtils.isEmpty(instruments)
+ ? NULL_PLACEHOLDER
+ : instruments.stream()
+ .map(TestRecordInstrument::getInstrumentName)
+ .collect(Collectors.joining(","));
+
+ // 使用的方法
+ List testMethodList = testRecord.getTestMethodList();
+ List testRecordMethodList = CollectionUtils.isEmpty(testMethodList)
+ ? Collections.emptyList()
+ : testRecordMethodService.list(Wrappers.lambdaQuery()
+ .in(TestRecordMethod::getId, testMethodList));
+ String useMethodNameStr = CollectionUtils.isEmpty(instruments)
+ ? NULL_PLACEHOLDER
+ : testRecordMethodList.stream()
+ .map(TestRecordMethod::getMethodName)
+ .collect(Collectors.joining(","));
+
+ // 开始封装数据参数
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.set("jcxz", inspectRecordService.buildMaterialCharacterDesc(sampleInfoList)); // 检材性状描述
+ jsonObject.set("jdwsh", String.format("[%s]%s号", splitAcceptNo.get(0), splitAcceptNo.get(1))); // 鉴定文书号
+ jsonObject.set("jysj", testRecord.getTestStartDate().format(formatter)); // 检验时间
+ jsonObject.set("jyy1qm", testUser.getName()); // 检测人1
+ jsonObject.set(
+ "jyyq",
+ String.format("对所送检材中是否含有 %s 成分进行定性检测", stdStr)
+ ); // 检验要求,根据模板上的值来生成
+ jsonObject.set("sjr1", entrustInfo.getDeliver1Name()); // 送检人1
+ jsonObject.set("sjr2", entrustInfo.getDeliver2Name()); // 送检人2
+ jsonObject.set("slrq", entrustInfo.getAcceptDate().format(formatter)); // 受理日期
+ jsonObject.set("sprqm", testUser.getName()); // 检测人2
+ jsonObject.set("swmbw", stdStr); // 生物检测目标物
+ jsonObject.set("tpfj", NULL_PLACEHOLDER); // 图谱
+ jsonObject.set("wtdw", entrustInfo.getEntrustDepartment());
+
+ // 生物检材定性记录样品信息
+ List sampleJsonList = new ArrayList<>();
+ testDataGroupBySampleNO.forEach((sampleNo, testDataList) -> {
+ JSONObject sampleJson = new JSONObject();
+ sampleJson.set("caseName", sampleNo); // 样品编号
+
+ // 该样品编号对应的检测数据信息
+ List gauging = testDataList.stream().map(data -> {
+ JSONObject testDataJson = new JSONObject();
+ testDataJson.set("blsjxdwc", data.getRtTimeError()); // 保留时间相对误差(%)
+ TestRecordSampleDataVO stdData = stdTestDataMap.get(data.getCompoundName());
+ testDataJson.set("bzwzblsj", stdData.getTargetRtTime()); // 标准物质保留时间(min)
+ testDataJson.set("bzwzfdb", stdData.getIonAbundanceRatio()); // 标准物质离子对相对丰度比
+ testDataJson.set("bzwzwb", stdData.getCompoundName()); // 标准物质名称
+ testDataJson.set("dcypfdb", data.getIonAbundanceRatio()); // 待测样品离子对相对丰度比
+ testDataJson.set("dxlzd", data.getExpandList().stream().map(TestRecordSampleDataExpandVO::getQualitativeIonPair).collect(Collectors.joining(","))); // 定性离子对
+ testDataJson.set("dxyq", useInstrumentNameStr); // 使用仪器
+ testDataJson.set("fdbwc", data.getIonAbundanceRatioError()); // 丰度比相对偏差
+ testDataJson.set("ffStr", useMethodNameStr); // 所用的方法
+ testDataJson.set("gradientA", NULL_PLACEHOLDER); // 梯度洗脱参考条件-A(%)
+ testDataJson.set("gradientB", NULL_PLACEHOLDER); // 梯度洗脱参考条件-B(%)
+ testDataJson.set("gradientTime", NULL_PLACEHOLDER); // 梯度洗脱参考条件-时间(min)
+ testDataJson.set("jcxz", sampleInfoMap.get(data.getSampleNo()).getForm()); // 检材性状描述
+ testDataJson.set("jdwsh", String.format("[%s]%s号", splitAcceptNo.get(0), splitAcceptNo.get(1))); // 鉴定文书号
+ testDataJson.set("jdyj", inspectRecordService.buildInspectOpinion(Collections.singletonList(TestRecordSampleDataConverter.voToEntity(data))));
+ testDataJson.set("jgpd", data.getWhetherCheckOut()); // 结果判定
+ testDataJson.set("jysj", testRecord.getTestStartDate().format(formatter)); // 检验时间
+ testDataJson.set("jyyq", String.format("对所送检材中是否含有 %s 成分进行定性检测", stdData.getCompoundCnName())); // 检验要求
+ testDataJson.set("mbwblsj", data.getTargetRtTime()); // 目标物保留时间(min)
+ testDataJson.set("projectName", "定性检测"); // 化合物名称, 对方公司并未标明该字段信息
+ // 获取对应的化合物信息
+ Drug drug = drugMap.get(data.getCompoundName());
+ testDataJson.set("pzdy", StrUtil.join(",", drug.getMainCollisionEnergy(), drug.getMinorCollisionEnergy())); // 碰撞电压
+ testDataJson.set("rjwb", NULL_PLACEHOLDER); // 溶剂
+ testDataJson.set("sjr1", entrustInfo.getDeliver1Name()); // 送检人1
+ testDataJson.set("sjr2", entrustInfo.getDeliver2Name()); // 送检人2
+ testDataJson.set("slrq", entrustInfo.getAcceptDate().format(formatter)); // 受理日期
+ testDataJson.set("swbzgzyzb", NULL_PLACEHOLDER); // 标准工作溶液制备
+ testDataJson.set("swdcypzb", NULL_PLACEHOLDER); // 待测样品制备
+ testDataJson.set("swmbw", drug.getName()); // 目标物
+ testDataJson.set("swsj", NULL_PLACEHOLDER); // 上机
+ testDataJson.set("swyxtj", NULL_PLACEHOLDER); // 液相条件
+ testDataJson.set("swzptj", NULL_PLACEHOLDER); // 质谱条件
+ testDataJson.set("wjcjgpd", "±" + processInspectDataService.getHairCaseIonAbundanceRatioWithinErrorRange(stdData.getIonAbundanceRatio().doubleValue())); //相对离子丰度比的最大允许相对误差(%)
+ testDataJson.set("wtdw", entrustInfo.getEntrustDepartment());
+ testDataJson.set("wzdy", StrUtil.join(",", drug.getMainDeclusteringPotential(), drug.getMinorDeclusteringPotential()));
+ return testDataJson;
+ }).collect(Collectors.toList());
+ sampleJson.set("gauging", gauging); // 生物检材定性记录检测信息
+ sampleJsonList.add(sampleJson);
+ });
+ jsonObject.set("sample", sampleJsonList);
+ return jsonObject;
+ }
+
+
+ /**
+ * 通过POST请求与远程API进行交互
+ *
+ * @param jsonObject 包含请求数据的JSONObject对象
+ * @param url 远程API的URL
+ * @return ResponseEntity 包含远程API响应数据的ResponseEntity对象
+ */
+ private ResponseEntity exchangeRemoteApi(JSONObject jsonObject, String url) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON); // 设置为 application/json
+ HttpEntity requestEntity = new HttpEntity<>(jsonObject, headers);
+ ResponseEntity response = HttpsUtils
+ .genRestTemplate()
+ .exchange(
+ url,
+ HttpMethod.POST,
+ requestEntity,
+ String.class
+ );
+ return response;
+ }
+
+ /**
+ * 更新推送标志
+ *
+ * @param entrust 委托对象
+ * @param pushType 推送类型 EntrustLetter 委托书 | ItemConfirmLetter 事项确认书
+ * @param newFlag 新的推送标志
+ */
+ private void updatePushFlag(EntrustInfo entrust, String pushType, String newFlag) {
+ List flagList = Optional.ofNullable(StrUtil.split(entrust.getPushFlag(), StrUtil.COMMA))
+ .orElse(new ArrayList<>());
+
+ // 判断是否已有 指定的推送类型,如果有就替换,否则添加
+ boolean exists = flagList.stream().anyMatch(flag -> flag.contains(pushType));
+ if (exists) {
+ flagList.replaceAll(flag -> flag.contains(pushType) ? newFlag : flag);
+ } else {
+ flagList.add(newFlag);
+ }
+ // 更新标识, 防止定时任务那里连续推送数据时推送标识更新出错
+ entrust.setPushFlag(String.join(",", flagList));
+ // 更新数据库
+ entrustInfoService.update(Wrappers.lambdaUpdate()
+ .eq(EntrustInfo::getId, entrust.getId())
+ .set(EntrustInfo::getPushFlag, String.join(",", flagList)));
+ }
+
+}
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/TestRecordReagentServiceImpl.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/TestRecordReagentServiceImpl.java
index 4307a28..1c7c750 100644
--- a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/TestRecordReagentServiceImpl.java
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/TestRecordReagentServiceImpl.java
@@ -1,5 +1,6 @@
package digital.laboratory.platform.inspection.service.impl;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
@@ -66,6 +67,17 @@ public class TestRecordReagentServiceImpl extends ServiceImpl voListByWrapper(Wrapper queryWrapper) {
+ return TestRecordReagentConverter.entityToVOList(super.list(queryWrapper));
+ }
+
@Override
public TestRecordReagent addTestRecordReagent(TestRecordReagent testRecordReagent) {
if (StringUtils.isBlank(testRecordReagent.getId())) {
diff --git a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/TestRecordSampleDataServiceImpl.java b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/TestRecordSampleDataServiceImpl.java
index 8e1d39b..604f206 100644
--- a/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/TestRecordSampleDataServiceImpl.java
+++ b/dlp-drugtesting-biz/src/main/java/digital/laboratory/platform/inspection/service/impl/TestRecordSampleDataServiceImpl.java
@@ -28,6 +28,7 @@ import digital.laboratory.platform.inspection.enums.StdSolutionNum;
import digital.laboratory.platform.inspection.enums.TaskTestDataStatus;
import digital.laboratory.platform.inspection.event.AuditDataExecutionEvent;
import digital.laboratory.platform.inspection.event.FinishTestExecutionEvent;
+import digital.laboratory.platform.inspection.event.PushDataToLabsCareEvent;
import digital.laboratory.platform.inspection.mapper.SampleInjectorMapper;
import digital.laboratory.platform.inspection.mapper.TestRecordSampleDataMapper;
import digital.laboratory.platform.inspection.query.AnalysisTestResultPageQuery;
@@ -250,20 +251,6 @@ public class TestRecordSampleDataServiceImpl extends ServiceImpl list = baseMapper.queryTestRecordSampleDataVOList(Wrappers.lambdaQuery()
.eq(TestRecordSampleData::getTestId, testId)
.last("ORDER BY compound_name, SUBSTRING_INDEX(name, '-', -1) + 0"));
-// List