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/LimsQingyandanReportServiceImpl.java |  302 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 302 insertions(+), 0 deletions(-)

diff --git a/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsQingyandanReportServiceImpl.java b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsQingyandanReportServiceImpl.java
new file mode 100644
index 0000000..637bd26
--- /dev/null
+++ b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsQingyandanReportServiceImpl.java
@@ -0,0 +1,302 @@
+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.sdk.annotation.AuditLog;
+import jnpf.audit.AuditConsts;
+import jnpf.base.UserInfo;
+import jnpf.limsEntity.BaogaoStatus;
+import jnpf.limsEntity.JianyanJielun;
+import jnpf.limsEntity.JianyanStatus;
+import jnpf.limsEntity.LimsBaogaoSubmitParam;
+import jnpf.limsEntity.LimsJianyanXiangEntity;
+import jnpf.limsEntity.LimsQingyandanEntity;
+import jnpf.limsEntity.bizlog.BizLogAction;
+import jnpf.limsEntity.bizlog.BizLogType;
+import jnpf.limsService.LimsJianyanXiangService;
+import jnpf.limsService.LimsQingyandanReportService;
+import jnpf.limsService.LimsQingyandanService;
+import jnpf.util.JsonUtil;
+import jnpf.util.UserProvider;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 鎶ュ憡鐢熷懡鍛ㄦ湡鏈嶅姟瀹炵幇銆傛姤鍛婂凡鍚堝苟杩� lims_qingyandan锛屾墍鏈夊姩浣滄寜璇烽獙鍗� id 鍦ㄨ琛ㄧ殑
+ * baogao_* 鍒椾笂娴佽浆锛屼笉鍐嶈鍐� lims_qingyandan_report銆�
+ */
+@Service
+public class LimsQingyandanReportServiceImpl implements LimsQingyandanReportService {
+
+    @Autowired
+    private LimsQingyandanService limsQingyandanService;
+
+    @Autowired
+    private LimsJianyanXiangService limsJianyanXiangService;
+
+    @Autowired
+    private LimsOperationalMessagePublisher operationalMessagePublisher;
+
+    /**
+     * 灞� 3 鏄惧紡瀹¤鍏ュ彛銆�**蹇呴』鐢� {@link ObjectProvider} 娉ㄥ叆**锛歿@code audit.enabled=false} 鏃�
+     * 鏁翠釜鑷姩閰嶇疆绫昏繛鍚� AuditClient Bean 涓�璧风己甯紝鏅�� @Autowired 浼氳鏈湇鍔¤捣涓嶆潵銆�
+     */
+    @Autowired
+    private ObjectProvider<AuditClient> auditClientProvider;
+
+    @Override
+    public boolean generateReportData(String qingyandanId) {
+        if (qingyandanId == null || qingyandanId.isEmpty()) {
+            return false;
+        }
+
+        // 1. 婧愯楠屽崟锛堜富閿煡锛夈�備笉瀛樺湪 / 宸茶蒋鍒犵洿鎺ヨ烦杩囷紙涓嶆姏寮傚父锛岄伩鍏嶅洖婊氫笂娓哥殑瀹℃牳鍔ㄤ綔锛�
+        LimsQingyandanEntity qingyandan = limsQingyandanService.getById(qingyandanId);
+        if (qingyandan == null
+                || (qingyandan.getDeleteMark() != null && qingyandan.getDeleteMark() == 1)) {
+            return false;
+        }
+        // 骞傜瓑锛歜aogao_zhuangtai 闈炵┖涓插嵆瑙嗕负宸茬敓鎴愶紙鍚凡鎻愪氦/瀹℃牳/鎵瑰噯锛夛紝璺宠繃
+        if (qingyandan.getBaogaoZhuangtai() != null && !qingyandan.getBaogaoZhuangtai().isEmpty()) {
+            return false;
+        }
+
+        // 2. 璇ヨ楠屽崟涓嬫墍鏈夋湭杞垹鐨勬楠岄」锛氬垽"鍏� audited" + 绠� shifou_hege
+        List<LimsJianyanXiangEntity> xiangRows = loadXiangRowsByQingyandanId(qingyandanId);
+        if (xiangRows.isEmpty() || hasUnauditedXiang(xiangRows)) {
+            return false;
+        }
+
+        // 3. 鍦ㄨ楠屽崟琛屼笂钀芥姤鍛婂垵濮嬫暟鎹紙浠呭綋 baogao_zhuangtai 浠嶄负绌轰覆锛岄槻骞跺彂锛�
+        UpdateWrapper<LimsQingyandanEntity> uw = new UpdateWrapper<>();
+        uw.eq("f_id", qingyandanId);
+        uw.eq("baogao_zhuangtai", "");
+        uw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        uw.set("baogao_zhuangtai", BaogaoStatus.CREATED.getCode());
+        uw.set("baogao_shifou_hege", computeShifouHege(xiangRows));
+        // 鎶ュ憡涔︾紪鍙烽娆$敓鎴愬悗淇濇寔绋冲畾锛堥��鍥炰笉娓呯┖锛夛紝閬垮厤閲嶆柊鐢熸垚鏃剁紪鍙疯澶嶇敤
+        if (qingyandan.getBaogaoBianhao() == null || qingyandan.getBaogaoBianhao().isEmpty()) {
+            uw.set("baogao_bianhao", generateBaogaoBianhao(qingyandan.getChanpinBianma()));
+        }
+        return limsQingyandanService.update(null, uw);
+    }
+
+    /** 鎷胯璇烽獙鍗曚笅鎵�鏈夋湭杞垹鐨勬楠岄」銆備笉瑕佺敤 select 闄愬畾鍒楋紝JNPF 鎷︽埅鍣ㄤ細鎶婄己绯荤粺鍒楃殑琛屾暣浣撶疆 null銆� */
+    private List<LimsJianyanXiangEntity> loadXiangRowsByQingyandanId(String qingyandanId) {
+        QueryWrapper<LimsJianyanXiangEntity> wrapper = new QueryWrapper<>();
+        wrapper.eq("qingyandan_id", qingyandanId);
+        wrapper.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        return limsJianyanXiangService.list(wrapper);
+    }
+
+    /** 鎵�鏈夋楠岄」锛堝惈澶栨锛夊繀椤� audited 鎵嶅氨缁紝浠绘剰涓�琛屼笉灏辩华鍗� true銆� */
+    private boolean hasUnauditedXiang(List<LimsJianyanXiangEntity> rows) {
+        return rows.stream()
+                .filter(Objects::nonNull)
+                .anyMatch(r -> !JianyanStatus.AUDITED.getCode().equals(r.getJianyanZhuangtai()));
+    }
+
+    /** 浠讳竴妫�楠岄」缁撹 non_compliant 鈫� "no"锛屽惁鍒� "yes"銆� */
+    private String computeShifouHege(List<LimsJianyanXiangEntity> rows) {
+        boolean anyNonCompliant = rows.stream()
+                .filter(Objects::nonNull)
+                .anyMatch(r -> JianyanJielun.NON_COMPLIANT.getCode().equals(r.getJianyanJielun()));
+        return anyNonCompliant ? "no" : "yes";
+    }
+
+    /**
+     * 鐢熸垚鎶ュ憡涔︾紪鍙凤細{chanpin_bianma}-{yyyyDDD}A{NN}锛屼緥锛欳T001-2026005A01銆�
+     * NN 鍙栧悓 (chanpin_bianma, yyyyDDD) 鍓嶇紑鐨勫凡鍒嗛厤缂栧彿鏁� + 1锛�2 浣嶈ˉ闆躲��
+     * 缂栧彿涓�缁忓垎閰嶄笉鍐嶆竻绌猴紙閫�鍥炰繚鐣欙級锛岃鏁板崟璋冿紝閬垮厤澶嶇敤銆�
+     */
+    private String generateBaogaoBianhao(String chanpinBianma) {
+        String yyyyDDD = new SimpleDateFormat("yyyyDDD").format(new Date());
+        String prefix = (chanpinBianma == null ? "" : chanpinBianma) + "-" + yyyyDDD + "A";
+        QueryWrapper<LimsQingyandanEntity> wrapper = new QueryWrapper<>();
+        wrapper.likeRight("baogao_bianhao", prefix);
+        long n = limsQingyandanService.count(wrapper) + 1;
+        return prefix + String.format("%02d", n);
+    }
+
+    @Override
+    @AuditLog(
+            action = "REPORT_GENERATE", label = "鐢熸垚鎶ュ憡", bizType = "BAOGAO",
+            bizCodeExpr = "#result ? #param.id : ''",
+            targetTable = "lims_qingyandan", targetIdExpr = "#param.id",
+            diff = true, entityClass = LimsQingyandanEntity.class
+    )
+    public boolean submitReport(LimsBaogaoSubmitParam param) {
+        UserInfo userInfo = UserProvider.getUser();
+        LimsQingyandanEntity report = limsQingyandanService.getById(param.getId());
+        if (report == null) {
+            return false;
+        }
+        Date submittedAt = new Date();
+        UpdateWrapper<LimsQingyandanEntity> uw = new UpdateWrapper<>();
+        uw.eq("f_id", param.getId());
+        // 浠� created 鍙"鐢熸垚鎶ュ憡"锛岄槻骞跺彂 / 闃查噸澶嶇偣鍑�
+        uw.eq("baogao_zhuangtai", BaogaoStatus.CREATED.getCode());
+        uw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        uw.set("baogao_ren", userInfo.getUserId());
+        uw.set("baogao_riqi", param.getBaogaoRiqi());
+        uw.set("baogao_beizhu", param.getBeizhu());
+        uw.set("baogao_zhuangtai", BaogaoStatus.PENDING_AUDIT.getCode());
+        // 閲嶆柊鐢熸垚鎶ュ憡鏃舵竻绌轰笂涓�娆¢��鍥炵棔杩�
+        uw.set("baogao_tuihui_yuanyin", null);
+        uw.set("baogao_tuihui_ren", null);
+        uw.set("baogao_tuihui_riqi", null);
+        boolean updated = limsQingyandanService.update(null, uw);
+        if (updated) {
+            operationalMessagePublisher.reportPendingReview(report, userInfo, submittedAt);
+        }
+        return updated;
+    }
+
+    @Override
+    @AuditLog(
+            action = "AUDIT_PASS", label = "鎶ュ憡瀹℃牳閫氳繃", bizType = "BAOGAO",
+            bizCodeExpr = "#result ? #qingyandanId : ''",
+            targetTable = "lims_qingyandan", targetIdExpr = "#qingyandanId",
+            diff = true, entityClass = LimsQingyandanEntity.class
+    )
+    public boolean auditPassReport(String qingyandanId) {
+        UserInfo userInfo = UserProvider.getUser();
+        LimsQingyandanEntity report = limsQingyandanService.getById(qingyandanId);
+        if (report == null || Objects.equals(report.getBaogaoRen(), userInfo.getUserId())) {
+            return false;
+        }
+        Date reviewedAt = new Date();
+        UpdateWrapper<LimsQingyandanEntity> uw = new UpdateWrapper<>();
+        uw.eq("f_id", qingyandanId);
+        uw.eq("baogao_zhuangtai", BaogaoStatus.PENDING_AUDIT.getCode());
+        uw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        uw.set("baogao_shenhe_ren", userInfo.getUserId());
+        uw.set("baogao_shenhe_riqi", reviewedAt);
+        uw.set("baogao_zhuangtai", BaogaoStatus.AUDITED.getCode());
+        boolean updated = limsQingyandanService.update(null, uw);
+        if (updated) {
+            operationalMessagePublisher.reportPendingApproval(report, userInfo, reviewedAt);
+        }
+        return updated;
+    }
+
+    @Override
+    @AuditLog(
+            action = "APPROVE", label = "鎶ュ憡鎵瑰噯", bizType = "BAOGAO",
+            bizCodeExpr = "#result ? #qingyandanId : ''",
+            targetTable = "lims_qingyandan", targetIdExpr = "#qingyandanId",
+            diff = true, entityClass = LimsQingyandanEntity.class
+    )
+    public boolean approveReport(String qingyandanId) {
+        UserInfo userInfo = UserProvider.getUser();
+        LimsQingyandanEntity report = limsQingyandanService.getById(qingyandanId);
+        if (report == null || Objects.equals(report.getBaogaoRen(), userInfo.getUserId())
+                || Objects.equals(report.getBaogaoShenheRen(), userInfo.getUserId())) {
+            return false;
+        }
+        UpdateWrapper<LimsQingyandanEntity> uw = new UpdateWrapper<>();
+        uw.eq("f_id", qingyandanId);
+        uw.eq("baogao_zhuangtai", BaogaoStatus.AUDITED.getCode());
+        uw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        uw.set("baogao_pizhun_ren", userInfo.getUserId());
+        uw.set("baogao_pizhun_riqi", new Date());
+        uw.set("baogao_zhuangtai", BaogaoStatus.APPROVED.getCode());
+        return limsQingyandanService.update(null, uw);
+    }
+
+    @Override
+    public boolean rejectReport(String qingyandanId, String tuihuiYuanyin) {
+        UserInfo userInfo = UserProvider.getUser();
+
+        LimsQingyandanEntity qingyandan = limsQingyandanService.getById(qingyandanId);
+        if (qingyandan == null
+                || (qingyandan.getDeleteMark() != null && qingyandan.getDeleteMark() == 1)) {
+            return false;
+        }
+        String currentStatus = qingyandan.getBaogaoZhuangtai();
+        boolean rejectable = BaogaoStatus.CREATED.getCode().equals(currentStatus)
+                || BaogaoStatus.PENDING_AUDIT.getCode().equals(currentStatus)
+                || BaogaoStatus.AUDITED.getCode().equals(currentStatus);
+        if (!rejectable) {
+            return false;
+        }
+
+        Date now = new Date();
+        List<LimsJianyanXiangEntity> affectedItems = loadXiangRowsByQingyandanId(qingyandanId);
+        // 閲嶇疆鎶ュ憡闃舵瀛楁鍥炲垵濮嬶紙baogao_bianhao 淇濈暀锛屼究浜庨噸鏂扮敓鎴愭椂娌跨敤鏃х紪鍙凤級锛�
+        // 鍐欓��鍥炰笁浠跺锛涘甫鐘舵�佸畧鍗槻骞跺彂銆�
+        UpdateWrapper<LimsQingyandanEntity> uw = new UpdateWrapper<>();
+        uw.eq("f_id", qingyandanId);
+        uw.eq("baogao_zhuangtai", currentStatus);
+        uw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        uw.set("baogao_zhuangtai", "");
+        uw.set("baogao_ren", null);
+        uw.set("baogao_riqi", null);
+        uw.set("baogao_beizhu", null);
+        uw.set("baogao_shenhe_ren", null);
+        uw.set("baogao_shenhe_riqi", null);
+        uw.set("baogao_pizhun_ren", null);
+        uw.set("baogao_pizhun_riqi", null);
+        uw.set("baogao_tuihui_yuanyin", tuihuiYuanyin);
+        uw.set("baogao_tuihui_ren", userInfo.getUserId());
+        uw.set("baogao_tuihui_riqi", now);
+        if (!limsQingyandanService.update(null, uw)) {
+            return false;
+        }
+
+        // 妫�楠岄」鎸夋姤鍛婂綋鍓嶇姸鎬佹帹鍥烇細
+        //   created 鈫� AUDIT_REJECTED锛堥噸鏂板綍鍏� 鈫� 澶嶆牳 鈫� 瑙﹀彂鏂颁竴杞敓鎴愶級
+        //   pending_audit / audited 鈫� PENDING_AUDIT锛堥噸鏂� audit-pass 鈫� 瑙﹀彂鏂颁竴杞敓鎴愶級
+        String xiangTargetStatus = BaogaoStatus.CREATED.getCode().equals(currentStatus)
+                ? JianyanStatus.AUDIT_REJECTED.getCode()
+                : JianyanStatus.PENDING_AUDIT.getCode();
+        UpdateWrapper<LimsJianyanXiangEntity> xw = new UpdateWrapper<>();
+        xw.eq("jianyan_liushuihao", qingyandan.getJianyanLiushuihao());
+        xw.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        xw.set("jianyan_zhuangtai", xiangTargetStatus);
+        limsJianyanXiangService.update(null, xw);
+
+        recordRejectReportEvent(qingyandan.getJianyanLiushuihao(), qingyandanId, tuihuiYuanyin);
+        operationalMessagePublisher.reportRejected(
+                qingyandan, affectedItems, currentStatus, tuihuiYuanyin, userInfo, now);
+        return true;
+    }
+
+    /**
+     * 灞� 3 鏄惧紡鍐欎竴鏉°�屾姤鍛婇��鍥炪�嶅璁′簨浠躲��
+     *
+     * <p>涓轰粈涔堣蛋灞� 3 鑰屼笉鏄柟娉曠骇 {@code @AuditLog}锛氭湰浜嬩欢鐨� bizCode 鏄�
+     * {@code qingyandan.getJianyanLiushuihao()}锛岃�� {@code qingyandan} 鏄� {@link #rejectReport}
+     * 鏂规硶浣撳唴 {@code getById} 鏌ュ嚭鏉ョ殑**灞�閮ㄥ彉閲�**鈥斺�旀柟娉曠骇 SpEL 鍙兘鍙栨柟娉曞弬鏁�
+     * 锛坽@code qingyandanId} / {@code tuihuiYuanyin}锛変笌 {@code #result}锛屽涓嶇潃瀹冦��
+     *
+     * <p>{@code operationId} **鏈柟娉曚笉浼�**锛屼氦 {@code AuditTxHolder} 鍗曟簮琛ラ綈锛岀悊鐢卞悓
+     * {@code LimsJianceTaskServiceImpl#recordAuditFailEvent}鈥斺�斿悓澶勪篃璁颁簡銆屽師娉ㄩ噴璇寸暀绌恒�嶇殑鍕樿銆�
+     * 瀹炴祴鏈簨浠剁殑 operation_id 闈炵┖锛屼笖涓庡悓涓�鎵� AUDIT_FAIL 閭f涓嶅悓锛堜袱娆℃搷浣滃悇鑷垚鍩燂級銆�
+     *
+     * <p>extra 鐢� {@code reason} 閿紝涓庡眰 2 @AuditLog 鐨� {@code extraExpr="{'reason': 鈥"} 瀵归綈锛�
+     * 涓嶆部鐢ㄦ棫琛ㄧ殑 {@code note}鈥斺�斿惁鍒欏悓涓� actionCode 鍦� audit_events 閲屽嚭鐜颁袱绉嶉敭鍚嶃��
+     */
+    private void recordRejectReportEvent(String jianyanLiushuihao, String qingyandanId, String tuihuiYuanyin) {
+        auditClientProvider.ifAvailable(client -> client.record(AuditEventDTO.builder()
+                .eventType(AuditConsts.TYPE_BIZ_ACTION)
+                .actionCode(BizLogAction.RETURN.getCode())
+                .actionLabel("鎶ュ憡閫�鍥�")
+                .bizType(BizLogType.BAOGAO.getCode())
+                .bizCode(jianyanLiushuihao)
+                .targetTable("lims_qingyandan")
+                .targetId(qingyandanId)
+                .sourceLayer(3)
+                .extra(JsonUtil.getObjectToString(Collections.singletonMap("reason", tuihuiYuanyin)))
+                .build()));
+    }
+}

--
Gitblit v1.8.0