From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master
---
jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsJianceTaskServiceImpl.java | 798 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 798 insertions(+), 0 deletions(-)
diff --git a/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsJianceTaskServiceImpl.java b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsJianceTaskServiceImpl.java
new file mode 100644
index 0000000..4885c75
--- /dev/null
+++ b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsJianceTaskServiceImpl.java
@@ -0,0 +1,798 @@
+package jnpf.limsService.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import jnpf.audit.model.AuditEventDTO;
+import jnpf.audit.sdk.AuditClient;
+import jnpf.audit.AuditConsts;
+import jnpf.audit.sdk.annotation.AuditLog;
+import jnpf.base.UserInfo;
+import jnpf.limsEntity.FenjianZhuangtai;
+import jnpf.limsEntity.JianyanStatus;
+import jnpf.limsEntity.LimsFenjianItem;
+import jnpf.limsEntity.LimsJianceTaskAcceptParam;
+import jnpf.limsEntity.LimsJianyanAssignItem;
+import jnpf.limsEntity.LimsJianyanAssignParam;
+import jnpf.limsEntity.LimsJianyanAuditFailParam;
+import jnpf.limsEntity.LimsJianyanAuditItem;
+import jnpf.limsEntity.LimsJianyanAuditOutcome;
+import jnpf.limsEntity.LimsJianyanAuditParam;
+import jnpf.limsEntity.LimsJianyanResultItem;
+import jnpf.limsEntity.LimsJianyanResultParam;
+import jnpf.limsEntity.LimsQingyandanEntity;
+import jnpf.limsEntity.LimsSignUsageTarget;
+import jnpf.limsEntity.LimsSubmitResultOutcome;
+import jnpf.limsEntity.LimsJianyanXiangEntity;
+import jnpf.limsEntity.bizlog.BizLogAction;
+import jnpf.limsEntity.bizlog.BizLogType;
+import jnpf.limsMapper.LimsJianyanXiangMapper;
+import jnpf.limsMapper.LimsQingyandanMapper;
+import jnpf.limsService.LimsJianceTaskService;
+import jnpf.limsService.LimsQingyandanReportService;
+import jnpf.limsService.LimsYichangDiaochaService;
+import jnpf.permission.UserRelationApi;
+import jnpf.permission.entity.UserRelationEntity;
+import jnpf.constant.PermissionConst;
+import jnpf.exception.DataException;
+import jnpf.util.JsonUtil;
+import jnpf.util.RandomUtil;
+import jnpf.util.UserProvider;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class LimsJianceTaskServiceImpl implements LimsJianceTaskService {
+
+ @Autowired
+ private LimsJianyanXiangMapper limsJianyanXiangMapper;
+
+ @Autowired
+ private LimsQingyandanMapper limsQingyandanMapper;
+
+ @Autowired
+ private UserRelationApi userRelationApi;
+
+ @Autowired
+ private LimsQingyandanReportService limsQingyandanReportService;
+
+ @Autowired
+ private LimsYichangDiaochaService limsYichangDiaochaService;
+
+ @Autowired
+ private LimsOperationalMessagePublisher operationalMessagePublisher;
+
+ /**
+ * 灞� 3 鏄惧紡瀹¤鍏ュ彛銆�**蹇呴』鐢� {@link ObjectProvider} 娉ㄥ叆**锛歿@code audit.enabled=false} 鏃�
+ * 鏁翠釜鑷姩閰嶇疆绫昏繛鍚� AuditClient Bean 涓�璧风己甯紝鏅�� @Autowired 浼氳鏈湇鍔¤捣涓嶆潵銆�
+ */
+ @Autowired
+ private ObjectProvider<AuditClient> auditClientProvider;
+
+ @Override
+ @AuditLog(
+ action = "ACCEPT", label = "鏍峰搧鍒嗘", bizType = "JIANCE_TASK",
+ bizCodeExpr = "#result != null && !#result.isEmpty() ? #param.jianyanLiushuihao : ''",
+ targetTable = "lims_jianyan_xiang", targetIdExpr = "#param.data[0].id",
+ targetIdsExpr = "#param.data.![id]",
+ diff = true, entityClass = LimsJianyanXiangEntity.class
+ )
+ public List<LimsSignUsageTarget> fenjianSamples(LimsJianceTaskAcceptParam param) {
+ if (param == null || param.getData() == null || param.getData().isEmpty()) {
+ return Collections.emptyList();
+ }
+ if (param.getBizSign() == null || param.getBizSign().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ // 鍒嗘浜� = 褰撳墠鐧诲綍鐢ㄦ埛锛屽垎妫�鏃ユ湡 = 褰撳墠鏃堕棿锛堝啓鍏ヨ鎺ㄨ繘鐨勮锛�
+ String fenjianRen = UserProvider.getUser().getUserId();
+ Date fenjianRiqi = new Date();
+ // 缁勫悎id锛氭暣娆¤姹傜敓鎴愪竴涓紝浠呭啓鍏� shifou_zuhe_fenjian=yes 鐨勮锛堥潪缁勫悎琛屼笉浼氱敤鍒帮級
+ String zuheId = RandomUtil.uuId();
+ // 缁勫悎鍒嗘閲� / 鍗曚綅锛氳姹傜骇鍏变韩鍊硷紙鍓嶇鍦� param 椤跺眰鎻愪氦涓�娆★級锛屽墠绔紶浜�(闈� null)灏卞啓鍏ユ湰娆″垎妫�鐨勬楠岄」琛�
+ String zuheFenjianLiang = param.getZuheFenjianLiang();
+ String zuheFenjianDanwei = param.getZuheFenjianDanwei();
+
+ // 棰勫姞杞界洰鏍囪锛岃鍚勮鏍峰搧绾� shifou_zuhe_fenjian锛堝叆鍙備笉甯﹁瀛楁锛岄渶鏌� DB 鍒ゆ柇鏄惁缁勫悎琛岋級
+ List<String> validIds = param.getData().stream()
+ .filter(i -> i != null && i.getId() != null && !i.getId().isEmpty())
+ .map(LimsFenjianItem::getId)
+ .collect(Collectors.toList());
+ Map<String, LimsJianyanXiangEntity> rowMap = new HashMap<>();
+ if (!validIds.isEmpty()) {
+ for (LimsJianyanXiangEntity r : loadRowsByIds(validIds)) {
+ if (r != null && r.getId() != null) {
+ rowMap.put(r.getId(), r);
+ }
+ }
+ }
+
+ // 蹇呴』鏁存壒鏍¢獙瀹屽啀寮�濮� UPDATE銆侰ontroller 浼氭妸 DataException 杞垚 R.error锛涜嫢杈规牎楠岃竟鏇存柊锛�
+ // 鍚庝竴琛岄潪娉曟椂鍓嶉潰宸叉洿鏂扮殑琛屼細鎻愪氦锛屼絾 SIGN_USED 灏氭湭鐢熸垚锛屽舰鎴愭棤绛惧悕璇佹嵁鐨勫崐鎵瑰啓鍏ャ��
+ validateFenjianItems(param.getData());
+ List<LimsSignUsageTarget> affectedTargets = new ArrayList<>();
+
+ BigDecimal fenjianShuliang = BigDecimal.ZERO;
+ if (zuheFenjianLiang != null && !zuheFenjianLiang.trim().isEmpty()) {
+ fenjianShuliang = parseFenjianQuantity(zuheFenjianLiang, "缁勫悎鍒嗘閲�");
+ }
+ for (LimsFenjianItem item : param.getData()) {
+ if (item == null || item.getId() == null || item.getId().isEmpty()) {
+ continue;
+ }
+
+ LimsJianyanXiangEntity row = rowMap.get(item.getId());
+ if (row == null) {
+ continue;
+ }
+ if (zuheFenjianLiang == null || zuheFenjianLiang.trim().isEmpty()) {
+ fenjianShuliang = fenjianShuliang.add(
+ parseFenjianQuantity(item.getFenjianLiang(), "鍒嗘閲�"));
+ }
+
+ // 浠呮帹杩� submit 鏃跺垱寤虹殑 created 琛岋紱鍏朵綑鐘舵�� / 宸茶蒋鍒� / 涓嶅瓨鍦� 鑷姩璺宠繃锛坅ffected=0锛�
+ UpdateWrapper<LimsJianyanXiangEntity> wrapper = new UpdateWrapper<>();
+ wrapper.eq("f_id", item.getId());
+ wrapper.eq("jianyan_zhuangtai", JianyanStatus.CREATED.getCode());
+ wrapper.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ wrapper.set("jianyan_zhuangtai", JianyanStatus.PENDING_ASSIGN.getCode());
+ wrapper.set("fenjian_zhuangtai", FenjianZhuangtai.FENJIAN.getCode());
+ wrapper.set("fenjian_liang", item.getFenjianLiang());
+ wrapper.set("fenjian_danwei", item.getFenjianDanwei());
+ wrapper.set("fenjian_fenzhuang_rongqi", item.getFenjianFenzhuangRongqi());
+ wrapper.set("fenjian_baocun_tiaojian", item.getFenjianBaocunTiaojian());
+ wrapper.set("fenjian_ren", fenjianRen);
+ wrapper.set("fenjian_riqi", fenjianRiqi);
+
+ // 缁勫悎涓夊垪浠呭啓璇ヨ DB 涓� shifou_zuhe_fenjian=yes 鐨勭粍鍚堣锛�
+ // zuhe_id 鐢ㄦ湰娆¤姹傚叡浜殑 zuheId锛涚粍鍚堝垎妫�閲� / 鍗曚綅鍙栬姹傜骇鍏变韩鍊硷紝鍓嶇浼犱簡(闈� null)鎵嶅啓銆佷笉鏍¢獙绌�
+ if ("yes".equalsIgnoreCase(row.getShifouZuheFenjian())) {
+ wrapper.set("zuhe_id", zuheId);
+ if (zuheFenjianLiang != null) {
+ wrapper.set("zuhe_fenjian_liang", zuheFenjianLiang);
+ }
+ if (zuheFenjianDanwei != null) {
+ wrapper.set("zuhe_fenjian_danwei", zuheFenjianDanwei);
+ }
+ }
+
+ int rows = limsJianyanXiangMapper.update(null, wrapper);
+ if (rows > 0) {
+ affectedTargets.add(signUsageTarget(row));
+ }
+ }
+
+ // 鏇存柊璇烽獙鍗曞簱瀛樻暟閲�
+ updateQingyandanKucun(param.getJianyanLiushuihao(), fenjianShuliang);
+
+ return affectedTargets;
+ }
+
+ @Override
+ @AuditLog(
+ action = "ASSIGN",
+ label = "妫�楠屼换鍔″垎閰�",
+ bizType = "JIANCE_TASK",
+ bizCodeExpr = "#result[0].bizCode",
+ targetTable = "lims_jianyan_xiang",
+ targetIdExpr = "#param.data[0].id",
+ targetIdsExpr = "#param.data.![id]",
+ diff = true,
+ entityClass = LimsJianyanXiangEntity.class
+ )
+ public List<LimsSignUsageTarget> assignJianyanXiang(LimsJianyanAssignParam param) {
+ if (param == null || param.getData() == null || param.getData().isEmpty()) {
+ return Collections.emptyList();
+ }
+ if (param.getBizSign() == null || param.getBizSign().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ UserInfo userInfo = UserProvider.getUser();
+ List<LimsSignUsageTarget> affectedTargets = new ArrayList<>();
+
+ // 棰勫姞杞芥墍鏈夌洰鏍囪锛屾寜 shifou_waijian 鍐冲畾姣忚鐨勫繀濉垎鏀細
+ // "yes" 鈫� waijian_danwei + waijian_lururen 蹇呭~锛堝妫�鍒嗛厤锛�
+ // 鍏朵粬 鈫� jiancha_ren + wancheng_riqi 蹇呭~锛堟櫘閫氬垎閰嶏級
+ List<String> validIds = param.getData().stream()
+ .filter(i -> i != null && i.getId() != null && !i.getId().isEmpty())
+ .map(LimsJianyanAssignItem::getId)
+ .collect(Collectors.toList());
+ Map<String, LimsJianyanXiangEntity> rowMap = new HashMap<>();
+ if (!validIds.isEmpty()) {
+ for (LimsJianyanXiangEntity r : loadRowsByIds(validIds)) {
+ if (r != null && r.getId() != null) {
+ rowMap.put(r.getId(), r);
+ }
+ }
+ }
+ validateAssignItems(param.getData(), rowMap);
+
+ for (LimsJianyanAssignItem item : param.getData()) {
+ if (item == null || item.getId() == null || item.getId().isEmpty()) {
+ continue;
+ }
+ LimsJianyanXiangEntity existing = rowMap.get(item.getId());
+ // 娌℃湁棰勫姞杞藉埌鐨勮涓嶆墽琛� UPDATE锛屼繚璇佹垚鍔熻蹇呯劧鑳芥槧灏勪负瀹為檯绛惧悕鐩爣銆�
+ if (existing == null) {
+ continue;
+ }
+ boolean isWaijian = "yes".equalsIgnoreCase(existing.getShifouWaijian());
+
+ UpdateWrapper<LimsJianyanXiangEntity> wrapper = new UpdateWrapper<>();
+ Date assignedAt = new Date();
+ wrapper.eq("f_id", item.getId());
+ // 鍏佽鍒濇鍒嗛厤 + 鏀规淳锛歱ending_assign / pending_test 鍧囧彲锛屽鏍镐腑 / 宸插鏍哥姝�
+ wrapper.in("jianyan_zhuangtai",
+ JianyanStatus.PENDING_ASSIGN.getCode(),
+ JianyanStatus.PENDING_TEST.getCode());
+ wrapper.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ wrapper.set("renwu_fenpeiren", userInfo.getUserId());
+ wrapper.set("renwu_fenpei_shijian", assignedAt);
+ wrapper.set("jianyan_zhuangtai", JianyanStatus.PENDING_TEST.getCode());
+ if (item.getHetonghao() != null && !item.getHetonghao().isEmpty()) {
+ wrapper.set("hetonghao", item.getHetonghao());
+ }
+ if (item.getBeizhu() != null && !item.getBeizhu().isEmpty()) {
+ wrapper.set("beizhu", item.getBeizhu());
+ }
+
+ if (isWaijian) {
+ wrapper.set("waijian_danwei", item.getWaijianDanwei());
+ wrapper.set("waijian_lururen", item.getWaijianLururen());
+ // 鍚屾椂娓呯┖妫�楠屽憳 / 瀹屾垚鏃ユ湡锛岄伩鍏嶆敼娲惧垏鎹负澶栨鍚庢畫鐣欐棫鍊�
+ wrapper.set("jiancha_ren", null);
+ wrapper.set("wancheng_riqi", null);
+ } else {
+ // wancheng_riqi 鏀逛负闈炲繀濉紙涓氬姟鍏佽"鍏堝垎閰嶄汉锛屽悗缁啀琛ュ畬鎴愭棩鏈�"锛夈��
+ // null / 闈� null 閮芥寜鍏ュ弬鍘熸牱鍐欏叆锛岀敱 daily 鎻愰啋浠诲姟鍦� IS NOT NULL 鏃舵墠鍛戒腑銆�
+ wrapper.set("jiancha_ren", item.getJianchaRen());
+ wrapper.set("wancheng_riqi", item.getWanchengRiqi());
+ }
+ // 浠讳綍鍒嗛厤 / 鏀规淳閮芥竻绌� remind_flags锛氱瓑鍚岄噸鏂拌鏃讹紝涓嬩竴杞� D-N 閲嶆柊璁$畻銆�
+ wrapper.set("remind_flags", null);
+
+ int rows = limsJianyanXiangMapper.update(null, wrapper);
+ if (rows > 0) {
+ affectedTargets.add(signUsageTarget(existing));
+ operationalMessagePublisher.taskAssigned(existing, item, userInfo, assignedAt);
+ }
+ }
+
+ return affectedTargets;
+ }
+
+ @Override
+ @AuditLog(
+ action = "RESULT_SUBMIT", label = "妫�楠岀粨鏋滃綍鍏�", bizType = "JIANCE_TASK",
+ bizCodeExpr = "#result.affected > 0 ? #result.affectedTargets[0].bizCode : ''",
+ targetTable = "lims_jianyan_xiang", targetIdExpr = "#param.data[0].id",
+ targetIdsExpr = "#param.data.![id]",
+ diff = true, entityClass = LimsJianyanXiangEntity.class
+ )
+ public LimsSubmitResultOutcome submitJianyanResult(LimsJianyanResultParam param) {
+ if (param == null || param.getData() == null || param.getData().isEmpty()) {
+ return new LimsSubmitResultOutcome();
+ }
+ if (param.getBizSign() == null || param.getBizSign().isEmpty()) {
+ return new LimsSubmitResultOutcome();
+ }
+
+ UserInfo userInfo = UserProvider.getUser();
+ String currentUserId = userInfo.getUserId();
+ Date now = new Date();
+ int totalAffected = 0;
+ LimsSubmitResultOutcome outcome = new LimsSubmitResultOutcome();
+
+ List<String> incomingIds = param.getData().stream()
+ .filter(i -> i != null)
+ .map(LimsJianyanResultItem::getId)
+ .collect(Collectors.toList());
+
+ // 棰勫姞杞借锛氬妫�琛屾病鏈夋楠屽憳锛坖iancha_ren=NULL锛夛紝涓嶈兘鐢� jiancha_ren=currentUserId 杩囨护锛�
+ // 鍏朵粬鍒楋紙qingyandan_id锛変篃浼氱敤鏉ュ悗缁厹搴曡Е鍙� generateReportData
+ List<String> validIds = incomingIds.stream()
+ .filter(id -> id != null && !id.isEmpty())
+ .collect(Collectors.toList());
+ Map<String, LimsJianyanXiangEntity> rowMap = new HashMap<>();
+ if (!validIds.isEmpty()) {
+ for (LimsJianyanXiangEntity r : loadRowsByIds(validIds)) {
+ if (r != null && r.getId() != null) {
+ rowMap.put(r.getId(), r);
+ }
+ }
+ }
+ Set<String> affectedQingyandanIds = new HashSet<>();
+ List<LimsJianyanXiangEntity> messageRows = new ArrayList<>();
+
+ for (LimsJianyanResultItem item : param.getData()) {
+ if (item == null) {
+ continue;
+ }
+ if (item.getId() == null || item.getId().isEmpty()) {
+ continue;
+ }
+ if (item.getJianyanJieguo() == null || item.getJianyanJieguo().isEmpty()) {
+ continue;
+ }
+ if (item.getJianyanJielun() == null || item.getJianyanJielun().isEmpty()) {
+ continue;
+ }
+
+ LimsJianyanXiangEntity existing = rowMap.get(item.getId());
+ // 娌℃湁棰勫姞杞藉埌鐨勮涓嶆墽琛� UPDATE锛岄伩鍏嶅苟鍙戞彃鍏ュ悗鍐欐垚鍔熷嵈缂哄皯 bizCode/target 鍏冩暟鎹��
+ if (existing == null) {
+ continue;
+ }
+ boolean isWaijian = "yes".equalsIgnoreCase(existing.getShifouWaijian());
+
+ UpdateWrapper<LimsJianyanXiangEntity> wrapper = new UpdateWrapper<>();
+ wrapper.eq("f_id", item.getId());
+ // 鍏佽鍒濇褰曞叆锛坧ending_test锛夋垨閲嶆柊褰曞叆锛坅udit_rejected 瀹℃牳閫�鍥炲悗锛夋垨宸叉挙鍥炵殑閲嶆柊褰曞叆
+ wrapper.in("jianyan_zhuangtai",
+ JianyanStatus.PENDING_TEST.getCode(),
+ JianyanStatus.CANCELLED.getCode(),
+ JianyanStatus.AUDIT_REJECTED.getCode());
+ // 鏉冮檺杩囨护锛氫粎闈炲妫�椤规牎楠�"鍒嗛厤缁欒嚜宸辩殑浠诲姟"銆傚妫�琛� jiancha_ren=NULL锛�
+ // 涓嶅仛璇ユ牎楠岋紝鐢� waijian_lururen / 涓婁笅娓镐笟鍔$害鏉熷厹搴曘��
+ if (!isWaijian) {
+ wrapper.eq("jiancha_ren", currentUserId);
+ }
+ wrapper.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ wrapper.set("jianyan_jieguo", item.getJianyanJieguo());
+ wrapper.set("jianyan_jielun", item.getJianyanJielun());
+ wrapper.set("biz_files", item.getBizFiles());
+ wrapper.set("jieguo_luru_riqi", now);
+ wrapper.set("jianyan_zhuangtai", JianyanStatus.PENDING_AUDIT.getCode());
+ // 鍐欏洖鏈閫夋嫨鐨勫紓甯歌皟鏌ョ被鍨嬶紙na/ad/oos/oot锛夛紝渚涘彴璐︿笌鍚庣画鑱斿姩浣跨敤
+ wrapper.set("yichang_diaocha_leixing", item.getYichangDiaochaLeixing());
+ int rows = limsJianyanXiangMapper.update(null, wrapper);
+ if (rows > 0 && existing.getQingyandanId() != null) {
+ affectedQingyandanIds.add(existing.getQingyandanId());
+ }
+ if (rows > 0) {
+ outcome.getAffectedTargets().add(signUsageTarget(existing));
+ messageRows.add(existing);
+ }
+ // 缁撴灉鐧昏鎴愬姛鍚庯紝鎸夋湰琛屽紓甯歌皟鏌ョ被鍨嬪彂璧� AD/OOS/OOT 璋冩煡宸ヤ綔娴併��
+ // best-effort锛氬け璐ュ彧璁版棩蹇� + 鏀堕泦娴佹按鍙蜂緵鍝嶅簲鎻愮ず锛屼笉涓婃姏锛堝惁鍒欎細瑙﹀彂 Controller 灞備簨鍔″洖婊氾紝杩炲甫鍥炴粴缁撴灉鐧昏锛夈��
+ if (rows > 0) {
+ try {
+ limsYichangDiaochaService.createIfNeeded(existing, item);
+ } catch (Exception e) {
+ log.error("[submit-result] 鍙戣捣寮傚父璋冩煡娴佺▼澶辫触 id={} type={}: {}",
+ item.getId(), item.getYichangDiaochaLeixing(), e.getMessage(), e);
+ if (existing.getJianyanLiushuihao() != null && !existing.getJianyanLiushuihao().isEmpty()) {
+ outcome.getFlowLaunchFailed().add(existing.getJianyanLiushuihao());
+ }
+ }
+ }
+ totalAffected += rows;
+ }
+
+ // 鍏滃簳瑙﹀彂鎶ュ憡鐢熸垚銆�
+ //
+ // 鍘嗗彶鑳屾櫙锛氭棭鏈熷妫�椤瑰厑璁�"褰曞叆瀹屽嵆灏辩华"锛屼笉璧板鏍� 鈥斺�� 褰撲竴涓� liushuihao
+ // 涓嬪叏鏄妫�椤规椂锛屾案杩滀笉浼氳皟鍒� passJianyanAudit锛岄渶瑕佽繖閲屽湪褰曞叆鍚庡厹搴�
+ // 灏濊瘯鐢熸垚鎶ュ憡銆�
+ //
+ // 鐜扮姸锛�2026-05-20 璧凤級锛氬妫�涔熷繀椤� audited 鎵嶈涓哄氨缁紝瑙�
+ // LimsQingyandanReportServiceImpl.hasUnauditedXiang 鐨勫巻鍙插彉鏇磋鏄庛��
+ // 鍥犳鏈厹搴曞疄闄呯瓑鍚屼簬 no-op 鈥斺�� 褰曞叆瀹屽彧鍒� pending_audit锛�
+ // generateReportData 浼氳瀹堥棬鎷掔粷銆備繚鐣欒繖娈佃皟鐢ㄦ槸"鏃犲鐨勯槻寰$綉"锛�
+ // 鑻ユ湭鏉ュ啀娆″嚭鐜�"鏃犻渶澶嶆牳"鐨勫揩閫熼�氶亾锛堝绱ф�ユ斁琛岋級锛屼笉蹇呭啀鍔犺Е鍙戠偣銆�
+ if (!affectedQingyandanIds.isEmpty()) {
+ for (String qingyandanId : affectedQingyandanIds) {
+ limsQingyandanReportService.generateReportData(qingyandanId);
+ }
+ }
+
+ outcome.setAffected(totalAffected);
+ if (!messageRows.isEmpty()) {
+ operationalMessagePublisher.inspectionResultsPendingReview(messageRows, userInfo, now);
+ }
+ return outcome;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @AuditLog(
+ action = "AUDIT_PASS", label = "妫�楠岀粨鏋滃鏍搁�氳繃", bizType = "JIANCE_TASK",
+ bizCodeExpr = "#result.affected > 0 ? #result.affectedTargets[0].bizCode : ''",
+ targetTable = "lims_jianyan_xiang", targetIdExpr = "#param.data[0].id",
+ targetIdsExpr = "#param.data.![id]",
+ diff = true, entityClass = LimsJianyanXiangEntity.class
+ )
+ public LimsJianyanAuditOutcome passJianyanAudit(LimsJianyanAuditParam param) {
+ LimsJianyanAuditOutcome outcome = new LimsJianyanAuditOutcome();
+ if (param == null || param.getData() == null || param.getData().isEmpty()) {
+ return outcome;
+ }
+ outcome.setTotal(param.getData().size());
+ if (param.getBizSign() == null || param.getBizSign().isEmpty()) {
+ return outcome;
+ }
+ List<String> validIds = param.getData().stream()
+ .filter(i -> i != null && i.getId() != null && !i.getId().isEmpty())
+ .map(LimsJianyanAuditItem::getId)
+ .collect(Collectors.toList());
+ if (validIds.isEmpty()) {
+ return outcome;
+ }
+
+ UserInfo auditor = UserProvider.getUser();
+ String auditorId = auditor.getUserId();
+
+ // 涓�娆℃煡鍥炴墍鏈夋湭杞垹鐨勮锛岀己澶卞嵆 notFound锛涜瀛樺湪浣嗙姸鎬佷笉绗﹀嵆 wrongStatus
+ List<LimsJianyanXiangEntity> allRows = loadRowsByIds(validIds);
+ Map<String, LimsJianyanXiangEntity> rowMap = allRows.stream()
+ .filter(r -> r != null && r.getId() != null)
+ .collect(Collectors.toMap(LimsJianyanXiangEntity::getId, r -> r, (a, b) -> a));
+
+ List<LimsJianyanXiangEntity> pendingAuditRows = new ArrayList<>();
+ for (String id : validIds) {
+ LimsJianyanXiangEntity r = rowMap.get(id);
+ if (r == null) {
+ outcome.getNotFoundIds().add(id);
+ } else if (!JianyanStatus.PENDING_AUDIT.getCode().equals(r.getJianyanZhuangtai())) {
+ outcome.getWrongStatusIds().add(id);
+ } else {
+ pendingAuditRows.add(r);
+ }
+ }
+
+ if (pendingAuditRows.isEmpty()) {
+ return outcome;
+ }
+
+ Set<String> auditorOrganizes = fetchAuditorOrganizes(auditorId);
+ List<String> passedIds = classifyAuditableRows(pendingAuditRows, auditorId, auditorOrganizes, outcome);
+ if (passedIds.isEmpty()) {
+ return outcome;
+ }
+ List<String> affectedIds = new ArrayList<>();
+ for (String id : passedIds) {
+ if (markAudited(id, auditorId) > 0) {
+ affectedIds.add(id);
+ outcome.getAffectedTargets().add(signUsageTarget(rowMap.get(id)));
+ }
+ }
+ outcome.setAffected(affectedIds.size());
+ if (!affectedIds.isEmpty()) {
+ tryGenerateReports(affectedIds);
+ }
+ return outcome;
+ }
+
+ /**
+ * 瀹℃牳閫氳繃鍚庯紝瀵规湰鎵� audited 娑夊強鐨勮楠屽崟閫愪釜灏濊瘯鐢熸垚鎶ュ憡銆�
+ * 涓� audit-pass 鍦ㄥ悓涓�浜嬪姟鍐咃細鎶ュ憡鐢熸垚澶辫触浼氬洖婊氭暣涓姩浣溿��
+ */
+ private void tryGenerateReports(List<String> auditedIds) {
+ // 涓嶉檺瀹� select 鍒楋紝閬垮厤 JNPF 鎷︽埅鍣ㄥ閮ㄥ垎鍒楃粨鏋滅疆 null锛堣 loadRowsByStatus 娉ㄩ噴锛夈��
+ QueryWrapper<LimsJianyanXiangEntity> sq = new QueryWrapper<>();
+ sq.in("f_id", auditedIds);
+ List<LimsJianyanXiangEntity> rows = limsJianyanXiangMapper.selectList(sq);
+ Set<String> qingyandanIds = rows.stream()
+ .filter(r -> r != null)
+ .map(LimsJianyanXiangEntity::getQingyandanId)
+ .filter(s -> s != null && !s.isEmpty())
+ .collect(Collectors.toSet());
+ for (String qingyandanId : qingyandanIds) {
+ limsQingyandanReportService.generateReportData(qingyandanId);
+ }
+ }
+
+ /** 鏌ラ�変腑 id 鍒楄〃涓浜庢寚瀹氱姸鎬佷笖鏈蒋鍒犵殑琛屻��
+ * 娉ㄦ剰锛氫笉瑕佺敤 sq.select(...) 闄愬畾鍒楋紝鍚﹀垯 JNPF 澶氱鎴�/鏁版嵁鏉冮檺鎷︽埅鍣�
+ * 鍦ㄧ粨鏋滄槧灏勫悗缃鐞嗛樁娈典細鎶婄己灏� f_tenant_id 绛夌郴缁熷垪鐨� entity 鏁翠釜缃负 null銆� */
+ private List<LimsJianyanXiangEntity> loadRowsByStatus(List<String> ids, JianyanStatus status) {
+ QueryWrapper<LimsJianyanXiangEntity> sq = new QueryWrapper<>();
+ sq.in("f_id", ids);
+ sq.eq("jianyan_zhuangtai", status.getCode());
+ sq.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ return limsJianyanXiangMapper.selectList(sq);
+ }
+
+ /**
+ * 鏌ュ鏍镐汉鎵�灞炵殑閮ㄩ棬 id 闆嗗悎锛坆ase_user_relation.f_object_type='organize'锛夈��
+ * 瓒呯骇绠$悊鍛橈紙UserInfo.isAdministrator锛夎繑鍥� null 浣滀负"鍏ㄧ彮缁勯�氳璇�"锛�
+ * 鐢� {@link #classifyAuditableRows} 璇嗗埆鍚庤烦杩囩彮缁勬牎楠屻��
+ */
+ private Set<String> fetchAuditorOrganizes(String auditorId) {
+ UserInfo userInfo = UserProvider.getUser();
+ if (Boolean.TRUE.equals(userInfo.getIsAdministrator())) {
+ return null;
+ }
+ List<UserRelationEntity> relations = userRelationApi.getList(auditorId, PermissionConst.ORGANIZE);
+ if (relations == null || relations.isEmpty()) {
+ return new HashSet<>();
+ }
+ return relations.stream()
+ .map(UserRelationEntity::getObjectId)
+ .filter(s -> s != null && !s.isEmpty())
+ .collect(Collectors.toSet());
+ }
+
+ /** 閫愯鍐欏叆瀹℃牳浜猴紝浣胯繑鍥炲�艰兘绮剧‘瀵瑰簲鐪熸鍛戒腑鐨� ID銆� */
+ private int markAudited(String id, String auditorId) {
+ UpdateWrapper<LimsJianyanXiangEntity> uw = new UpdateWrapper<>();
+ uw.eq("f_id", id);
+ uw.eq("jianyan_zhuangtai", JianyanStatus.PENDING_AUDIT.getCode());
+ uw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ uw.set("renwu_shenheren", auditorId);
+ uw.set("jianyan_zhuangtai", JianyanStatus.AUDITED.getCode());
+ uw.set("renwu_shenheren_shijian", new Date());
+ return limsJianyanXiangMapper.update(null, uw);
+ }
+
+ @Override
+ public LimsJianyanAuditOutcome failJianyanAudit(LimsJianyanAuditFailParam param) {
+ LimsJianyanAuditOutcome outcome = new LimsJianyanAuditOutcome();
+ if (param == null || param.getData() == null || param.getData().isEmpty()) {
+ return outcome;
+ }
+ outcome.setTotal(param.getData().size());
+ if (param.getBizSign() == null || param.getBizSign().isEmpty()) {
+ return outcome;
+ }
+ if (param.getNote() == null || param.getNote().isEmpty()) {
+ return outcome;
+ }
+ List<String> validIds = param.getData().stream()
+ .filter(i -> i != null && i.getId() != null && !i.getId().isEmpty())
+ .map(LimsJianyanAuditItem::getId)
+ .collect(Collectors.toList());
+ if (validIds.isEmpty()) {
+ return outcome;
+ }
+
+ UserInfo auditor = UserProvider.getUser();
+ String auditorId = auditor.getUserId();
+
+ // 涓�娆℃煡鍥炴墍鏈夋湭杞垹鐨勮锛岀己澶卞嵆 notFound锛涜瀛樺湪浣嗙姸鎬佷笉绗﹀嵆 wrongStatus
+ List<LimsJianyanXiangEntity> allRows = loadRowsByIds(validIds);
+ Map<String, LimsJianyanXiangEntity> rowMap = allRows.stream()
+ .filter(r -> r != null && r.getId() != null)
+ .collect(Collectors.toMap(LimsJianyanXiangEntity::getId, r -> r, (a, b) -> a));
+
+ List<LimsJianyanXiangEntity> pendingAuditRows = new ArrayList<>();
+ for (String id : validIds) {
+ LimsJianyanXiangEntity r = rowMap.get(id);
+ if (r == null) {
+ outcome.getNotFoundIds().add(id);
+ } else if (!JianyanStatus.PENDING_AUDIT.getCode().equals(r.getJianyanZhuangtai())) {
+ outcome.getWrongStatusIds().add(id);
+ } else {
+ pendingAuditRows.add(r);
+ }
+ }
+
+ if (pendingAuditRows.isEmpty()) {
+ return outcome;
+ }
+
+ Set<String> auditorOrganizes = fetchAuditorOrganizes(auditorId);
+ List<String> passedIds = classifyAuditableRows(pendingAuditRows, auditorId, auditorOrganizes, outcome);
+ if (passedIds.isEmpty()) {
+ return outcome;
+ }
+ List<String> affectedIds = new ArrayList<>();
+ for (String id : passedIds) {
+ if (markAuditRejected(id, param.getNote(), auditorId) > 0) {
+ affectedIds.add(id);
+ outcome.getAffectedTargets().add(signUsageTarget(rowMap.get(id)));
+ }
+ }
+ outcome.setAffected(affectedIds.size());
+
+ String auditExtraJson = JsonUtil.getObjectToString(
+ Collections.singletonMap("reason", param.getNote()));
+ for (String id : affectedIds) {
+ LimsJianyanXiangEntity row = rowMap.get(id);
+ if (row == null) continue;
+ recordAuditFailEvent(row.getJianyanLiushuihao(), id, auditExtraJson);
+ }
+ if (!affectedIds.isEmpty()) {
+ List<LimsJianyanXiangEntity> rejectedRows = affectedIds.stream()
+ .map(rowMap::get).filter(java.util.Objects::nonNull).collect(Collectors.toList());
+ operationalMessagePublisher.inspectionResultsRejected(
+ rejectedRows, param.getNote(), auditor, new Date());
+ }
+ return outcome;
+ }
+
+ /**
+ * 灞� 3 鏄惧紡鍐欎竴鏉°�屾楠岀粨鏋滈」鐩��鍥炪�嶅璁′簨浠躲��
+ *
+ * <p>涓轰粈涔堣蛋灞� 3 鑰屼笉鏄柟娉曠骇 {@code @AuditLog}锛氭湰鍔ㄤ綔鍦� {@code for} 寰幆閲�**閫愰」**鍐� N 鏉�
+ * 锛堟瘡涓閫�鍥炵殑妫�楠岄」涓�鏉°�乥izCode 鍚勪笉鐩稿悓锛夛紝鏂规硶绾ф敞瑙e彧浼氫骇鍑� 1 鏉°��
+ *
+ * <p>{@code operationId} **鏈柟娉曚笉浼�**锛屼氦 {@code AuditTxHolder} 鍗曟簮琛ラ綈鈥斺�攕pec 濂戠害瑙勫畾
+ * 瀹冪敱 holder 鐢熸垚銆佽皟鐢ㄦ柟涓庡垏闈㈤兘涓嶅緱鑷鐢熸垚銆傛敞鎰忋�屼笉浼犮�嶄笉绛変簬銆岃惤搴撲负 NULL銆嶏細
+ * {@code submit} 鐨勪笁鏉¤矾寰勶紙浜嬩欢鑷甫 鈫� 澶栧眰 MDC 鈫� 鏂扮敓鎴愶級閮戒笉鐣欑┖锛屼簨鍔″垎鏀繕浼氭妸
+ * 缂撳啿鍐呮墍鏈変簨浠剁粺涓�瑕嗙洊鎴愬悓涓�涓� {@code buffer.operationId}銆俢ontroller 涓婃湁 @Transactional锛�
+ * 鏁呮湰鏂规硶鍦� for 寰幆閲屼骇鍑虹殑 N 鏉′簨浠�**鍚屽睘涓�涓搷浣滀綔鐢ㄥ煙**锛屾煡璇晶鑳芥姌鍙犳垚涓�娆℃搷浣溿��
+ *
+ * <p>锛堝嫎璇細dddce53 鍘熸敞閲婂啓銆屽埢鎰忕暀绌恒�佷唬浠锋槸鎶樺彔涓嶅埌涓�璧枫�嶏紝閭f槸鏈墽琛屽氨涓嬬殑缁撹锛�
+ * 2026-07-27 绔埌绔疄娴� 3 鏉′簨浠跺叡浜悓涓�涓潪绌� operation_id锛岃 tasks/audit-layer3-e2e.sh A6銆傦級
+ */
+ private void recordAuditFailEvent(String jianyanLiushuihao, String rowId, String extraJson) {
+ auditClientProvider.ifAvailable(client -> client.record(AuditEventDTO.builder()
+ .eventType(AuditConsts.TYPE_BIZ_ACTION)
+ .actionCode(BizLogAction.AUDIT_FAIL.getCode())
+ .actionLabel("妫�楠岀粨鏋滈」鐩��鍥�")
+ .bizType(BizLogType.JIANCE_TASK.getCode())
+ .bizCode(jianyanLiushuihao)
+ .targetTable("lims_jianyan_xiang")
+ .targetId(rowId)
+ .sourceLayer(3)
+ .extra(extraJson)
+ .build()));
+ }
+
+ /** 璺� loadRowsByStatus 鍚屼竴鏌ヨ缁撴瀯浣嗕笉闄愬畾鐘舵�侊紝鐢ㄤ簬鍋� notFound / wrongStatus 鍖哄垎銆� */
+ private List<LimsJianyanXiangEntity> loadRowsByIds(List<String> ids) {
+ QueryWrapper<LimsJianyanXiangEntity> sq = new QueryWrapper<>();
+ sq.in("f_id", ids);
+ sq.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ return limsJianyanXiangMapper.selectList(sq);
+ }
+
+ /**
+ * 杩囨护鍙鏍� / 閫�鍥炵殑琛岋細瀹℃牳浜轰笉鏄綍鍏ヤ汉銆佷笖瀹℃牳浜哄湪璇ヨ鐝粍鍐呫��
+ * auditorOrganizes 涓� null 琛ㄧず瓒呯閫氳锛岃烦杩囩彮缁勬牎楠岋紙浠嶄繚鐣欎笉鑳藉鑷繁鐨勭害鏉燂級銆�
+ * 鎶�"鑷鑷�""闈炴湰鐝粍"涓ょ澶辫触鍘熷洜鍒嗗埆璁板叆 outcome锛屼究浜� controller 缁欏嚭閽堝鎬ч敊璇��
+ */
+ private List<String> classifyAuditableRows(List<LimsJianyanXiangEntity> rows,
+ String auditorId,
+ Set<String> auditorOrganizes,
+ LimsJianyanAuditOutcome outcome) {
+ List<String> passedIds = new ArrayList<>();
+ for (LimsJianyanXiangEntity r : rows) {
+ if (r == null || r.getId() == null || r.getId().isEmpty()) {
+ continue;
+ }
+ if (auditorId.equals(r.getJianchaRen())) {
+ outcome.getSelfRejectIds().add(r.getId());
+ continue;
+ }
+ if (auditorOrganizes != null) {
+ if (r.getJianchaBanzu() == null || !auditorOrganizes.contains(r.getJianchaBanzu())) {
+ outcome.getNotInBanzuIds().add(r.getId());
+ continue;
+ }
+ }
+ passedIds.add(r.getId());
+ }
+ return passedIds;
+ }
+
+ /** 鏁存壒鍒嗘鍏ュ弬鐨勭函鏍¢獙闃舵锛涜繑鍥炲悗涓氬姟寰幆鎵嶅厑璁告墽琛岀涓�鏉� UPDATE銆� */
+ static void validateFenjianItems(List<LimsFenjianItem> items) {
+ if (items == null) {
+ return;
+ }
+ int idx = 0;
+ for (LimsFenjianItem item : items) {
+ idx++;
+ if (item == null || item.getId() == null || item.getId().isEmpty()) {
+ continue;
+ }
+ String label = "绗� " + idx + " 鏉�";
+ if (item.getFenjianLiang() == null || item.getFenjianLiang().isEmpty()) {
+ throw new DataException(label + " 缂哄皯鍒嗘閲�");
+ }
+ if (item.getFenjianDanwei() == null || item.getFenjianDanwei().isEmpty()) {
+ throw new DataException(label + " 缂哄皯鍒嗘鍗曚綅");
+ }
+ if (item.getFenjianFenzhuangRongqi() == null || item.getFenjianFenzhuangRongqi().isEmpty()) {
+ throw new DataException(label + " 缂哄皯鍒嗚瀹瑰櫒");
+ }
+ if (item.getFenjianBaocunTiaojian() == null || item.getFenjianBaocunTiaojian().isEmpty()) {
+ throw new DataException(label + " 缂哄皯鍒嗘鍚庡緟妫�鏍峰搧淇濆瓨鏉′欢");
+ }
+ }
+ }
+
+ private void updateQingyandanKucun(String jianyanLiushuihao, BigDecimal fenjianShuliang) {
+ if (jianyanLiushuihao == null || jianyanLiushuihao.trim().isEmpty()) {
+ throw new DataException("缂哄皯妫�楠屾祦姘村彿");
+ }
+
+ QueryWrapper<LimsQingyandanEntity> queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("jianyan_liushuihao", jianyanLiushuihao);
+ LimsQingyandanEntity qingyandan = limsQingyandanMapper.selectOne(queryWrapper);
+ if (qingyandan == null) {
+ throw new DataException("鏈壘鍒版楠屾祦姘村彿瀵瑰簲鐨勮楠屽崟");
+ }
+
+ BigDecimal kucunShuliang = parseFenjianQuantity(qingyandan.getKucunShuliang(), "搴撳瓨鏁伴噺");
+ BigDecimal shengyuShuliang = kucunShuliang.subtract(fenjianShuliang).max(BigDecimal.ZERO);
+ UpdateWrapper<LimsQingyandanEntity> updateWrapper = new UpdateWrapper<>();
+ updateWrapper.eq("f_id", qingyandan.getId());
+ updateWrapper.set("kucun_shuliang", shengyuShuliang.stripTrailingZeros().toPlainString());
+ limsQingyandanMapper.update(null, updateWrapper);
+ }
+
+ private BigDecimal parseFenjianQuantity(String value, String fieldName) {
+ if (value == null || value.trim().isEmpty()) {
+ throw new DataException(fieldName + "涓嶈兘涓虹┖");
+ }
+ try {
+ return new BigDecimal(value.trim());
+ } catch (NumberFormatException e) {
+ throw new DataException(fieldName + "鏍煎紡涓嶆纭�");
+ }
+ }
+
+ /** 鏁存壒鍒嗛厤鍏ュ弬鐨勭函鏍¢獙闃舵锛涚己澶变笟鍔¤浠嶆寜鍘熸湁閮ㄥ垎鎴愬姛璇箟璺宠繃銆� */
+ static void validateAssignItems(List<LimsJianyanAssignItem> items,
+ Map<String, LimsJianyanXiangEntity> rowMap) {
+ if (items == null) {
+ return;
+ }
+ int idx = 0;
+ for (LimsJianyanAssignItem item : items) {
+ idx++;
+ if (item == null || item.getId() == null || item.getId().isEmpty()) {
+ continue;
+ }
+ LimsJianyanXiangEntity existing = rowMap == null ? null : rowMap.get(item.getId());
+ if (existing == null) {
+ continue;
+ }
+ String label = existing.getJianyanXiangmuMingcheng() != null
+ ? existing.getJianyanXiangmuMingcheng()
+ : "绗� " + idx + " 鏉�";
+ if ("yes".equalsIgnoreCase(existing.getShifouWaijian())) {
+ if (item.getWaijianDanwei() == null || item.getWaijianDanwei().isEmpty()) {
+ throw new DataException("澶栨椤广��" + label + "銆嶅繀椤诲~鍐欏妫�妫�娴嬪崟浣�");
+ }
+ if (item.getWaijianLururen() == null || item.getWaijianLururen().isEmpty()) {
+ throw new DataException("澶栨椤广��" + label + "銆嶅繀椤诲~鍐欏妫�缁撴灉褰曞叆浜�");
+ }
+ } else if (item.getJianchaRen() == null || item.getJianchaRen().isEmpty()) {
+ throw new DataException(label + " 鏈�夋嫨妫�楠屽憳");
+ }
+ }
+ }
+
+ /** 閫愯閫�鍥烇紝浣块儴鍒嗘垚鍔熸椂鍙簿纭褰曞疄闄� ID銆� */
+ private int markAuditRejected(String id, String note, String auditorId) {
+ UpdateWrapper<LimsJianyanXiangEntity> uw = new UpdateWrapper<>();
+ uw.eq("f_id", id);
+ uw.eq("jianyan_zhuangtai", JianyanStatus.PENDING_AUDIT.getCode());
+ uw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ uw.set("renwu_tuihuiren", auditorId);
+ uw.set("tuihui_yuanyin", note);
+ uw.set("jianyan_zhuangtai", JianyanStatus.AUDIT_REJECTED.getCode());
+ return limsJianyanXiangMapper.update(null, uw);
+ }
+
+ private LimsSignUsageTarget signUsageTarget(LimsJianyanXiangEntity row) {
+ return LimsSignUsageTarget.builder()
+ .targetTable("lims_jianyan_xiang")
+ .targetId(row == null ? null : row.getId())
+ .bizType(BizLogType.JIANCE_TASK.getCode())
+ .bizModule("LIMS-妫�娴嬩换鍔�")
+ .bizCode(row == null ? null : row.getJianyanLiushuihao())
+ .build();
+ }
+}
--
Gitblit v1.8.0