package jnpf.limsService.impl;
|
|
import cn.hutool.core.util.IdUtil;
|
import jnpf.base.ActionResult;
|
import jnpf.base.ActionResultCode;
|
import jnpf.base.UserInfo;
|
import jnpf.eln.RecordFreezeApi;
|
import jnpf.eln.RecordFreezeCheckBatch;
|
import jnpf.eln.RecordFreezeCheckRequest;
|
import jnpf.eln.RecordFreezeCheckResult;
|
import jnpf.eln.RecordFreezeIssue;
|
import jnpf.eln.RecordFreezeReleaseRequest;
|
import jnpf.exception.DataException;
|
import jnpf.limsEntity.JianyanStatus;
|
import jnpf.limsEntity.LimsJianyanXiangEntity;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Component;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
import org.springframework.util.StringUtils;
|
|
import java.util.ArrayList;
|
import java.util.Collection;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
|
/**
|
* LIMS 的唯一 ELN 提交门禁。先完成本地权限/状态预检,再向 ELN 申请冻结;
|
* 任何一项未就绪均在本地 UPDATE 前抛出,保证 Controller 事务零写入。
|
*/
|
@Slf4j
|
@Component
|
public class LimsElnRecordFreezeGate {
|
private final RecordFreezeApi recordFreezeApi;
|
|
public LimsElnRecordFreezeGate(RecordFreezeApi recordFreezeApi) {
|
this.recordFreezeApi = recordFreezeApi;
|
}
|
|
public Map<String, SubmissionValues> prepareAndReleaseAfterCompletion(String tenantId, UserInfo user,
|
List<String> testWorkItemIds,
|
Map<String, LimsJianyanXiangEntity> rows) {
|
String actualTenantId = StringUtils.hasText(tenantId) ? tenantId.trim() : "0";
|
List<String> ids = uniqueIds(testWorkItemIds);
|
List<String> localIssues = new ArrayList<>();
|
if (ids.size() != testWorkItemIds.size()) localIssues.add("提交检验项存在重复或无效 id");
|
localIssues.addAll(precheckLocal(ids, rows, user == null ? null : user.getUserId()));
|
RecordFreezeCheckRequest request = new RecordFreezeCheckRequest();
|
request.setTenantId(actualTenantId);
|
request.setUserId(user.getUserId());
|
request.setOperationCode(IdUtil.getSnowflakeNextIdStr());
|
request.setTestWorkItemIds(ids);
|
RecordFreezeCheckBatch batch;
|
try {
|
batch = preparedBatch(request);
|
} catch (DataException e) {
|
releaseBestEffort(actualTenantId, request.getOperationCode(), ids);
|
throw e;
|
} catch (RuntimeException e) {
|
// 响应丢失时无法确认权威复算是否已提交;释放确定性 fence 并拒绝本次提交,
|
// 避免同操作码自动重试产生重复计算日志和记录版本递增。
|
releaseBestEffort(actualTenantId, request.getOperationCode(), ids);
|
throw new DataException("ELN 冻结校验服务调用失败,已拒绝提交");
|
}
|
registerRelease(actualTenantId, request.getOperationCode(), ids);
|
return validateBatch(ids, batch, localIssues);
|
}
|
|
private static List<String> precheckLocal(List<String> ids, Map<String, LimsJianyanXiangEntity> rows, String userId) {
|
List<String> issues = new ArrayList<>();
|
for (String id : ids) {
|
LimsJianyanXiangEntity row = rows.get(id);
|
if (row == null) {
|
issues.add(id + ": 检验项不存在");
|
continue;
|
}
|
String status = row.getJianyanZhuangtai();
|
if (!JianyanStatus.PENDING_TEST.getCode().equals(status)
|
&& !JianyanStatus.CANCELLED.getCode().equals(status)
|
&& !JianyanStatus.AUDIT_REJECTED.getCode().equals(status)) {
|
issues.add(id + ": 检验项状态不允许提交");
|
}
|
boolean external = "yes".equalsIgnoreCase(row.getShifouWaijian());
|
String expectedUser = external ? row.getWaijianLururen() : row.getJianchaRen();
|
if (!StringUtils.hasText(expectedUser) || !expectedUser.equals(userId)) {
|
issues.add(id + ": 当前用户不是检验结果录入负责人");
|
}
|
}
|
return issues;
|
}
|
|
private static Map<String, SubmissionValues> validateBatch(List<String> requestedIds,
|
RecordFreezeCheckBatch batch,
|
List<String> localIssues) {
|
if (batch == null || batch.getItems() == null) throw new DataException("ELN 冻结校验响应无效,已拒绝提交");
|
Map<String, RecordFreezeCheckResult> byId = new HashMap<>();
|
List<String> issues = new ArrayList<>(localIssues);
|
for (RecordFreezeCheckResult item : batch.getItems()) {
|
if (item == null || !StringUtils.hasText(item.getTestWorkItemId()) || byId.put(item.getTestWorkItemId(), item) != null) {
|
throw new DataException("ELN 冻结校验响应包含重复或无效检验项,已拒绝提交");
|
}
|
}
|
if (byId.size() != requestedIds.size() || !byId.keySet().containsAll(requestedIds)) {
|
throw new DataException("ELN 冻结校验响应缺少检验项,已拒绝提交");
|
}
|
for (String id : requestedIds) {
|
RecordFreezeCheckResult item = byId.get(id);
|
if (RecordFreezeCheckResult.NOT_APPLICABLE.equals(item.getStatus())) continue;
|
if (!RecordFreezeCheckResult.READY.equals(item.getStatus()) || !StringUtils.hasText(item.getReleaseToken())) {
|
if (item.getDiagnostics() == null || item.getDiagnostics().isEmpty()) {
|
issues.add(id + ": ELN 记录未准备就绪");
|
} else {
|
for (RecordFreezeIssue issue : item.getDiagnostics()) {
|
issues.add(id + ": " + (issue == null || !StringUtils.hasText(issue.getMessage())
|
? "ELN 记录未准备就绪" : issue.getMessage()));
|
}
|
}
|
}
|
}
|
if (!issues.isEmpty()) throw new DataException("ELN 可冻结校验失败:" + String.join(";", issues));
|
Map<String, SubmissionValues> values = new HashMap<>();
|
for (String id : requestedIds) {
|
RecordFreezeCheckResult item = byId.get(id);
|
if (RecordFreezeCheckResult.NOT_APPLICABLE.equals(item.getStatus())) continue;
|
if (!StringUtils.hasText(item.getTestResult()) || !StringUtils.hasText(item.getTestConclusion())) {
|
throw new DataException("ELN 可冻结校验响应缺少已校验的检验结果或结论,已拒绝提交");
|
}
|
values.put(id, new SubmissionValues(item.getTestResult().trim(),
|
item.getTestConclusion().trim()));
|
}
|
return values;
|
}
|
|
private void registerRelease(String tenantId, String operationCode, List<String> testWorkItemIds) {
|
RecordFreezeReleaseRequest request = new RecordFreezeReleaseRequest();
|
request.setTenantId(tenantId);
|
request.setOperationCode(operationCode);
|
request.setTestWorkItemIds(testWorkItemIds);
|
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
release(request);
|
throw new DataException("LIMS 提交事务未激活,已释放 ELN 冻结");
|
}
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
@Override
|
public void afterCompletion(int status) {
|
release(request);
|
}
|
});
|
}
|
|
private void release(RecordFreezeReleaseRequest request) {
|
try {
|
ActionResult<Boolean> response = recordFreezeApi.release(request);
|
if (response == null || !ActionResultCode.SUCCESS.getCode().equals(response.getCode())
|
|| !Boolean.TRUE.equals(response.getData())) {
|
log.error("[submit-result] ELN 冻结释放失败 operation={}", request.getOperationCode());
|
}
|
} catch (RuntimeException e) {
|
log.error("[submit-result] ELN 冻结释放异常 operation={}", request.getOperationCode(), e);
|
}
|
}
|
|
private RecordFreezeCheckBatch preparedBatch(RecordFreezeCheckRequest request) {
|
ActionResult<RecordFreezeCheckBatch> response = recordFreezeApi.prepare(request);
|
if (response == null) throw new IllegalStateException("ELN 冻结校验服务无响应");
|
if (!ActionResultCode.SUCCESS.getCode().equals(response.getCode()) || response.getData() == null) {
|
throw new DataException(StringUtils.hasText(response.getMsg())
|
? response.getMsg() : "ELN 冻结校验服务不可用,已拒绝提交");
|
}
|
return response.getData();
|
}
|
|
private void releaseBestEffort(String tenantId, String operationCode, List<String> testWorkItemIds) {
|
RecordFreezeReleaseRequest request = new RecordFreezeReleaseRequest();
|
request.setTenantId(tenantId);
|
request.setOperationCode(operationCode);
|
request.setTestWorkItemIds(testWorkItemIds);
|
release(request);
|
}
|
|
private static List<String> uniqueIds(Collection<String> ids) {
|
if (ids == null) return Collections.emptyList();
|
Set<String> unique = new LinkedHashSet<>();
|
for (String id : ids) if (StringUtils.hasText(id)) unique.add(id.trim());
|
return new ArrayList<>(unique);
|
}
|
|
/** 仅 READY 的 ELN 项携带;LIMS 无需了解 ELN 字段目录。 */
|
public static final class SubmissionValues {
|
private final String testResult;
|
private final String testConclusion;
|
|
private SubmissionValues(String testResult, String testConclusion) {
|
this.testResult = testResult;
|
this.testConclusion = testConclusion;
|
}
|
|
public String getTestResult() {
|
return testResult;
|
}
|
|
public String getTestConclusion() {
|
return testConclusion;
|
}
|
}
|
}
|