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 prepareAndReleaseAfterCompletion(String tenantId, UserInfo user, List testWorkItemIds, Map rows) { String actualTenantId = StringUtils.hasText(tenantId) ? tenantId.trim() : "0"; List ids = uniqueIds(testWorkItemIds); List 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 precheckLocal(List ids, Map rows, String userId) { List 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 validateBatch(List requestedIds, RecordFreezeCheckBatch batch, List localIssues) { if (batch == null || batch.getItems() == null) throw new DataException("ELN 冻结校验响应无效,已拒绝提交"); Map byId = new HashMap<>(); List 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 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 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 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 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 testWorkItemIds) { RecordFreezeReleaseRequest request = new RecordFreezeReleaseRequest(); request.setTenantId(tenantId); request.setOperationCode(operationCode); request.setTestWorkItemIds(testWorkItemIds); release(request); } private static List uniqueIds(Collection ids) { if (ids == null) return Collections.emptyList(); Set 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; } } }