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

diff --git a/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsQingyandanServiceImpl.java b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsQingyandanServiceImpl.java
new file mode 100644
index 0000000..beea976
--- /dev/null
+++ b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsQingyandanServiceImpl.java
@@ -0,0 +1,702 @@
+package jnpf.limsService.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import jnpf.audit.sdk.annotation.AuditLog;
+import jnpf.base.ActionResult;
+import jnpf.base.BillRuleApi;
+import jnpf.base.UserInfo;
+import jnpf.base.service.SuperServiceImpl;
+import jnpf.exception.DataException;
+import jnpf.limsEntity.*;
+import jnpf.limsMapper.LimsQingyandanMapper;
+import jnpf.limsService.*;
+import jnpf.util.JsonUtil;
+import jnpf.util.RandomUtil;
+import jnpf.util.StringUtil;
+import jnpf.util.UserProvider;
+import org.springframework.beans.BeanUtils;
+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.*;
+import java.util.stream.Collectors;
+
+@Service
+public class LimsQingyandanServiceImpl extends SuperServiceImpl<LimsQingyandanMapper, LimsQingyandanEntity>
+        implements LimsQingyandanService {
+
+    /**
+     * 鐣欐牱鎺ユ敹缂栧彿瑙勫垯 enCode
+     */
+    private static final String LIUYANG_JIESHOU_BIANHAO_RULE = "liuyangJieshouBianhaoGuize";
+    private static final String JIANYAN_LIUSHUIHAO_RULE = "jianyan_liushuihao";
+
+    @Autowired
+    private BillRuleApi billRuleApi;
+
+    @Autowired
+    private ZhiliangBiaozhunGuanliService zhiliangBiaozhunGuanliService;
+
+    @Autowired
+    private ZhiliangBiaozhunGuanliZixiangService zhiliangBiaozhunGuanliZixiangService;
+
+    @Autowired
+    private LimsJianyanXiangService limsJianyanXiangService;
+
+    @Autowired
+    private LimsLiuyangJiluService limsLiuyangJiluService;
+
+    @Autowired
+    private LimsSxtRenwuQingyandanZhuangtaiTongbuService renwuQingyandanZhuangtaiTongbuService;
+
+    @Autowired
+    private LimsOperationalMessagePublisher operationalMessagePublisher;
+
+    @Override
+    public LimsQingyandanEntity findByFormData(LimsQingyandanEntity formData) {
+        if (formData.getId() != null && !formData.getId().isEmpty()) {
+            return this.getById(formData.getId());
+        }
+        // JNPF 鍦ㄧ嚎琛ㄥ崟淇濆瓨鍚庝笉杩斿洖璁板綍ID锛岄�氳繃涓氬姟瀛楁鍖归厤
+        QueryWrapper<LimsQingyandanEntity> wrapper = new QueryWrapper<>();
+        wrapper.eq("chanpin_id", formData.getChanpinId());
+        if (formData.getPihao() != null) {
+            wrapper.eq("pihao", formData.getPihao());
+        }
+        wrapper.orderByDesc("f_id");
+        wrapper.last("LIMIT 1");
+        return this.getOne(wrapper);
+    }
+
+    @Override
+    public LimsQingyandanEntity findByLiushuihao(String liushuihao) {
+        QueryWrapper<LimsQingyandanEntity> wrapper = new QueryWrapper<>();
+        wrapper.eq("jianyan_liushuihao", liushuihao);
+        return this.getOne(wrapper);
+    }
+
+    @Override
+    @AuditLog(
+            action = "SUBMIT", label = "璇烽獙鍗� 鎻愪氦", bizType = "QINGYANDAN",
+            bizCodeExpr = "#qingyandan.jianyanLiushuihao",
+            targetTable = "lims_qingyandan", targetIdExpr = "#qingyandan.id",
+            diff = true, entityClass = LimsQingyandanEntity.class
+    )
+    @Transactional(rollbackFor = Exception.class)
+    public void submit(LimsQingyandanEntity qingyandan) {
+        String expectedBizStatus = qingyandan.getBizStatus();
+        ZhiliangBiaozhunGuanliEntity standard = findReadyQualityStandardOrThrow(qingyandan);
+        copyStandardLiangFields(qingyandan, standard);
+        qingyandan.setBizStatus(BizStatus.CONFIRMED.getCode());
+        // 璇烽獙鍗曟彁浜ゅ悗杩涘叆鍙栨牱闃舵锛氬彇鏍风姸鎬佸垵濮嬪寲涓哄緟鍙栨牱
+        qingyandan.setQuyangStatus(BizStatus.PENDING_FETCH.getCode());
+        backfillCreatorTimeIfMissing(qingyandan);
+        UpdateWrapper<LimsQingyandanEntity> submitUpdate = activeRowUpdate(qingyandan.getId());
+        submitUpdate.eq("biz_status", expectedBizStatus);
+        if (!this.update(qingyandan, submitUpdate)) {
+            throw new DataException("璇烽獙鍗曠姸鎬佸凡鍙樻洿锛屾彁浜ゆ湭鐢熸晥");
+        }
+        createJianyanXiang(qingyandan, standard);
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+    }
+
+    @Override
+    public void wendingxingSubmit(LimsQingyandanEntity qingyandan) {
+        ZhiliangBiaozhunGuanliEntity standard = findReadyQualityStandardOrThrow(qingyandan);
+        createJianyanXiang(qingyandan, standard);
+    }
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public LimsQingyandanEntity sxtAutoSubmit(LimsQingyandanEntity qingyandan) {
+        ZhiliangBiaozhunGuanliEntity standard = findReadyQualityStandardOrThrow(qingyandan);
+        String plannedJianyanLiang = qingyandan.getJianyanLiang();
+        String plannedQuyangDanwei = qingyandan.getQuyangDanwei();
+
+        copyStandardLiangFields(qingyandan, standard);
+        if (StringUtil.isNotEmpty(plannedJianyanLiang)) {
+            qingyandan.setJianyanLiang(plannedJianyanLiang);
+        }
+        if (StringUtil.isNotEmpty(plannedQuyangDanwei)) {
+            qingyandan.setQuyangDanwei(plannedQuyangDanwei);
+        }
+        qingyandan.setBizStatus(BizStatus.CONFIRMED.getCode());
+        // 璇烽獙鍗曟彁浜ゅ悗杩涘叆鍙栨牱闃舵锛氬彇鏍风姸鎬佸垵濮嬪寲涓哄緟鍙栨牱
+        qingyandan.setQuyangStatus(BizStatus.PENDING_FETCH.getCode());
+        qingyandan.setJianyanLiushuihao(nextJianyanBianhao());
+        backfillCreatorTimeIfMissing(qingyandan);
+        this.saveOrUpdate(qingyandan);
+        createJianyanXiang(qingyandan, standard);
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+        return qingyandan;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public LimsQingyandanEntity sxtRenwuKuaizhaoAutoSubmit(
+            LimsQingyandanEntity qingyandan,
+            List<LimsSxtQuyangJihuaRenwuJianyanXiangKuaizhaoEntity> jianyanKuaizhaoList) {
+        if (jianyanKuaizhaoList == null || jianyanKuaizhaoList.isEmpty()) {
+            throw new DataException("浠诲姟妫�楠岄」蹇収涓嶈兘涓虹┖");
+        }
+        if (StringUtil.isEmpty(qingyandan.getLaiyuanRenwuId())) {
+            throw new DataException("璇烽獙鍗曟潵婧愪换鍔′笉鑳戒负绌�");
+        }
+        if (StringUtil.isEmpty(qingyandan.getId())) {
+            qingyandan.setId(RandomUtil.uuId());
+        }
+        qingyandan.setBizStatus(BizStatus.CONFIRMED.getCode());
+        qingyandan.setQuyangStatus(BizStatus.PENDING_FETCH.getCode());
+        qingyandan.setJianyanLiushuihao(nextJianyanBianhao());
+        backfillCreatorTimeIfMissing(qingyandan);
+        this.save(qingyandan);
+
+        List<LimsJianyanXiangEntity> records = new ArrayList<>(jianyanKuaizhaoList.size());
+        for (LimsSxtQuyangJihuaRenwuJianyanXiangKuaizhaoEntity kuaizhao : jianyanKuaizhaoList) {
+            records.add(buildXiangFromRenwuKuaizhao(qingyandan, kuaizhao));
+        }
+        limsJianyanXiangService.saveBatch(records);
+        return qingyandan;
+    }
+
+    @Override
+    @AuditLog(
+            action = "UPDATE", label = "璇烽獙鍗� 缂栬緫", bizType = "QINGYANDAN",
+            bizCodeExpr = "#qingyandan.jianyanLiushuihao",
+            targetTable = "lims_qingyandan", targetIdExpr = "#qingyandan.id",
+            diff = true, entityClass = LimsQingyandanEntity.class
+    )
+    @Transactional(rollbackFor = Exception.class)
+    public void edit(LimsQingyandanEntity qingyandan, LimsQingyandanEntity formData) {
+        String expectedBizStatus = qingyandan.getBizStatus();
+        String originalId = qingyandan.getId();
+        // 鎺掗櫎鍚庣绠$悊鐨勭姸鎬佸瓧娈碉細鍓嶇 formData 涓嶅甫杩欎簺瀛楁锛孊eanUtils 浼氱敤 null 瑕嗙洊銆�
+        // 灏ゅ叾 quyangStatus 鍔犱簡 updateStrategy=ALWAYS锛岃瑕嗙洊鎴� null 浼氭挒 NOT NULL 绾︽潫銆�
+        BeanUtils.copyProperties(formData, qingyandan,
+                "id", "tenantId", "bizStatus", "quyangStatus", "jieyangStatus",
+                "laiyuanRenwuId", "renwuKuaizhaoHaxi",
+                "deleteMark", "deleteTime",
+                "deleteUserId", "flowId", "version", "flowTaskId", "flowState",
+                "jianyanLiushuihao", "creatorUserId", "creatorTime",
+                "lastModifyUserId", "lastModifyTime");
+        qingyandan.setId(originalId);
+
+        ZhiliangBiaozhunGuanliEntity standard = findReadyQualityStandardOrThrow(qingyandan);
+        copyStandardLiangFields(qingyandan, standard);
+
+        qingyandan.setBizStatus(BizStatus.CONFIRMED.getCode());
+        // 閫�鍥炲悗淇閲嶆柊鎻愪氦锛氬彇鏍风姸鎬侀噸鏂板洖鍒板緟鍙栨牱锛堜笌 submit 涓�鑷达級
+        qingyandan.setQuyangStatus(BizStatus.PENDING_FETCH.getCode());
+        backfillCreatorTimeIfMissing(qingyandan);
+        UpdateWrapper<LimsQingyandanEntity> editUpdate = activeRowUpdate(qingyandan.getId());
+        editUpdate.eq("biz_status", expectedBizStatus);
+        if (!this.update(qingyandan, editUpdate)) {
+            throw new DataException("璇烽獙鍗曠姸鎬佸凡鍙樻洿锛岄噸鏂版彁浜ゆ湭鐢熸晥");
+        }
+
+        // 缂栬緫鍚庨噸鏂版彁浜わ細閲嶅缓妫�楠岄」锛堝厛杞垹鏃х殑锛岄伩鍏嶉噸澶嶏級
+        softDeleteJianyanXiangRecords(qingyandan.getId());
+        createJianyanXiang(qingyandan, standard);
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(
+            action = "FETCH",
+            label = "鍙栨牱",
+            bizType = "QUYANG",
+            bizCodeExpr = "#qingyandan.jianyanLiushuihao",
+            targetTable = "lims_qingyandan",
+            targetIdExpr = "#qingyandan.id",
+            diff = true,
+            entityClass = LimsQingyandanEntity.class
+    )
+    public void fetch(LimsQingyandanEntity qingyandan, LimsQingyandanEntity formData) {
+        String expectedQuyangStatus = qingyandan.getQuyangStatus();
+        // 鍙啓鍙栨牱闃舵褰曞叆瀛楁锛屼笉鐢� BeanUtils 鍏ㄩ噺澶嶅埗锛岄伩鍏嶈鐩� submit 鏃朵粠璐ㄩ噺鏍囧噯
+        // 澶嶅埗鐨勯噺/鍗曚綅瀛楁锛坖ianyan_liang / quyang_danwei / jiance_liuyang_liang /
+        // liuyang_liang / liuyang_danwei / shifou_shixiaoxing_yaoqiu锛夈��
+        qingyandan.setJianyanLiangShiji(formData.getJianyanLiangShiji());
+        qingyandan.setJianceLiuyangLiangShiji(formData.getJianceLiuyangLiangShiji());
+        qingyandan.setLiuyangLiangShiji(formData.getLiuyangLiangShiji());
+        qingyandan.setQuyangren(formData.getQuyangren());
+        qingyandan.setQuyangRiqi(formData.getQuyangRiqi());
+        qingyandan.setQuyangDidian(formData.getQuyangDidian());
+        qingyandan.setQuyangBeizhu(formData.getQuyangBeizhu());
+        qingyandan.setBizSign(formData.getBizSign());
+
+        // 鍙栨牱瀹屾垚锛氬彇鏍风姸鎬� 鈫� 宸插彇鏍凤紝鎺ユ牱鐘舵�� 鈫� 寰呮帴鏍凤紱biz_status 涓荤姸鎬佷繚鎸佷笉鍙�
+        qingyandan.setQuyangStatus(BizStatus.FETCHED.getCode());
+        qingyandan.setJieyangStatus(BizStatus.PENDING_RECEIVE.getCode());
+        LimsLiuyangJiluEntity liuyangJilu = null;
+        if (isPositiveNumber(qingyandan.getLiuyangLiangShiji())) {
+            liuyangJilu = buildLiuyangJiluFromQingyandan(qingyandan);
+            limsLiuyangJiluService.save(liuyangJilu);
+            qingyandan.setLiuyangJiluId(liuyangJilu.getId());
+        }
+        UpdateWrapper<LimsQingyandanEntity> fetchUpdate = activeRowUpdate(qingyandan.getId());
+        fetchUpdate.eq("quyang_status", expectedQuyangStatus);
+        if (!this.update(qingyandan, fetchUpdate)) {
+            throw new DataException("璇烽獙鍗曠姸鎬佸凡鍙樻洿锛屽彇鏍锋湭鐢熸晥");
+        }
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+        if (liuyangJilu != null) {
+            operationalMessagePublisher.retainSamplePendingReceive(
+                    liuyangJilu, qingyandan, UserProvider.getUser());
+        }
+    }
+
+    private boolean isPositiveNumber(String value) {
+        if (value == null || value.trim().isEmpty()) {
+            return false;
+        }
+        try {
+            return new BigDecimal(value.trim()).compareTo(BigDecimal.ZERO) > 0;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    private LimsLiuyangJiluEntity buildLiuyangJiluFromQingyandan(LimsQingyandanEntity qingyandan) {
+        LimsLiuyangJiluEntity record = new LimsLiuyangJiluEntity();
+        record.setId(RandomUtil.uuId());
+        record.setJieshouBianhao(nextLiuyangJieshouBianhao());
+        record.setYangpinMingcheng(qingyandan.getChanpinMingcheng());
+        record.setYangpinBianma(qingyandan.getChanpinBianma());
+        record.setQingyandanId(qingyandan.getId());
+        record.setBizType(qingyandan.getBizType());
+        record.setGuige(qingyandan.getGuige());
+        record.setBaozhuangGuige(qingyandan.getBaozhuangGuige());
+        record.setPihao(qingyandan.getPihao());
+        record.setLaiyuanBianma(qingyandan.getLaiyuanBianma());
+        record.setShengchanRiqi(qingyandan.getShengchanRiqi());
+        record.setYouxiaoqi(qingyandan.getYouxiaoqi());
+        record.setChucunTiaojian(qingyandan.getChucunTiaojian());
+        record.setSongjianRen(qingyandan.getQingyanRen());
+        record.setSongjianRiqi(qingyandan.getQingyanRiqi());
+        record.setBizSign(qingyandan.getBizSign());
+        record.setBizStatus(LiuyangStatus.PENDING_RECEIVE.getCode());
+        record.setLiuyangLiangShiji(qingyandan.getLiuyangLiangShiji());
+        record.setLiuyangDanwei(qingyandan.getLiuyangDanwei());
+        return record;
+    }
+
+    /**
+     * 鐣欐牱鎺ユ敹缂栧彿鍙栧彿澶辫触鏃剁珛鍗充腑鏂紝閬垮厤鐢熸垚绌虹紪鍙风殑鐣欐牱璁板綍銆�
+     */
+    private String nextLiuyangJieshouBianhao() throws DataException {
+        ActionResult<String> result = billRuleApi.getBillNumber(LIUYANG_JIESHOU_BIANHAO_RULE);
+        String bianhao = result == null ? null : result.getData();
+        if (StringUtil.isEmpty(bianhao)) {
+            throw new DataException("鐣欐牱鎺ユ敹缂栧彿鍙栧彿澶辫触锛氬崟鎹鍒橻" + LIUYANG_JIESHOU_BIANHAO_RULE + "]涓嶅彲鐢紝璇风‘璁よ鍒欏凡閰嶇疆涓旂郴缁熸湇鍔″湪绾�");
+        }
+        return bianhao;
+    }
+
+    /**
+     * 鐢熸垚鏂扮殑妫�楠屾祦姘村彿
+     */
+    private String nextJianyanBianhao() throws DataException {
+        ActionResult<String> result = billRuleApi.getBillNumber(JIANYAN_LIUSHUIHAO_RULE);
+        String bianhao = result == null ? null : result.getData();
+        if (StringUtil.isEmpty(bianhao)) {
+            throw new DataException("妫�楠屾祦姘村彿鍙栧彿澶辫触锛氬崟鎹鍒橻" + JIANYAN_LIUSHUIHAO_RULE + "]涓嶅彲鐢紝璇风‘璁よ鍒欏凡閰嶇疆涓旂郴缁熸湇鍔″湪绾�");
+        }
+        return bianhao;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(
+            action = "RECEIVE",
+            label = "鎺ユ牱",
+            bizType = "JIEYANG",
+            bizCodeExpr = "#qingyandan.jianyanLiushuihao",
+            targetTable = "lims_qingyandan",
+            targetIdExpr = "#qingyandan.id",
+            diff = true,
+            entityClass = LimsQingyandanEntity.class
+    )
+    public void jieyangReceive(LimsQingyandanEntity qingyandan, LimsQingyandanEntity formData) {
+        String expectedJieyangStatus = qingyandan.getJieyangStatus();
+        // 鍙啓鎺ユ牱闃舵褰曞叆瀛楁锛涙楠岄」 submit 鏃跺凡鍒涘缓锛坈reated + wei_fenjian锛夛紝
+        // 鏈涓嶅垱寤恒�佷笉鎺ㄨ繘妫�楠岄」鐘舵�侊紙鐢卞悗缁崟鐙殑"鍒嗘"鍔ㄤ綔澶勭悊锛夈��
+        qingyandan.setJieshouren(formData.getJieshouren());
+        qingyandan.setJieshouRiqi(formData.getJieshouRiqi());
+        qingyandan.setJieshouShuliang(formData.getJieshouShuliang());
+        qingyandan.setJieshouShuliangDanwei(formData.getJieshouShuliangDanwei());
+        qingyandan.setCunfangWeizhi(formData.getCunfangWeizhi());
+        qingyandan.setShixiaoxingJianceyuan(formData.getShixiaoxingJianceyuan());
+        qingyandan.setJieyangBeizhu(formData.getJieyangBeizhu());
+        qingyandan.setBizSign(formData.getBizSign());
+
+        // 鎺ユ牱瀹屾垚
+        qingyandan.setJieyangStatus(BizStatus.RECEIVED.getCode());
+        UpdateWrapper<LimsQingyandanEntity> receiveUpdate = activeRowUpdate(qingyandan.getId());
+        receiveUpdate.eq("jieyang_status", expectedJieyangStatus);
+        if (!this.update(qingyandan, receiveUpdate)) {
+            throw new DataException("璇烽獙鍗曠姸鎬佸凡鍙樻洿锛屾帴鏍锋湭鐢熸晥");
+        }
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+    }
+
+    private UpdateWrapper<LimsQingyandanEntity> activeRowUpdate(String id) {
+        UpdateWrapper<LimsQingyandanEntity> update = new UpdateWrapper<>();
+        update.eq("f_id", id);
+        update.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        return update;
+    }
+
+    /**
+     * 璇烽獙鍗曞垱寤� / 缂栬緫鎻愪氦鏃讹紝浠庤川閲忔爣鍑嗕富琛ㄥ鍒堕噺涓庡崟浣嶉厤缃埌璇烽獙鍗曟湰琛岋細
+     * 鍙栨牱鍗曚綅銆佹楠岄噺锛堚啇 jiance_liang锛夈�佹娴嬬暀鏍烽噺銆佺暀鏍烽噺銆佺暀鏍峰崟浣嶏紙鈫� liuyang_kaocha_danwei锛夈��
+     * 瀵瑰簲鐨� _shiji 瀹為檯鍊煎瓧娈电暀绌猴紝鐢卞悗缁彇鏍� / 鎺ユ牱闃舵褰曞叆銆�
+     */
+    private void copyStandardLiangFields(LimsQingyandanEntity qingyandan, ZhiliangBiaozhunGuanliEntity standard) {
+        qingyandan.setQuyangDanwei(standard.getQuyangDanwei());
+        qingyandan.setJianyanLiang(standard.getJianceLiang());
+        qingyandan.setJianceLiuyangLiang(standard.getJianceLiuyangLiang());
+        qingyandan.setLiuyangLiang(standard.getLiuyangLiang());
+        qingyandan.setLiuyangDanwei(standard.getLiuyangKaochaDanwei());
+        qingyandan.setShifouShixiaoxingYaoqiu(standard.getShifouShixiaoxingYaoqiu());
+    }
+
+    /**
+     * lims_qingyandan 鐢� JNPF 鍦ㄧ嚎琛ㄥ崟 INSERT锛岀粫寮� MyBatis-Plus锛�
+     * MetaObjectHandler 鐨� insertFill 涓嶈Е鍙戯紝瀵艰嚧 f_creator_time 姘歌繙涓� null銆�
+     * 鍦ㄦ垜浠笟鍔′唬鐮侀娆℃帴绠¤繖鏉¤褰曟椂锛坰ubmit / edit锛夛紝濡傛灉杩樻病鍊煎氨琛ュ~褰撳墠鏃堕棿銆�
+     */
+    private void backfillCreatorTimeIfMissing(LimsQingyandanEntity qingyandan) {
+        if (qingyandan.getCreatorTime() == null) {
+            qingyandan.setCreatorTime(new Date());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(
+            action = "CANCEL",
+            label = "璇烽獙鍗� 鎾ゅ洖",
+            bizType = "QINGYANDAN",
+            bizCodeExpr = "#result ? #qingyandan.jianyanLiushuihao : null",
+            targetTable = "lims_qingyandan",
+            targetIdExpr = "#qingyandan.id",
+            diff = true,
+            entityClass = LimsQingyandanEntity.class
+    )
+    public boolean cancel(LimsQingyandanEntity qingyandan) {
+        UpdateWrapper<LimsQingyandanEntity> update = activeRowUpdate(qingyandan.getId());
+        update.eq("biz_status", BizStatus.CONFIRMED.getCode());
+        update.set("biz_status", BizStatus.CANCELLED.getCode());
+        if (!this.update(update)) {
+            return false;
+        }
+
+        softDeleteJianyanXiangRecords(qingyandan.getId());
+        qingyandan.setBizStatus(BizStatus.CANCELLED.getCode());
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(
+            action = "RETURN",
+            label = "鍙栨牱 閫�鍥�",
+            bizType = "QINGYANDAN",
+            bizCodeExpr = "#qingyandan.jianyanLiushuihao",
+            extraExpr = "{'reason': #tuihuiyuanyin}",
+            targetTable = "lims_qingyandan",
+            targetIdExpr = "#qingyandan.id",
+            diff = true,
+            entityClass = LimsQingyandanEntity.class
+    )
+    public boolean reject(LimsQingyandanEntity qingyandan, String tuihuiyuanyin, String bizSign) {
+        UserInfo userInfo = UserProvider.getUser();
+        Date now = new Date();
+        UpdateWrapper<LimsQingyandanEntity> update = new UpdateWrapper<>();
+        update.eq("f_id", qingyandan.getId());
+        update.in("biz_status", BizStatus.PENDING_FETCH.getCode(), BizStatus.CONFIRMED.getCode());
+        update.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        update.set("biz_status", BizStatus.REJECTED.getCode());
+        update.set("quyang_status", "");
+        update.set("quyang_tuihui_ren", userInfo.getUserId());
+        update.set("quyang_tuihui_yuanyin", tuihuiyuanyin);
+        update.set("quyang_tuihui_riqi", now);
+        if (!this.update(update)) {
+            return false;
+        }
+
+        softDeleteJianyanXiangRecords(qingyandan.getId());
+        qingyandan.setBizStatus(BizStatus.REJECTED.getCode());
+        qingyandan.setQuyangStatus("");
+        qingyandan.setQuyangTuihuiRen(userInfo.getUserId());
+        qingyandan.setQuyangTuihuiYuanyin(tuihuiyuanyin);
+        qingyandan.setQuyangTuihuiRiqi(now);
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(
+            action = "RETURN",
+            label = "鎺ユ牱 閫�鍥�",
+            bizType = "JIEYANG",
+            bizCodeExpr = "#qingyandan.jianyanLiushuihao",
+            extraExpr = "{'reason': #tuihuiyuanyin}",
+            targetTable = "lims_qingyandan",
+            targetIdExpr = "#qingyandan.id",
+            diff = true,
+            entityClass = LimsQingyandanEntity.class
+    )
+    public boolean jieyangReject(LimsQingyandanEntity qingyandan, String tuihuiyuanyin, String bizSign) {
+        UserInfo userInfo = UserProvider.getUser();
+        Date now = new Date();
+        UpdateWrapper<LimsQingyandanEntity> update = new UpdateWrapper<>();
+        update.eq("f_id", qingyandan.getId());
+        update.eq("jieyang_status", BizStatus.PENDING_RECEIVE.getCode());
+        update.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        update.set("quyang_status", BizStatus.PENDING_FETCH.getCode());
+        update.set("jieyang_status", "");
+        update.set("jieyang_tuihui_yuanyin", tuihuiyuanyin);
+        update.set("jieyang_tuihui_ren", userInfo.getUserId());
+        update.set("jieyang_tuihui_riqi", now);
+        if (!this.update(update)) {
+            return false;
+        }
+
+        // 鎺ユ牱閫�鍥� 鈫� 閫�鍥炲彇鏍烽樁娈甸噸鏂板彇鏍凤細鍙栨牱鐘舵�佸洖鍒板緟鍙栨牱銆佹帴鏍风姸鎬佹竻绌猴紝
+        // biz_status 涓荤姸鎬佷笉鍙橈紱妫�楠岄」浠嶅湪娴佺▼涓紝涓嶈蒋鍒犮��
+        qingyandan.setQuyangStatus(BizStatus.PENDING_FETCH.getCode());
+        qingyandan.setJieyangStatus("");
+        qingyandan.setJieyangTuihuiYuanyin(tuihuiyuanyin);
+        qingyandan.setJieyangTuihuiRen(userInfo.getUserId());
+        qingyandan.setJieyangTuihuiRiqi(now);
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AuditLog(
+            action = "DELETE",
+            label = "璇烽獙鍗� 搴熷純",
+            bizType = "QINGYANDAN",
+            bizCodeExpr = "#qingyandan.jianyanLiushuihao",
+            targetTable = "lims_qingyandan",
+            targetIdExpr = "#qingyandan.id",
+            diff = true,
+            entityClass = LimsQingyandanEntity.class
+    )
+    public void delete(LimsQingyandanEntity qingyandan) {
+        softDeleteJianyanXiangRecords(qingyandan.getId());
+        qingyandan.setBizStatus(BizStatus.DELETED.getCode());
+        this.updateById(qingyandan);
+        tongbuRenwuQingyandanZhuangtai(qingyandan);
+    }
+
+    private void tongbuRenwuQingyandanZhuangtai(LimsQingyandanEntity qingyandan) {
+        renwuQingyandanZhuangtaiTongbuService.tongbu(qingyandan, "qingyandan_zhuangtai_tongbu");
+    }
+
+    /**
+     * 鎵归噺杞垹闄よ璇烽獙鍗曚笅鎵�鏈夋湭杞垹鐨勬楠岄」锛坙ims_jianyan_xiang锛夈��
+     * 鎾ゅ洖 / 閫�鍥� / 搴熷純鏃惰皟鐢紝鎾ゆ帀鏈鎻愪氦鐢熸垚鐨勬楠屼换鍔¢」銆�
+     */
+    private void softDeleteJianyanXiangRecords(String qingyandanId) {
+        UserInfo userInfo = UserProvider.getUser();
+        QueryWrapper<LimsJianyanXiangEntity> wrapper = new QueryWrapper<>();
+        wrapper.eq("qingyandan_id", qingyandanId);
+        wrapper.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+        List<LimsJianyanXiangEntity> list = limsJianyanXiangService.list(wrapper);
+        for (LimsJianyanXiangEntity item : list) {
+            item.setDeleteMark(1);
+            item.setDeleteTime(new Date());
+            item.setDeleteUserId(userInfo.getUserId());
+            limsJianyanXiangService.updateById(item);
+        }
+    }
+
+    /**
+     * 鎻愪氦璇烽獙鍗曟椂锛屾寜璐ㄩ噺鏍囧噯瀛愯〃锛坺hiliang_biaozhun_guanli_zixiang锛夐�愭潯鐩村啓 lims_jianyan_xiang銆�
+     * 鏇夸唬鏃х殑"鍐� lims_qingyandan_jiancexiang 涓棿琛�"閾捐矾锛氭楠岄」鍒濆 jianyan_zhuangtai = created銆�
+     * fenjian_zhuangtai = wei_fenjian锛岀瓑鍚庣画"鍒嗘"鍔ㄤ綔鎺ㄨ繘鍒� pending_assign 杩涘叆鍒嗛厤/褰曞叆/瀹℃牳娴佺▼銆�
+     *
+     * <p>鍙敓鎴愯楠屽崟 jiance_xiangmu 閫変腑鐨勫瓙椤癸細璇ュ瓧娈靛瓨鍓嶇鍕鹃�夌殑 zixiang.f_id 鏁扮粍
+     * 锛堝叏妫�浼氫紶鍏ㄩ儴 id锛岄儴鍒嗘浼犲瓙闆嗭級銆俲iance_xiangmu 涓虹┖鏃跺厹搴曞彇鍏ㄩ儴瀛愰」锛岄伩鍏嶅紓甯告儏鍐垫紡鐢熸垚銆�
+     */
+    private void createJianyanXiang(LimsQingyandanEntity qingyandan, ZhiliangBiaozhunGuanliEntity standard) {
+        List<ZhiliangBiaozhunGuanliZixiangEntity> zixiangList =
+                zhiliangBiaozhunGuanliZixiangService.listByForeignId(standard.getId());
+        if (zixiangList == null || zixiangList.isEmpty()) {
+            return;
+        }
+
+        // 浠呬繚鐣欏墠绔�変腑鐨勬楠屽瓙椤癸紙jiance_xiangmu 瀛橀�変腑鐨� zixiang.f_id锛�
+        Set<String> selectedIds = parseSelectedXiangmuIds(qingyandan.getJianceXiangmu());
+        if (!selectedIds.isEmpty()) {
+            zixiangList = zixiangList.stream()
+                    .filter(z -> selectedIds.contains(z.getId()))
+                    .collect(Collectors.toList());
+        }
+        if (zixiangList.isEmpty()) {
+            return;
+        }
+
+        List<LimsJianyanXiangEntity> records = new ArrayList<>(zixiangList.size());
+        for (ZhiliangBiaozhunGuanliZixiangEntity zixiang : zixiangList) {
+            records.add(buildXiangFromQingyandan(qingyandan, zixiang, standard));
+        }
+        limsJianyanXiangService.saveBatch(records);
+    }
+
+    /**
+     * 瑙f瀽璇烽獙鍗� jiance_xiangmu锛堝墠绔嬀閫夌殑妫�楠屽瓙椤� zixiang.f_id锛孞SON 鏁扮粍瀛楃涓插 ["id1","id2"]锛�
+     * 涓� id 闆嗗悎銆傜┖ / null / 瑙f瀽澶辫触杩斿洖绌洪泦锛岃皟鐢ㄦ柟鎹鍏滃簳鍙栧叏閮ㄥ瓙椤广��
+     */
+    private Set<String> parseSelectedXiangmuIds(String jianceXiangmu) {
+        if (jianceXiangmu == null || jianceXiangmu.trim().isEmpty()) {
+            return Collections.emptySet();
+        }
+        try {
+            List<String> ids = JsonUtil.getJsonToList(jianceXiangmu, String.class);
+            if (ids == null) {
+                return Collections.emptySet();
+            }
+            return ids.stream()
+                    .filter(s -> s != null && !s.isEmpty())
+                    .collect(Collectors.toCollection(LinkedHashSet::new));
+        } catch (Exception e) {
+            // 瀹归敊锛氭牸寮忓紓甯告椂杩斿洖绌洪泦锛屽厹搴曡蛋鍏ㄩ儴瀛愰」锛岄伩鍏嶆紡鐢熸垚
+            return Collections.emptySet();
+        }
+    }
+
+    /**
+     * 浠庤楠屽崟 + 璐ㄩ噺鏍囧噯瀛愰」鏋勯�犱竴琛� lims_jianyan_xiang銆�
+     * 澶翠俊鎭紙浜у搧 / 鎵瑰彿 / 瑙勬牸 / 娴佹按鍙� / 鍌ㄥ瓨鏉′欢锛変粠璇烽獙鍗曞彇锛屾楠岄」閰嶇疆浠庡瓙椤瑰彇銆�
+     */
+    private LimsJianyanXiangEntity buildXiangFromQingyandan(LimsQingyandanEntity qingyandan,
+                                                            ZhiliangBiaozhunGuanliZixiangEntity zixiang,
+                                                            ZhiliangBiaozhunGuanliEntity standard) {
+        LimsJianyanXiangEntity record = new LimsJianyanXiangEntity();
+        record.setTenantId(qingyandan.getTenantId());
+        record.setForeignId(zixiang.getId());
+        record.setQingyandanId(qingyandan.getId());
+        record.setBizType(qingyandan.getBizType());
+        record.setJianyanLiushuihao(qingyandan.getJianyanLiushuihao());
+        record.setChanpinId(qingyandan.getChanpinId());
+        record.setChanpinBianma(qingyandan.getChanpinBianma());
+        record.setChanpinMingcheng(qingyandan.getChanpinMingcheng());
+        record.setPihao(qingyandan.getPihao());
+        record.setGuige(qingyandan.getGuige());
+        record.setJianyanXiangmuBianma(zixiang.getJianyanXiangmuBianma());
+        record.setJianyanXiangmuMingcheng(zixiang.getJianyanXiangmuMingcheng());
+        record.setFenxiLeixing(zixiang.getFenxiLeixing());
+        record.setShifouWaijian(zixiang.getShifouWaijian());
+        record.setShifouChujuBaogao(zixiang.getShifouChujuBaogao());
+        record.setBiaozhunGuiding(zixiang.getBiaozhunGuiding());
+        record.setBiaozhunShangxian(zixiang.getBiaozhunShangxian());
+        record.setBiaozhunXiaxian(zixiang.getBiaozhunXiaxian());
+        record.setShujuLeixing(zixiang.getShujuLeixing());
+        record.setJianchaBanzu(zixiang.getJianchaBanzu());
+        record.setJianyanZhuangtai(JianyanStatus.CREATED.getCode());
+        record.setFenjianZhuangtai(FenjianZhuangtai.WEI_FENJIAN.getCode());
+        record.setFenjianLiang(zixiang.getFenjianLiang());
+        record.setFenjianDanwei(zixiang.getFenjianDanwei());
+        record.setFenjianFenzhuangRongqi(zixiang.getFenjianFenzhuangRongqi());
+        record.setFenjianBaocunTiaojian(zixiang.getFenjianBaocunTiaojian());
+        record.setFenjianHuanjing(zixiang.getFenjianHuanjing());
+        // 鏄惁缁勫悎鍒嗘嫞锛氭牱鍝佺骇閰嶇疆锛屼粠璐ㄩ噺鏍囧噯涓昏〃澶嶅埗
+        record.setShifouZuheFenjian(standard.getShifouZuheFenjian());
+        return record;
+    }
+
+    private LimsJianyanXiangEntity buildXiangFromRenwuKuaizhao(
+            LimsQingyandanEntity qingyandan,
+            LimsSxtQuyangJihuaRenwuJianyanXiangKuaizhaoEntity kuaizhao) {
+        LimsJianyanXiangEntity record = new LimsJianyanXiangEntity();
+        record.setId(RandomUtil.uuId());
+        record.setTenantId(qingyandan.getTenantId());
+        record.setForeignId(kuaizhao.getLaiyuanZhiliangBiaozhunZixiangId());
+        record.setQingyandanId(qingyandan.getId());
+        record.setLaiyuanRenwuId(qingyandan.getLaiyuanRenwuId());
+        record.setRenwuJianyanXiangKuaizhaoId(kuaizhao.getId());
+        record.setBizType(qingyandan.getBizType());
+        record.setJianyanLiushuihao(qingyandan.getJianyanLiushuihao());
+        record.setChanpinId(qingyandan.getChanpinId());
+        record.setChanpinBianma(qingyandan.getChanpinBianma());
+        record.setChanpinMingcheng(qingyandan.getChanpinMingcheng());
+        record.setPihao(qingyandan.getPihao());
+        record.setGuige(qingyandan.getGuige());
+        record.setJianyanXiangmuBianma(kuaizhao.getJianyanXiangmuBianma());
+        record.setJianyanXiangmuMingcheng(kuaizhao.getJianyanXiangmuMingcheng());
+        record.setFenxiLeixing(kuaizhao.getFenxiLeixing());
+        record.setBiaozhunGuiding(kuaizhao.getBiaozhunGuiding());
+        record.setBiaozhunShangxian(kuaizhao.getBiaozhunShangxian());
+        record.setBiaozhunXiaxian(kuaizhao.getBiaozhunXiaxian());
+        record.setShujuLeixing(kuaizhao.getShujuLeixing());
+        record.setJianchaBanzu(kuaizhao.getJianchaBanzu());
+        record.setJianyanZhuangtai(JianyanStatus.CREATED.getCode());
+        record.setFenjianZhuangtai(FenjianZhuangtai.WEI_FENJIAN.getCode());
+        return record;
+    }
+
+    /**
+     * jiance_fangshi 瀛楀吀閲�"鍏嶆"瀵瑰簲鐨� enCode
+     */
+    private static final String JIANCE_FANGSHI_MIANJIAN = "mianjian";
+
+    /**
+     * 鏍¢獙璇烽獙鍗曢�変腑鐨勮川閲忔爣鍑嗘槸鍚﹂綈澶囷細
+     * - qingyandan.jianyan_biaozhun 鐩存帴瀛� zhiliang_biaozhun_guanli.f_id
+     * - 涓昏〃蹇呴』瀛樺湪 / 鏈蒋鍒� / biz_enabled=enabled
+     * - 涓斿叾 yangpin_id 蹇呴』涓� qingyandan.chanpin_id 涓�鑷�
+     * - 闈炲厤妫�鍦烘櫙鍐嶆牎楠屽瓙椤硅嚦灏� 1 鏉�
+     * 浠讳竴鏉′欢涓嶆弧瓒冲垯鎶� DataException銆�
+     */
+    private ZhiliangBiaozhunGuanliEntity findReadyQualityStandardOrThrow(LimsQingyandanEntity qingyandan) {
+        String chanpinId = qingyandan.getChanpinId();
+        String chanpinMingcheng = qingyandan.getChanpinMingcheng();
+        String label = (chanpinMingcheng != null && !chanpinMingcheng.isEmpty())
+                ? chanpinMingcheng : chanpinId;
+
+        if (chanpinId == null || chanpinId.isEmpty()) {
+            throw new DataException("璇烽獙鍗曟湭鍏宠仈鏍峰搧ID锛屾棤娉曟牎楠岃川閲忔爣鍑�");
+        }
+
+        // 璇烽獙鍗曚笂閫変腑鐨勮川閲忔爣鍑嗕富閿�
+        String biaozhunId = qingyandan.getJianyanBiaozhun();
+        if (biaozhunId == null || biaozhunId.isEmpty()) {
+            throw new DataException("璇烽獙鍗曟湭鎸囧畾妫�楠屾爣鍑嗭紝鏃犳硶瀹氫綅璐ㄩ噺鏍囧噯");
+        }
+
+        ZhiliangBiaozhunGuanliEntity standard = zhiliangBiaozhunGuanliService.getById(biaozhunId);
+        if (standard == null
+                || (standard.getDeleteMark() != null && standard.getDeleteMark() == 1)) {
+            throw new DataException("鏍峰搧銆�" + label + "銆戠殑璐ㄩ噺鏍囧噯涓嶅瓨鍦ㄦ垨宸茶鍒犻櫎锛岃鍏堝湪銆愯川閲忔爣鍑嗙鐞嗐�戜腑缁存姢鍚庡啀鎻愪氦");
+        }
+        if (!BizEnabledEnum.ENABLED.getCode().equals(standard.getBizEnabled())) {
+            throw new DataException("鏍峰搧銆�" + label + "銆戞墍閫夌殑璐ㄩ噺鏍囧噯宸插仠鐢紝璇烽噸鏂伴�夋嫨鏈夋晥鐨勮川閲忔爣鍑�");
+        }
+        if (!chanpinId.equals(standard.getYangpinId())) {
+            throw new DataException("鏍峰搧銆�" + label + "銆戜笌鎵�閫夎川閲忔爣鍑嗕笉鍖归厤锛岃閲嶆柊閫夋嫨");
+        }
+
+        // 鍏嶆鏃惰烦杩�"妫�楠岄」鐩嚦灏� 1 鏉�"鐨勬牎楠岋紱鍏朵粬妫�娴嬫柟寮忎粛瑕佹眰鑷冲皯閰嶇疆 1 椤�
+        if (!JIANCE_FANGSHI_MIANJIAN.equals(qingyandan.getJianceFangshi())) {
+            long itemCount = zhiliangBiaozhunGuanliZixiangService.countByForeignId(standard.getId());
+            if (itemCount <= 0) {
+                throw new DataException("鏍峰搧銆�" + label + "銆戠殑璐ㄩ噺鏍囧噯鏈厤缃楠岄」鐩紝璇峰厛琛ュ厖妫�楠岄」鐩悗鍐嶆彁浜�");
+            }
+        }
+
+        return standard;
+    }
+}

--
Gitblit v1.8.0